mirror of
https://github.com/overte-org/overte.git
synced 2025-08-10 06:23:06 +02:00
Merge branch 'master' of https://github.com/highfidelity/hifi into light_types
This commit is contained in:
commit
725a8795fb
26 changed files with 3224 additions and 2810 deletions
|
@ -18,4 +18,3 @@ Script.load("lobby.js");
|
||||||
Script.load("notifications.js");
|
Script.load("notifications.js");
|
||||||
Script.load("look.js");
|
Script.load("look.js");
|
||||||
Script.load("users.js");
|
Script.load("users.js");
|
||||||
Script.load("utilities/LODWarning.js");
|
|
||||||
|
|
157
examples/example/entities/makeHouses.js
Normal file
157
examples/example/entities/makeHouses.js
Normal file
|
@ -0,0 +1,157 @@
|
||||||
|
//
|
||||||
|
// makeHouses.js
|
||||||
|
//
|
||||||
|
//
|
||||||
|
// Created by Stojce Slavkovski on March 14, 2015
|
||||||
|
// Copyright 2015 High Fidelity, Inc.
|
||||||
|
//
|
||||||
|
// This sample script that creates house entities based on parameters.
|
||||||
|
//
|
||||||
|
// Distributed under the Apache License, Version 2.0.
|
||||||
|
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||||
|
//
|
||||||
|
|
||||||
|
(function () {
|
||||||
|
|
||||||
|
/** options **/
|
||||||
|
var numHouses = 100;
|
||||||
|
var xRange = 300;
|
||||||
|
var yRange = 300;
|
||||||
|
|
||||||
|
var sizeOfTheHouse = {
|
||||||
|
x: 10,
|
||||||
|
y: 15,
|
||||||
|
z: 10
|
||||||
|
};
|
||||||
|
|
||||||
|
var randomizeModels = false;
|
||||||
|
/**/
|
||||||
|
|
||||||
|
var modelUrlPrefix = "http://public.highfidelity.io/load_testing/3-Buildings-2-SanFranciscoHouse-";
|
||||||
|
var modelurlExt = ".fbx";
|
||||||
|
var modelVariations = 100;
|
||||||
|
|
||||||
|
var houses = [];
|
||||||
|
|
||||||
|
function addHouseAt(position, rotation) {
|
||||||
|
// get house model
|
||||||
|
var modelNumber = randomizeModels ?
|
||||||
|
1 + Math.floor(Math.random() * (modelVariations - 1)) :
|
||||||
|
(houses.length + 1) % modelVariations;
|
||||||
|
|
||||||
|
if (modelNumber == 0) {
|
||||||
|
modelNumber = modelVariations;
|
||||||
|
}
|
||||||
|
|
||||||
|
var modelUrl = modelUrlPrefix + (modelNumber + "") + modelurlExt;
|
||||||
|
print("Model ID:" + modelNumber);
|
||||||
|
print("Model URL:" + modelUrl);
|
||||||
|
|
||||||
|
var properties = {
|
||||||
|
type: "Model",
|
||||||
|
position: position,
|
||||||
|
rotation: rotation,
|
||||||
|
dimensions: sizeOfTheHouse,
|
||||||
|
modelURL: modelUrl
|
||||||
|
};
|
||||||
|
|
||||||
|
return Entities.addEntity(properties);
|
||||||
|
}
|
||||||
|
|
||||||
|
// calculate initial position
|
||||||
|
var posX = MyAvatar.position.x - (xRange / 2);
|
||||||
|
var measures = calculateParcels(numHouses, xRange, yRange);
|
||||||
|
var dd = 0;
|
||||||
|
|
||||||
|
// avatar facing rotation
|
||||||
|
var rotEven = Quat.fromPitchYawRollDegrees(0, 270.0 + MyAvatar.bodyYaw, 0.0);
|
||||||
|
|
||||||
|
// avatar opposite rotation
|
||||||
|
var rotOdd = Quat.fromPitchYawRollDegrees(0, 90.0 + MyAvatar.bodyYaw, 0.0);
|
||||||
|
var housePos = Vec3.sum(MyAvatar.position, Quat.getFront(Camera.getOrientation()));
|
||||||
|
|
||||||
|
for (var j = 0; j < measures.rows; j++) {
|
||||||
|
|
||||||
|
var posX1 = 0 - (xRange / 2);
|
||||||
|
dd += measures.parcelLength;
|
||||||
|
|
||||||
|
for (var i = 0; i < measures.cols; i++) {
|
||||||
|
|
||||||
|
// skip reminder of houses
|
||||||
|
if (houses.length > numHouses) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
var posShift = {
|
||||||
|
x: posX1,
|
||||||
|
y: 0,
|
||||||
|
z: dd
|
||||||
|
};
|
||||||
|
|
||||||
|
print("House nr.:" + (houses.length + 1));
|
||||||
|
houses.push(
|
||||||
|
addHouseAt(Vec3.sum(housePos, posShift), (j % 2 == 0) ? rotEven : rotOdd)
|
||||||
|
);
|
||||||
|
posX1 += measures.parcelWidth;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// calculate rows and columns in area, and dimension of single parcel
|
||||||
|
function calculateParcels(items, areaWidth, areaLength) {
|
||||||
|
|
||||||
|
var idealSize = Math.min(Math.sqrt(areaWidth * areaLength / items), areaWidth, areaLength);
|
||||||
|
|
||||||
|
var baseWidth = Math.min(Math.floor(areaWidth / idealSize), items);
|
||||||
|
var baseLength = Math.min(Math.floor(areaLength / idealSize), items);
|
||||||
|
|
||||||
|
var sirRows = baseWidth;
|
||||||
|
var sirCols = Math.ceil(items / sirRows);
|
||||||
|
var sirW = areaWidth / sirRows;
|
||||||
|
var sirL = areaLength / sirCols;
|
||||||
|
|
||||||
|
var visCols = baseLength;
|
||||||
|
var visRows = Math.ceil(items / visCols);
|
||||||
|
var visW = areaWidth / visRows;
|
||||||
|
var visL = areaLength / visCols;
|
||||||
|
|
||||||
|
var rows = 0;
|
||||||
|
var cols = 0;
|
||||||
|
var parcelWidth = 0;
|
||||||
|
var parcelLength = 0;
|
||||||
|
|
||||||
|
if (Math.min(sirW, sirL) > Math.min(visW, visL)) {
|
||||||
|
rows = sirRows;
|
||||||
|
cols = sirCols;
|
||||||
|
parcelWidth = sirW;
|
||||||
|
parcelLength = sirL;
|
||||||
|
} else {
|
||||||
|
rows = visRows;
|
||||||
|
cols = visCols;
|
||||||
|
parcelWidth = visW;
|
||||||
|
parcelLength = visL;
|
||||||
|
}
|
||||||
|
|
||||||
|
print("rows:" + rows);
|
||||||
|
print("cols:" + cols);
|
||||||
|
print("parcelWidth:" + parcelWidth);
|
||||||
|
print("parcelLength:" + parcelLength);
|
||||||
|
|
||||||
|
return {
|
||||||
|
rows: rows,
|
||||||
|
cols: cols,
|
||||||
|
parcelWidth: parcelWidth,
|
||||||
|
parcelLength: parcelLength
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function cleanup() {
|
||||||
|
while (houses.length > 0) {
|
||||||
|
if (!houses[0].isKnownID) {
|
||||||
|
houses[0] = Entities.identifyEntity(houses[0]);
|
||||||
|
}
|
||||||
|
Entities.deleteEntity(houses.shift());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Script.scriptEnding.connect(cleanup);
|
||||||
|
})();
|
|
@ -13,5 +13,4 @@ Script.load("progress.js");
|
||||||
Script.load("lobby.js");
|
Script.load("lobby.js");
|
||||||
Script.load("notifications.js");
|
Script.load("notifications.js");
|
||||||
Script.load("controllers/oculus/goTo.js");
|
Script.load("controllers/oculus/goTo.js");
|
||||||
Script.load("utilities/LODWarning.js");
|
|
||||||
//Script.load("scripts.js"); // Not created yet
|
//Script.load("scripts.js"); // Not created yet
|
||||||
|
|
|
@ -43,7 +43,6 @@
|
||||||
// after that we will send it to createNotification(text).
|
// after that we will send it to createNotification(text).
|
||||||
// If the message is 42 chars or less you should bypass wordWrap() and call createNotification() directly.
|
// If the message is 42 chars or less you should bypass wordWrap() and call createNotification() directly.
|
||||||
|
|
||||||
|
|
||||||
// To add a keypress driven notification:
|
// To add a keypress driven notification:
|
||||||
//
|
//
|
||||||
// 1. Add a key to the keyPressEvent(key).
|
// 1. Add a key to the keyPressEvent(key).
|
||||||
|
@ -85,16 +84,19 @@ var PLAY_NOTIFICATION_SOUNDS_MENU_ITEM = "Play Notification Sounds";
|
||||||
var NOTIFICATION_MENU_ITEM_POST = " Notifications";
|
var NOTIFICATION_MENU_ITEM_POST = " Notifications";
|
||||||
var PLAY_NOTIFICATION_SOUNDS_SETTING = "play_notification_sounds";
|
var PLAY_NOTIFICATION_SOUNDS_SETTING = "play_notification_sounds";
|
||||||
var PLAY_NOTIFICATION_SOUNDS_TYPE_SETTING_PRE = "play_notification_sounds_type_";
|
var PLAY_NOTIFICATION_SOUNDS_TYPE_SETTING_PRE = "play_notification_sounds_type_";
|
||||||
|
var lodTextID = false;
|
||||||
|
|
||||||
var NotificationType = {
|
var NotificationType = {
|
||||||
UNKNOWN: 0,
|
UNKNOWN: 0,
|
||||||
MUTE_TOGGLE: 1,
|
MUTE_TOGGLE: 1,
|
||||||
SNAPSHOT: 2,
|
SNAPSHOT: 2,
|
||||||
WINDOW_RESIZE: 3,
|
WINDOW_RESIZE: 3,
|
||||||
|
LOD_WARNING: 4,
|
||||||
properties: [
|
properties: [
|
||||||
{ text: "Mute Toggle" },
|
{ text: "Mute Toggle" },
|
||||||
{ text: "Snapshot" },
|
{ text: "Snapshot" },
|
||||||
{ text: "Window Resize" }
|
{ text: "Window Resize" },
|
||||||
|
{ text: "Level of Detail" }
|
||||||
],
|
],
|
||||||
getTypeFromMenuItem: function(menuItemName) {
|
getTypeFromMenuItem: function(menuItemName) {
|
||||||
if (menuItemName.substr(menuItemName.length - NOTIFICATION_MENU_ITEM_POST.length) !== NOTIFICATION_MENU_ITEM_POST) {
|
if (menuItemName.substr(menuItemName.length - NOTIFICATION_MENU_ITEM_POST.length) !== NOTIFICATION_MENU_ITEM_POST) {
|
||||||
|
@ -143,6 +145,10 @@ function createArrays(notice, button, createTime, height, myAlpha) {
|
||||||
|
|
||||||
// This handles the final dismissal of a notification after fading
|
// This handles the final dismissal of a notification after fading
|
||||||
function dismiss(firstNoteOut, firstButOut, firstOut) {
|
function dismiss(firstNoteOut, firstButOut, firstOut) {
|
||||||
|
if (firstNoteOut == lodTextID) {
|
||||||
|
lodTextID = false;
|
||||||
|
}
|
||||||
|
|
||||||
Overlays.deleteOverlay(firstNoteOut);
|
Overlays.deleteOverlay(firstNoteOut);
|
||||||
Overlays.deleteOverlay(firstButOut);
|
Overlays.deleteOverlay(firstButOut);
|
||||||
notifications.splice(firstOut, 1);
|
notifications.splice(firstOut, 1);
|
||||||
|
@ -261,7 +267,8 @@ function notify(notice, button, height) {
|
||||||
height: noticeHeight
|
height: noticeHeight
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
notifications.push((Overlays.addOverlay("text", notice)));
|
var notificationText = Overlays.addOverlay("text", notice);
|
||||||
|
notifications.push((notificationText));
|
||||||
buttons.push((Overlays.addOverlay("image", button)));
|
buttons.push((Overlays.addOverlay("image", button)));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -272,6 +279,7 @@ function notify(notice, button, height) {
|
||||||
last = notifications.length - 1;
|
last = notifications.length - 1;
|
||||||
createArrays(notifications[last], buttons[last], times[last], heights[last], myAlpha[last]);
|
createArrays(notifications[last], buttons[last], times[last], heights[last], myAlpha[last]);
|
||||||
fadeIn(notifications[last], buttons[last]);
|
fadeIn(notifications[last], buttons[last]);
|
||||||
|
return notificationText;
|
||||||
}
|
}
|
||||||
|
|
||||||
// This function creates and sizes the overlays
|
// This function creates and sizes the overlays
|
||||||
|
@ -331,11 +339,15 @@ function createNotification(text, notificationType) {
|
||||||
randomSounds.playRandom();
|
randomSounds.playRandom();
|
||||||
}
|
}
|
||||||
|
|
||||||
notify(noticeProperties, buttonProperties, height);
|
return notify(noticeProperties, buttonProperties, height);
|
||||||
}
|
}
|
||||||
|
|
||||||
function deleteNotification(index) {
|
function deleteNotification(index) {
|
||||||
Overlays.deleteOverlay(notifications[index]);
|
var notificationTextID = notifications[index];
|
||||||
|
if (notificationTextID == lodTextID) {
|
||||||
|
lodTextID = false;
|
||||||
|
}
|
||||||
|
Overlays.deleteOverlay(notificationTextID);
|
||||||
Overlays.deleteOverlay(buttons[index]);
|
Overlays.deleteOverlay(buttons[index]);
|
||||||
notifications.splice(index, 1);
|
notifications.splice(index, 1);
|
||||||
buttons.splice(index, 1);
|
buttons.splice(index, 1);
|
||||||
|
@ -575,6 +587,20 @@ function menuItemEvent(menuItem) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
LODManager.LODDecreased.connect(function() {
|
||||||
|
var warningText = "\n"
|
||||||
|
+ "Due to the complexity of the content, the \n"
|
||||||
|
+ "level of detail has been decreased."
|
||||||
|
+ "You can now see: \n"
|
||||||
|
+ LODManager.getLODFeedbackText();
|
||||||
|
|
||||||
|
if (lodTextID == false) {
|
||||||
|
lodTextID = createNotification(warningText, NotificationType.LOD_WARNING);
|
||||||
|
} else {
|
||||||
|
Overlays.editOverlay(lodTextID, { text: warningText });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
AudioDevice.muteToggled.connect(onMuteStateChanged);
|
AudioDevice.muteToggled.connect(onMuteStateChanged);
|
||||||
Controller.keyPressEvent.connect(keyPressEvent);
|
Controller.keyPressEvent.connect(keyPressEvent);
|
||||||
Controller.mousePressEvent.connect(mousePressEvent);
|
Controller.mousePressEvent.connect(mousePressEvent);
|
||||||
|
|
|
@ -1,115 +0,0 @@
|
||||||
// LODWarning.js
|
|
||||||
// examples
|
|
||||||
//
|
|
||||||
// Created by Brad Hefta-Gaub on 3/17/15.
|
|
||||||
// Copyright 2015 High Fidelity, Inc.
|
|
||||||
//
|
|
||||||
// This script will display a warning when the LOD is adjusted to do scene complexity.
|
|
||||||
//
|
|
||||||
// Distributed under the Apache License, Version 2.0.
|
|
||||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
|
||||||
//
|
|
||||||
|
|
||||||
var DISPLAY_WARNING_FOR = 3; // in seconds
|
|
||||||
var DISTANCE_FROM_CAMERA = 2;
|
|
||||||
var SHOW_LOD_UP_MESSAGE = false; // By default we only display the LOD message when reducing LOD
|
|
||||||
|
|
||||||
|
|
||||||
var warningIsVisible = false; // initially the warning is hidden
|
|
||||||
var warningShownAt = 0;
|
|
||||||
var billboardPosition = Vec3.sum(Camera.getPosition(),
|
|
||||||
Vec3.multiply(DISTANCE_FROM_CAMERA, Quat.getFront(Camera.getOrientation())));
|
|
||||||
|
|
||||||
var warningOverlay = Overlays.addOverlay("text3d", {
|
|
||||||
position: billboardPosition,
|
|
||||||
dimensions: { x: 2, y: 1.25 },
|
|
||||||
width: 2,
|
|
||||||
height: 1.25,
|
|
||||||
backgroundColor: { red: 0, green: 0, blue: 0 },
|
|
||||||
color: { red: 255, green: 255, blue: 255},
|
|
||||||
topMargin: 0.1,
|
|
||||||
leftMargin: 0.1,
|
|
||||||
lineHeight: 0.07,
|
|
||||||
text: "",
|
|
||||||
alpha: 0.5,
|
|
||||||
backgroundAlpha: 0.7,
|
|
||||||
isFacingAvatar: true,
|
|
||||||
visible: warningIsVisible,
|
|
||||||
});
|
|
||||||
|
|
||||||
// Handle moving the billboard to remain in front of the camera
|
|
||||||
var billboardNeedsMoving = false;
|
|
||||||
Script.update.connect(function() {
|
|
||||||
|
|
||||||
if (warningIsVisible) {
|
|
||||||
var bestBillboardPosition = Vec3.sum(Camera.getPosition(),
|
|
||||||
Vec3.multiply(DISTANCE_FROM_CAMERA, Quat.getFront(Camera.getOrientation())));
|
|
||||||
|
|
||||||
var MAX_DISTANCE = 0.5;
|
|
||||||
var CLOSE_ENOUGH = 0.01;
|
|
||||||
if (!billboardNeedsMoving && Vec3.distance(bestBillboardPosition, billboardPosition) > MAX_DISTANCE) {
|
|
||||||
billboardNeedsMoving = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (billboardNeedsMoving && Vec3.distance(bestBillboardPosition, billboardPosition) <= CLOSE_ENOUGH) {
|
|
||||||
billboardNeedsMoving = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (billboardNeedsMoving) {
|
|
||||||
// slurp the billboard to the best location
|
|
||||||
moveVector = Vec3.multiply(0.05, Vec3.subtract(bestBillboardPosition, billboardPosition));
|
|
||||||
billboardPosition = Vec3.sum(billboardPosition, moveVector);
|
|
||||||
Overlays.editOverlay(warningOverlay, { position: billboardPosition });
|
|
||||||
}
|
|
||||||
|
|
||||||
var now = new Date();
|
|
||||||
var sinceWarningShown = now - warningShownAt;
|
|
||||||
if (sinceWarningShown > 1000 * DISPLAY_WARNING_FOR) {
|
|
||||||
warningIsVisible = false;
|
|
||||||
Overlays.editOverlay(warningOverlay, { visible: warningIsVisible });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
LODManager.LODIncreased.connect(function() {
|
|
||||||
if (SHOW_LOD_UP_MESSAGE) {
|
|
||||||
// if the warning wasn't visible, then move it before showing it.
|
|
||||||
if (!warningIsVisible) {
|
|
||||||
billboardPosition = Vec3.sum(Camera.getPosition(),
|
|
||||||
Vec3.multiply(DISTANCE_FROM_CAMERA, Quat.getFront(Camera.getOrientation())));
|
|
||||||
Overlays.editOverlay(warningOverlay, { position: billboardPosition });
|
|
||||||
}
|
|
||||||
|
|
||||||
warningShownAt = new Date();
|
|
||||||
warningIsVisible = true;
|
|
||||||
warningText = "Level of detail has been increased. \n"
|
|
||||||
+ "You can now see: \n"
|
|
||||||
+ LODManager.getLODFeedbackText();
|
|
||||||
|
|
||||||
Overlays.editOverlay(warningOverlay, { visible: warningIsVisible, text: warningText });
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
LODManager.LODDecreased.connect(function() {
|
|
||||||
// if the warning wasn't visible, then move it before showing it.
|
|
||||||
if (!warningIsVisible) {
|
|
||||||
billboardPosition = Vec3.sum(Camera.getPosition(),
|
|
||||||
Vec3.multiply(DISTANCE_FROM_CAMERA, Quat.getFront(Camera.getOrientation())));
|
|
||||||
Overlays.editOverlay(warningOverlay, { position: billboardPosition });
|
|
||||||
}
|
|
||||||
|
|
||||||
warningShownAt = new Date();
|
|
||||||
warningIsVisible = true;
|
|
||||||
warningText = "\n"
|
|
||||||
+ "Due to the complexity of the content, the \n"
|
|
||||||
+ "level of detail has been decreased. \n"
|
|
||||||
+ "You can now see: \n"
|
|
||||||
+ LODManager.getLODFeedbackText();
|
|
||||||
|
|
||||||
Overlays.editOverlay(warningOverlay, { visible: warningIsVisible, text: warningText });
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
Script.scriptEnding.connect(function() {
|
|
||||||
Overlays.deleteOverlay(warningOverlay);
|
|
||||||
});
|
|
2
interface/external/sixense/readme.txt
vendored
2
interface/external/sixense/readme.txt
vendored
|
@ -2,7 +2,7 @@
|
||||||
Instructions for adding the Sixense driver to Interface
|
Instructions for adding the Sixense driver to Interface
|
||||||
Andrzej Kapolka, November 18, 2013
|
Andrzej Kapolka, November 18, 2013
|
||||||
|
|
||||||
1. Copy the Sixense sdk folders (lib, include) into the interface/external/Sixense folder. This readme.txt should be there as well.
|
1. Copy the Sixense sdk folders (bin, include, lib, and samples) into the interface/external/Sixense folder. This readme.txt should be there as well.
|
||||||
|
|
||||||
You may optionally choose to copy the SDK folders to a location outside the repository (so you can re-use with different checkouts and different projects).
|
You may optionally choose to copy the SDK folders to a location outside the repository (so you can re-use with different checkouts and different projects).
|
||||||
If so our CMake find module expects you to set the ENV variable 'HIFI_LIB_DIR' to a directory containing a subfolder 'sixense' that contains the folders mentioned above.
|
If so our CMake find module expects you to set the ENV variable 'HIFI_LIB_DIR' to a directory containing a subfolder 'sixense' that contains the folders mentioned above.
|
||||||
|
|
2058
interface/resources/html/edit-commands.html
Normal file
2058
interface/resources/html/edit-commands.html
Normal file
File diff suppressed because it is too large
Load diff
After Width: | Height: | Size: 197 KiB |
File diff suppressed because it is too large
Load diff
Before Width: | Height: | Size: 193 KiB |
|
@ -383,6 +383,10 @@ Application::Application(int& argc, char** argv, QElapsedTimer &startup_time) :
|
||||||
connect(locationUpdateTimer, &QTimer::timeout, discoverabilityManager.data(), &DiscoverabilityManager::updateLocation);
|
connect(locationUpdateTimer, &QTimer::timeout, discoverabilityManager.data(), &DiscoverabilityManager::updateLocation);
|
||||||
locationUpdateTimer->start(DATA_SERVER_LOCATION_CHANGE_UPDATE_MSECS);
|
locationUpdateTimer->start(DATA_SERVER_LOCATION_CHANGE_UPDATE_MSECS);
|
||||||
|
|
||||||
|
// if we get a domain change, immediately attempt update location in metaverse server
|
||||||
|
connect(&nodeList->getDomainHandler(), &DomainHandler::connectedToDomain,
|
||||||
|
discoverabilityManager.data(), &DiscoverabilityManager::updateLocation);
|
||||||
|
|
||||||
connect(nodeList.data(), &NodeList::nodeAdded, this, &Application::nodeAdded);
|
connect(nodeList.data(), &NodeList::nodeAdded, this, &Application::nodeAdded);
|
||||||
connect(nodeList.data(), &NodeList::nodeKilled, this, &Application::nodeKilled);
|
connect(nodeList.data(), &NodeList::nodeKilled, this, &Application::nodeKilled);
|
||||||
connect(nodeList.data(), SIGNAL(nodeKilled(SharedNodePointer)), SLOT(nodeKilled(SharedNodePointer)));
|
connect(nodeList.data(), SIGNAL(nodeKilled(SharedNodePointer)), SLOT(nodeKilled(SharedNodePointer)));
|
||||||
|
@ -1786,6 +1790,7 @@ bool Application::exportEntities(const QString& filename, float x, float y, floa
|
||||||
void Application::loadSettings() {
|
void Application::loadSettings() {
|
||||||
|
|
||||||
DependencyManager::get<AudioClient>()->loadSettings();
|
DependencyManager::get<AudioClient>()->loadSettings();
|
||||||
|
DependencyManager::get<LODManager>()->loadSettings();
|
||||||
|
|
||||||
Menu::getInstance()->loadSettings();
|
Menu::getInstance()->loadSettings();
|
||||||
_myAvatar->loadData();
|
_myAvatar->loadData();
|
||||||
|
@ -1793,6 +1798,7 @@ void Application::loadSettings() {
|
||||||
|
|
||||||
void Application::saveSettings() {
|
void Application::saveSettings() {
|
||||||
DependencyManager::get<AudioClient>()->saveSettings();
|
DependencyManager::get<AudioClient>()->saveSettings();
|
||||||
|
DependencyManager::get<LODManager>()->saveSettings();
|
||||||
|
|
||||||
Menu::getInstance()->saveSettings();
|
Menu::getInstance()->saveSettings();
|
||||||
_myAvatar->saveData();
|
_myAvatar->saveData();
|
||||||
|
@ -1969,7 +1975,7 @@ bool Application::isLookingAtMyAvatar(Avatar* avatar) {
|
||||||
void Application::updateLOD() {
|
void Application::updateLOD() {
|
||||||
PerformanceTimer perfTimer("LOD");
|
PerformanceTimer perfTimer("LOD");
|
||||||
// adjust it unless we were asked to disable this feature, or if we're currently in throttleRendering mode
|
// adjust it unless we were asked to disable this feature, or if we're currently in throttleRendering mode
|
||||||
if (!Menu::getInstance()->isOptionChecked(MenuOption::DisableAutoAdjustLOD) && !isThrottleRendering()) {
|
if (!isThrottleRendering()) {
|
||||||
DependencyManager::get<LODManager>()->autoAdjustLOD(_fps);
|
DependencyManager::get<LODManager>()->autoAdjustLOD(_fps);
|
||||||
} else {
|
} else {
|
||||||
DependencyManager::get<LODManager>()->resetLODAdjust();
|
DependencyManager::get<LODManager>()->resetLODAdjust();
|
||||||
|
@ -3295,11 +3301,6 @@ void Application::connectedToDomain(const QString& hostname) {
|
||||||
const QUuid& domainID = DependencyManager::get<NodeList>()->getDomainHandler().getUUID();
|
const QUuid& domainID = DependencyManager::get<NodeList>()->getDomainHandler().getUUID();
|
||||||
|
|
||||||
if (accountManager.isLoggedIn() && !domainID.isNull()) {
|
if (accountManager.isLoggedIn() && !domainID.isNull()) {
|
||||||
// update our data-server with the domain-server we're logged in with
|
|
||||||
QString domainPutJsonString = "{\"location\":{\"domain_id\":\"" + uuidStringWithoutCurlyBraces(domainID) + "\"}}";
|
|
||||||
accountManager.authenticatedRequest("/api/v1/user/location", QNetworkAccessManager::PutOperation,
|
|
||||||
JSONCallbackParameters(), domainPutJsonString.toUtf8());
|
|
||||||
|
|
||||||
_notifiedPacketVersionMismatchThisDomain = false;
|
_notifiedPacketVersionMismatchThisDomain = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -113,7 +113,7 @@ static const float MIRROR_FIELD_OF_VIEW = 30.0f;
|
||||||
static const quint64 TOO_LONG_SINCE_LAST_SEND_DOWNSTREAM_AUDIO_STATS = 1 * USECS_PER_SECOND;
|
static const quint64 TOO_LONG_SINCE_LAST_SEND_DOWNSTREAM_AUDIO_STATS = 1 * USECS_PER_SECOND;
|
||||||
|
|
||||||
static const QString INFO_HELP_PATH = "html/interface-welcome.html";
|
static const QString INFO_HELP_PATH = "html/interface-welcome.html";
|
||||||
static const QString INFO_EDIT_ENTITIES_PATH = "html/edit-entities-commands.html";
|
static const QString INFO_EDIT_ENTITIES_PATH = "html/edit-commands.html";
|
||||||
|
|
||||||
#ifdef Q_OS_WIN
|
#ifdef Q_OS_WIN
|
||||||
static const UINT UWM_IDENTIFY_INSTANCES =
|
static const UINT UWM_IDENTIFY_INSTANCES =
|
||||||
|
|
|
@ -17,13 +17,26 @@
|
||||||
|
|
||||||
#include "LODManager.h"
|
#include "LODManager.h"
|
||||||
|
|
||||||
Setting::Handle<bool> automaticAvatarLOD("automaticAvatarLOD", true);
|
Setting::Handle<float> desktopLODDecreaseFPS("desktopLODDecreaseFPS", DEFAULT_DESKTOP_LOD_DOWN_FPS);
|
||||||
Setting::Handle<float> avatarLODDecreaseFPS("avatarLODDecreaseFPS", DEFAULT_ADJUST_AVATAR_LOD_DOWN_FPS);
|
Setting::Handle<float> hmdLODDecreaseFPS("hmdLODDecreaseFPS", DEFAULT_HMD_LOD_DOWN_FPS);
|
||||||
Setting::Handle<float> avatarLODIncreaseFPS("avatarLODIncreaseFPS", ADJUST_LOD_UP_FPS);
|
|
||||||
Setting::Handle<float> avatarLODDistanceMultiplier("avatarLODDistanceMultiplier",
|
LODManager::LODManager() {
|
||||||
DEFAULT_AVATAR_LOD_DISTANCE_MULTIPLIER);
|
calculateAvatarLODDistanceMultiplier();
|
||||||
Setting::Handle<int> boundaryLevelAdjust("boundaryLevelAdjust", 0);
|
}
|
||||||
Setting::Handle<float> octreeSizeScale("octreeSizeScale", DEFAULT_OCTREE_SIZE_SCALE);
|
|
||||||
|
float LODManager::getLODDecreaseFPS() {
|
||||||
|
if (Application::getInstance()->isHMDMode()) {
|
||||||
|
return getHMDLODDecreaseFPS();
|
||||||
|
}
|
||||||
|
return getDesktopLODDecreaseFPS();
|
||||||
|
}
|
||||||
|
|
||||||
|
float LODManager::getLODIncreaseFPS() {
|
||||||
|
if (Application::getInstance()->isHMDMode()) {
|
||||||
|
return getHMDLODIncreaseFPS();
|
||||||
|
}
|
||||||
|
return getDesktopLODIncreaseFPS();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
void LODManager::autoAdjustLOD(float currentFPS) {
|
void LODManager::autoAdjustLOD(float currentFPS) {
|
||||||
|
@ -40,65 +53,64 @@ void LODManager::autoAdjustLOD(float currentFPS) {
|
||||||
|
|
||||||
quint64 now = usecTimestampNow();
|
quint64 now = usecTimestampNow();
|
||||||
|
|
||||||
const quint64 ADJUST_AVATAR_LOD_DOWN_DELAY = 1000 * 1000;
|
|
||||||
if (_automaticAvatarLOD) {
|
|
||||||
if (_fastFPSAverage.getAverage() < _avatarLODDecreaseFPS) {
|
|
||||||
if (now - _lastAvatarDetailDrop > ADJUST_AVATAR_LOD_DOWN_DELAY) {
|
|
||||||
// attempt to lower the detail in proportion to the fps difference
|
|
||||||
float targetFps = (_avatarLODDecreaseFPS + _avatarLODIncreaseFPS) * 0.5f;
|
|
||||||
float averageFps = _fastFPSAverage.getAverage();
|
|
||||||
const float MAXIMUM_MULTIPLIER_SCALE = 2.0f;
|
|
||||||
_avatarLODDistanceMultiplier = qMin(MAXIMUM_AVATAR_LOD_DISTANCE_MULTIPLIER, _avatarLODDistanceMultiplier *
|
|
||||||
(averageFps < EPSILON ? MAXIMUM_MULTIPLIER_SCALE :
|
|
||||||
qMin(MAXIMUM_MULTIPLIER_SCALE, targetFps / averageFps)));
|
|
||||||
_lastAvatarDetailDrop = now;
|
|
||||||
}
|
|
||||||
} else if (_fastFPSAverage.getAverage() > _avatarLODIncreaseFPS) {
|
|
||||||
// let the detail level creep slowly upwards
|
|
||||||
const float DISTANCE_DECREASE_RATE = 0.05f;
|
|
||||||
_avatarLODDistanceMultiplier = qMax(MINIMUM_AVATAR_LOD_DISTANCE_MULTIPLIER,
|
|
||||||
_avatarLODDistanceMultiplier - DISTANCE_DECREASE_RATE);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
bool changed = false;
|
bool changed = false;
|
||||||
|
bool octreeChanged = false;
|
||||||
quint64 elapsed = now - _lastAdjust;
|
quint64 elapsed = now - _lastAdjust;
|
||||||
|
|
||||||
if (elapsed > ADJUST_LOD_DOWN_DELAY && _fpsAverage.getAverage() < ADJUST_LOD_DOWN_FPS
|
if (_automaticLODAdjust) {
|
||||||
&& _octreeSizeScale > ADJUST_LOD_MIN_SIZE_SCALE) {
|
// LOD Downward adjustment
|
||||||
|
if (elapsed > ADJUST_LOD_DOWN_DELAY && _fpsAverage.getAverage() < getLODDecreaseFPS()) {
|
||||||
|
|
||||||
_octreeSizeScale *= ADJUST_LOD_DOWN_BY;
|
// Octree items... stepwise adjustment
|
||||||
|
if (_octreeSizeScale > ADJUST_LOD_MIN_SIZE_SCALE) {
|
||||||
|
_octreeSizeScale *= ADJUST_LOD_DOWN_BY;
|
||||||
|
if (_octreeSizeScale < ADJUST_LOD_MIN_SIZE_SCALE) {
|
||||||
|
_octreeSizeScale = ADJUST_LOD_MIN_SIZE_SCALE;
|
||||||
|
}
|
||||||
|
octreeChanged = changed = true;
|
||||||
|
}
|
||||||
|
|
||||||
if (_octreeSizeScale < ADJUST_LOD_MIN_SIZE_SCALE) {
|
if (changed) {
|
||||||
_octreeSizeScale = ADJUST_LOD_MIN_SIZE_SCALE;
|
_lastAdjust = now;
|
||||||
|
qDebug() << "adjusting LOD down... average fps for last approximately 5 seconds=" << _fpsAverage.getAverage()
|
||||||
|
<< "_octreeSizeScale=" << _octreeSizeScale;
|
||||||
|
|
||||||
|
emit LODDecreased();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
changed = true;
|
|
||||||
_lastAdjust = now;
|
|
||||||
qDebug() << "adjusting LOD down... average fps for last approximately 5 seconds=" << _fpsAverage.getAverage()
|
|
||||||
<< "_octreeSizeScale=" << _octreeSizeScale;
|
|
||||||
|
|
||||||
emit LODDecreased();
|
// LOD Upward adjustment
|
||||||
}
|
if (elapsed > ADJUST_LOD_UP_DELAY && _fpsAverage.getAverage() > getLODIncreaseFPS()) {
|
||||||
|
|
||||||
if (elapsed > ADJUST_LOD_UP_DELAY && _fpsAverage.getAverage() > ADJUST_LOD_UP_FPS
|
// Octee items... stepwise adjustment
|
||||||
&& _octreeSizeScale < ADJUST_LOD_MAX_SIZE_SCALE) {
|
if (_octreeSizeScale < ADJUST_LOD_MAX_SIZE_SCALE) {
|
||||||
_octreeSizeScale *= ADJUST_LOD_UP_BY;
|
if (_octreeSizeScale < ADJUST_LOD_MIN_SIZE_SCALE) {
|
||||||
if (_octreeSizeScale > ADJUST_LOD_MAX_SIZE_SCALE) {
|
_octreeSizeScale = ADJUST_LOD_MIN_SIZE_SCALE;
|
||||||
_octreeSizeScale = ADJUST_LOD_MAX_SIZE_SCALE;
|
} else {
|
||||||
|
_octreeSizeScale *= ADJUST_LOD_UP_BY;
|
||||||
|
}
|
||||||
|
if (_octreeSizeScale > ADJUST_LOD_MAX_SIZE_SCALE) {
|
||||||
|
_octreeSizeScale = ADJUST_LOD_MAX_SIZE_SCALE;
|
||||||
|
}
|
||||||
|
octreeChanged = changed = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (changed) {
|
||||||
|
_lastAdjust = now;
|
||||||
|
qDebug() << "adjusting LOD up... average fps for last approximately 5 seconds=" << _fpsAverage.getAverage()
|
||||||
|
<< "_octreeSizeScale=" << _octreeSizeScale;
|
||||||
|
|
||||||
|
emit LODIncreased();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
changed = true;
|
|
||||||
_lastAdjust = now;
|
|
||||||
qDebug() << "adjusting LOD up... average fps for last approximately 5 seconds=" << _fpsAverage.getAverage()
|
|
||||||
<< "_octreeSizeScale=" << _octreeSizeScale;
|
|
||||||
|
|
||||||
emit LODIncreased();
|
if (changed) {
|
||||||
}
|
calculateAvatarLODDistanceMultiplier();
|
||||||
|
_shouldRenderTableNeedsRebuilding = true;
|
||||||
if (changed) {
|
auto lodToolsDialog = DependencyManager::get<DialogsManager>()->getLodToolsDialog();
|
||||||
_shouldRenderTableNeedsRebuilding = true;
|
if (lodToolsDialog) {
|
||||||
auto lodToolsDialog = DependencyManager::get<DialogsManager>()->getLodToolsDialog();
|
lodToolsDialog->reloadSliders();
|
||||||
if (lodToolsDialog) {
|
}
|
||||||
lodToolsDialog->reloadSliders();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -106,7 +118,7 @@ void LODManager::autoAdjustLOD(float currentFPS) {
|
||||||
void LODManager::resetLODAdjust() {
|
void LODManager::resetLODAdjust() {
|
||||||
_fpsAverage.reset();
|
_fpsAverage.reset();
|
||||||
_fastFPSAverage.reset();
|
_fastFPSAverage.reset();
|
||||||
_lastAvatarDetailDrop = _lastAdjust = usecTimestampNow();
|
_lastAdjust = usecTimestampNow();
|
||||||
}
|
}
|
||||||
|
|
||||||
QString LODManager::getLODFeedbackText() {
|
QString LODManager::getLODFeedbackText() {
|
||||||
|
@ -116,29 +128,33 @@ QString LODManager::getLODFeedbackText() {
|
||||||
|
|
||||||
switch (boundaryLevelAdjust) {
|
switch (boundaryLevelAdjust) {
|
||||||
case 0: {
|
case 0: {
|
||||||
granularityFeedback = QString("at standard granularity.");
|
granularityFeedback = QString(".");
|
||||||
} break;
|
} break;
|
||||||
case 1: {
|
case 1: {
|
||||||
granularityFeedback = QString("at half of standard granularity.");
|
granularityFeedback = QString(" at half of standard granularity.");
|
||||||
} break;
|
} break;
|
||||||
case 2: {
|
case 2: {
|
||||||
granularityFeedback = QString("at a third of standard granularity.");
|
granularityFeedback = QString(" at a third of standard granularity.");
|
||||||
} break;
|
} break;
|
||||||
default: {
|
default: {
|
||||||
granularityFeedback = QString("at 1/%1th of standard granularity.").arg(boundaryLevelAdjust + 1);
|
granularityFeedback = QString(" at 1/%1th of standard granularity.").arg(boundaryLevelAdjust + 1);
|
||||||
} break;
|
} break;
|
||||||
}
|
}
|
||||||
|
|
||||||
// distance feedback
|
// distance feedback
|
||||||
float octreeSizeScale = getOctreeSizeScale();
|
float octreeSizeScale = getOctreeSizeScale();
|
||||||
float relativeToDefault = octreeSizeScale / DEFAULT_OCTREE_SIZE_SCALE;
|
float relativeToDefault = octreeSizeScale / DEFAULT_OCTREE_SIZE_SCALE;
|
||||||
|
int relativeToTwentyTwenty = 20 / relativeToDefault;
|
||||||
|
|
||||||
QString result;
|
QString result;
|
||||||
if (relativeToDefault > 1.01) {
|
if (relativeToDefault > 1.01) {
|
||||||
result = QString("%1 further %2").arg(relativeToDefault,8,'f',2).arg(granularityFeedback);
|
result = QString("20:%1 or %2 times further than average vision%3").arg(relativeToTwentyTwenty).arg(relativeToDefault,0,'f',2).arg(granularityFeedback);
|
||||||
} else if (relativeToDefault > 0.99) {
|
} else if (relativeToDefault > 0.99) {
|
||||||
result = QString("the default distance %1").arg(granularityFeedback);
|
result = QString("20:20 or the default distance for average vision%1").arg(granularityFeedback);
|
||||||
|
} else if (relativeToDefault > 0.01) {
|
||||||
|
result = QString("20:%1 or %2 of default distance for average vision%3").arg(relativeToTwentyTwenty).arg(relativeToDefault,0,'f',3).arg(granularityFeedback);
|
||||||
} else {
|
} else {
|
||||||
result = QString("%1 of default %2").arg(relativeToDefault,8,'f',3).arg(granularityFeedback);
|
result = QString("%2 of default distance for average vision%3").arg(relativeToDefault,0,'f',3).arg(granularityFeedback);
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
@ -184,9 +200,14 @@ bool LODManager::shouldRenderMesh(float largestDimension, float distanceToCamera
|
||||||
|
|
||||||
void LODManager::setOctreeSizeScale(float sizeScale) {
|
void LODManager::setOctreeSizeScale(float sizeScale) {
|
||||||
_octreeSizeScale = sizeScale;
|
_octreeSizeScale = sizeScale;
|
||||||
|
calculateAvatarLODDistanceMultiplier();
|
||||||
_shouldRenderTableNeedsRebuilding = true;
|
_shouldRenderTableNeedsRebuilding = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void LODManager::calculateAvatarLODDistanceMultiplier() {
|
||||||
|
_avatarLODDistanceMultiplier = AVATAR_TO_ENTITY_RATIO / (_octreeSizeScale / DEFAULT_OCTREE_SIZE_SCALE);
|
||||||
|
}
|
||||||
|
|
||||||
void LODManager::setBoundaryLevelAdjust(int boundaryLevelAdjust) {
|
void LODManager::setBoundaryLevelAdjust(int boundaryLevelAdjust) {
|
||||||
_boundaryLevelAdjust = boundaryLevelAdjust;
|
_boundaryLevelAdjust = boundaryLevelAdjust;
|
||||||
_shouldRenderTableNeedsRebuilding = true;
|
_shouldRenderTableNeedsRebuilding = true;
|
||||||
|
@ -194,21 +215,13 @@ void LODManager::setBoundaryLevelAdjust(int boundaryLevelAdjust) {
|
||||||
|
|
||||||
|
|
||||||
void LODManager::loadSettings() {
|
void LODManager::loadSettings() {
|
||||||
setAutomaticAvatarLOD(automaticAvatarLOD.get());
|
setDesktopLODDecreaseFPS(desktopLODDecreaseFPS.get());
|
||||||
setAvatarLODDecreaseFPS(avatarLODDecreaseFPS.get());
|
setHMDLODDecreaseFPS(hmdLODDecreaseFPS.get());
|
||||||
setAvatarLODIncreaseFPS(avatarLODIncreaseFPS.get());
|
|
||||||
setAvatarLODDistanceMultiplier(avatarLODDistanceMultiplier.get());
|
|
||||||
setBoundaryLevelAdjust(boundaryLevelAdjust.get());
|
|
||||||
setOctreeSizeScale(octreeSizeScale.get());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void LODManager::saveSettings() {
|
void LODManager::saveSettings() {
|
||||||
automaticAvatarLOD.set(getAutomaticAvatarLOD());
|
desktopLODDecreaseFPS.set(getDesktopLODDecreaseFPS());
|
||||||
avatarLODDecreaseFPS.set(getAvatarLODDecreaseFPS());
|
hmdLODDecreaseFPS.set(getHMDLODDecreaseFPS());
|
||||||
avatarLODIncreaseFPS.set(getAvatarLODIncreaseFPS());
|
|
||||||
avatarLODDistanceMultiplier.set(getAvatarLODDistanceMultiplier());
|
|
||||||
boundaryLevelAdjust.set(getBoundaryLevelAdjust());
|
|
||||||
octreeSizeScale.set(getOctreeSizeScale());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -17,9 +17,9 @@
|
||||||
#include <SharedUtil.h>
|
#include <SharedUtil.h>
|
||||||
#include <SimpleMovingAverage.h>
|
#include <SimpleMovingAverage.h>
|
||||||
|
|
||||||
const float ADJUST_LOD_DOWN_FPS = 40.0;
|
const float DEFAULT_DESKTOP_LOD_DOWN_FPS = 30.0;
|
||||||
const float ADJUST_LOD_UP_FPS = 55.0;
|
const float DEFAULT_HMD_LOD_DOWN_FPS = 60.0;
|
||||||
const float DEFAULT_ADJUST_AVATAR_LOD_DOWN_FPS = 30.0f;
|
const float INCREASE_LOD_GAP = 5.0f;
|
||||||
|
|
||||||
const quint64 ADJUST_LOD_DOWN_DELAY = 1000 * 1000 * 0.5; // Consider adjusting LOD down after half a second
|
const quint64 ADJUST_LOD_DOWN_DELAY = 1000 * 1000 * 0.5; // Consider adjusting LOD down after half a second
|
||||||
const quint64 ADJUST_LOD_UP_DELAY = ADJUST_LOD_DOWN_DELAY * 2;
|
const quint64 ADJUST_LOD_UP_DELAY = ADJUST_LOD_DOWN_DELAY * 2;
|
||||||
|
@ -33,9 +33,9 @@ const float ADJUST_LOD_UP_BY = 1.1f;
|
||||||
const float ADJUST_LOD_MIN_SIZE_SCALE = 1.0f;
|
const float ADJUST_LOD_MIN_SIZE_SCALE = 1.0f;
|
||||||
const float ADJUST_LOD_MAX_SIZE_SCALE = DEFAULT_OCTREE_SIZE_SCALE;
|
const float ADJUST_LOD_MAX_SIZE_SCALE = DEFAULT_OCTREE_SIZE_SCALE;
|
||||||
|
|
||||||
const float MINIMUM_AVATAR_LOD_DISTANCE_MULTIPLIER = 0.1f;
|
// The ratio of "visibility" of avatars to other content. A value larger than 1 will mean Avatars "cull" later than entities
|
||||||
const float MAXIMUM_AVATAR_LOD_DISTANCE_MULTIPLIER = 15.0f;
|
// do. But both are still culled using the same angular size logic.
|
||||||
const float DEFAULT_AVATAR_LOD_DISTANCE_MULTIPLIER = 1.0f;
|
const float AVATAR_TO_ENTITY_RATIO = 2.0f;
|
||||||
|
|
||||||
const int ONE_SECOND_OF_FRAMES = 60;
|
const int ONE_SECOND_OF_FRAMES = 60;
|
||||||
const int FIVE_SECONDS_OF_FRAMES = 5 * ONE_SECOND_OF_FRAMES;
|
const int FIVE_SECONDS_OF_FRAMES = 5 * ONE_SECOND_OF_FRAMES;
|
||||||
|
@ -46,14 +46,18 @@ class LODManager : public QObject, public Dependency {
|
||||||
SINGLETON_DEPENDENCY
|
SINGLETON_DEPENDENCY
|
||||||
|
|
||||||
public:
|
public:
|
||||||
void setAutomaticAvatarLOD(bool automaticAvatarLOD) { _automaticAvatarLOD = automaticAvatarLOD; }
|
Q_INVOKABLE void setAutomaticLODAdjust(bool value) { _automaticLODAdjust = value; }
|
||||||
bool getAutomaticAvatarLOD() const { return _automaticAvatarLOD; }
|
Q_INVOKABLE bool getAutomaticLODAdjust() const { return _automaticLODAdjust; }
|
||||||
void setAvatarLODDecreaseFPS(float avatarLODDecreaseFPS) { _avatarLODDecreaseFPS = avatarLODDecreaseFPS; }
|
|
||||||
float getAvatarLODDecreaseFPS() const { return _avatarLODDecreaseFPS; }
|
Q_INVOKABLE void setDesktopLODDecreaseFPS(float value) { _desktopLODDecreaseFPS = value; }
|
||||||
void setAvatarLODIncreaseFPS(float avatarLODIncreaseFPS) { _avatarLODIncreaseFPS = avatarLODIncreaseFPS; }
|
Q_INVOKABLE float getDesktopLODDecreaseFPS() const { return _desktopLODDecreaseFPS; }
|
||||||
float getAvatarLODIncreaseFPS() const { return _avatarLODIncreaseFPS; }
|
Q_INVOKABLE float getDesktopLODIncreaseFPS() const { return _desktopLODDecreaseFPS + INCREASE_LOD_GAP; }
|
||||||
void setAvatarLODDistanceMultiplier(float multiplier) { _avatarLODDistanceMultiplier = multiplier; }
|
|
||||||
float getAvatarLODDistanceMultiplier() const { return _avatarLODDistanceMultiplier; }
|
Q_INVOKABLE void setHMDLODDecreaseFPS(float value) { _hmdLODDecreaseFPS = value; }
|
||||||
|
Q_INVOKABLE float getHMDLODDecreaseFPS() const { return _hmdLODDecreaseFPS; }
|
||||||
|
Q_INVOKABLE float getHMDLODIncreaseFPS() const { return _hmdLODDecreaseFPS + INCREASE_LOD_GAP; }
|
||||||
|
|
||||||
|
Q_INVOKABLE float getAvatarLODDistanceMultiplier() const { return _avatarLODDistanceMultiplier; }
|
||||||
|
|
||||||
// User Tweakable LOD Items
|
// User Tweakable LOD Items
|
||||||
Q_INVOKABLE QString getLODFeedbackText();
|
Q_INVOKABLE QString getLODFeedbackText();
|
||||||
|
@ -63,12 +67,15 @@ public:
|
||||||
Q_INVOKABLE void setBoundaryLevelAdjust(int boundaryLevelAdjust);
|
Q_INVOKABLE void setBoundaryLevelAdjust(int boundaryLevelAdjust);
|
||||||
Q_INVOKABLE int getBoundaryLevelAdjust() const { return _boundaryLevelAdjust; }
|
Q_INVOKABLE int getBoundaryLevelAdjust() const { return _boundaryLevelAdjust; }
|
||||||
|
|
||||||
void autoAdjustLOD(float currentFPS);
|
|
||||||
Q_INVOKABLE void resetLODAdjust();
|
Q_INVOKABLE void resetLODAdjust();
|
||||||
Q_INVOKABLE float getFPSAverage() const { return _fpsAverage.getAverage(); }
|
Q_INVOKABLE float getFPSAverage() const { return _fpsAverage.getAverage(); }
|
||||||
Q_INVOKABLE float getFastFPSAverage() const { return _fastFPSAverage.getAverage(); }
|
Q_INVOKABLE float getFastFPSAverage() const { return _fastFPSAverage.getAverage(); }
|
||||||
|
|
||||||
|
Q_INVOKABLE float getLODDecreaseFPS();
|
||||||
|
Q_INVOKABLE float getLODIncreaseFPS();
|
||||||
|
|
||||||
bool shouldRenderMesh(float largestDimension, float distanceToCamera);
|
bool shouldRenderMesh(float largestDimension, float distanceToCamera);
|
||||||
|
void autoAdjustLOD(float currentFPS);
|
||||||
|
|
||||||
void loadSettings();
|
void loadSettings();
|
||||||
void saveSettings();
|
void saveSettings();
|
||||||
|
@ -78,18 +85,18 @@ signals:
|
||||||
void LODDecreased();
|
void LODDecreased();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
LODManager() {}
|
LODManager();
|
||||||
|
void calculateAvatarLODDistanceMultiplier();
|
||||||
|
|
||||||
bool _automaticAvatarLOD = true;
|
bool _automaticLODAdjust = true;
|
||||||
float _avatarLODDecreaseFPS = DEFAULT_ADJUST_AVATAR_LOD_DOWN_FPS;
|
float _desktopLODDecreaseFPS = DEFAULT_DESKTOP_LOD_DOWN_FPS;
|
||||||
float _avatarLODIncreaseFPS = ADJUST_LOD_UP_FPS;
|
float _hmdLODDecreaseFPS = DEFAULT_HMD_LOD_DOWN_FPS;
|
||||||
float _avatarLODDistanceMultiplier = DEFAULT_AVATAR_LOD_DISTANCE_MULTIPLIER;
|
|
||||||
|
|
||||||
|
float _avatarLODDistanceMultiplier;
|
||||||
float _octreeSizeScale = DEFAULT_OCTREE_SIZE_SCALE;
|
float _octreeSizeScale = DEFAULT_OCTREE_SIZE_SCALE;
|
||||||
int _boundaryLevelAdjust = 0;
|
int _boundaryLevelAdjust = 0;
|
||||||
|
|
||||||
quint64 _lastAdjust = 0;
|
quint64 _lastAdjust = 0;
|
||||||
quint64 _lastAvatarDetailDrop = 0;
|
|
||||||
SimpleMovingAverage _fpsAverage = FIVE_SECONDS_OF_FRAMES;
|
SimpleMovingAverage _fpsAverage = FIVE_SECONDS_OF_FRAMES;
|
||||||
SimpleMovingAverage _fastFPSAverage = ONE_SECOND_OF_FRAMES;
|
SimpleMovingAverage _fastFPSAverage = ONE_SECOND_OF_FRAMES;
|
||||||
|
|
||||||
|
|
|
@ -276,7 +276,6 @@ Menu::Menu() {
|
||||||
addCheckableActionToQMenuAndActionHash(renderOptionsMenu, MenuOption::Entities, 0, true);
|
addCheckableActionToQMenuAndActionHash(renderOptionsMenu, MenuOption::Entities, 0, true);
|
||||||
addCheckableActionToQMenuAndActionHash(renderOptionsMenu, MenuOption::AmbientOcclusion);
|
addCheckableActionToQMenuAndActionHash(renderOptionsMenu, MenuOption::AmbientOcclusion);
|
||||||
addCheckableActionToQMenuAndActionHash(renderOptionsMenu, MenuOption::DontFadeOnOctreeServerChanges);
|
addCheckableActionToQMenuAndActionHash(renderOptionsMenu, MenuOption::DontFadeOnOctreeServerChanges);
|
||||||
addCheckableActionToQMenuAndActionHash(renderOptionsMenu, MenuOption::DisableAutoAdjustLOD);
|
|
||||||
|
|
||||||
QMenu* ambientLightMenu = renderOptionsMenu->addMenu(MenuOption::RenderAmbientLight);
|
QMenu* ambientLightMenu = renderOptionsMenu->addMenu(MenuOption::RenderAmbientLight);
|
||||||
QActionGroup* ambientLightGroup = new QActionGroup(ambientLightMenu);
|
QActionGroup* ambientLightGroup = new QActionGroup(ambientLightMenu);
|
||||||
|
|
|
@ -136,7 +136,6 @@ namespace MenuOption {
|
||||||
const QString DecreaseAvatarSize = "Decrease Avatar Size";
|
const QString DecreaseAvatarSize = "Decrease Avatar Size";
|
||||||
const QString DeleteBookmark = "Delete Bookmark...";
|
const QString DeleteBookmark = "Delete Bookmark...";
|
||||||
const QString DisableActivityLogger = "Disable Activity Logger";
|
const QString DisableActivityLogger = "Disable Activity Logger";
|
||||||
const QString DisableAutoAdjustLOD = "Disable Automatically Adjusting LOD";
|
|
||||||
const QString DisableLightEntities = "Disable Light Entities";
|
const QString DisableLightEntities = "Disable Light Entities";
|
||||||
const QString DisableNackPackets = "Disable NACK Packets";
|
const QString DisableNackPackets = "Disable NACK Packets";
|
||||||
const QString DiskCacheEditor = "Disk Cache Editor";
|
const QString DiskCacheEditor = "Disk Cache Editor";
|
||||||
|
|
|
@ -87,7 +87,8 @@ void FaceModel::maybeUpdateEyeRotation(Model* model, const JointState& parentSta
|
||||||
void FaceModel::updateJointState(int index) {
|
void FaceModel::updateJointState(int index) {
|
||||||
JointState& state = _jointStates[index];
|
JointState& state = _jointStates[index];
|
||||||
const FBXJoint& joint = state.getFBXJoint();
|
const FBXJoint& joint = state.getFBXJoint();
|
||||||
if (joint.parentIndex != -1) {
|
// guard against out-of-bounds access to _jointStates
|
||||||
|
if (joint.parentIndex != -1 && joint.parentIndex >= 0 && joint.parentIndex < _jointStates.size()) {
|
||||||
const JointState& parentState = _jointStates.at(joint.parentIndex);
|
const JointState& parentState = _jointStates.at(joint.parentIndex);
|
||||||
const FBXGeometry& geometry = _geometry->getFBXGeometry();
|
const FBXGeometry& geometry = _geometry->getFBXGeometry();
|
||||||
if (index == geometry.neckJointIndex) {
|
if (index == geometry.neckJointIndex) {
|
||||||
|
|
|
@ -35,9 +35,24 @@ LodToolsDialog::LodToolsDialog(QWidget* parent) :
|
||||||
// Create layouter
|
// Create layouter
|
||||||
QFormLayout* form = new QFormLayout(this);
|
QFormLayout* form = new QFormLayout(this);
|
||||||
|
|
||||||
|
// Create a label with feedback...
|
||||||
|
_feedback = new QLabel(this);
|
||||||
|
QPalette palette = _feedback->palette();
|
||||||
|
const unsigned redish = 0xfff00000;
|
||||||
|
palette.setColor(QPalette::WindowText, QColor::fromRgb(redish));
|
||||||
|
_feedback->setPalette(palette);
|
||||||
|
_feedback->setText(lodManager->getLODFeedbackText());
|
||||||
|
const int FEEDBACK_WIDTH = 350;
|
||||||
|
_feedback->setFixedWidth(FEEDBACK_WIDTH);
|
||||||
|
form->addRow("You can see... ", _feedback);
|
||||||
|
|
||||||
|
form->addRow("Manually Adjust Level of Detail:", _manualLODAdjust = new QCheckBox(this));
|
||||||
|
_manualLODAdjust->setChecked(!lodManager->getAutomaticLODAdjust());
|
||||||
|
connect(_manualLODAdjust, SIGNAL(toggled(bool)), SLOT(updateAutomaticLODAdjust()));
|
||||||
|
|
||||||
_lodSize = new QSlider(Qt::Horizontal, this);
|
_lodSize = new QSlider(Qt::Horizontal, this);
|
||||||
const int MAX_LOD_SIZE = MAX_LOD_SIZE_MULTIPLIER;
|
const int MAX_LOD_SIZE = MAX_LOD_SIZE_MULTIPLIER;
|
||||||
const int MIN_LOD_SIZE = 0;
|
const int MIN_LOD_SIZE = ADJUST_LOD_MIN_SIZE_SCALE;
|
||||||
const int STEP_LOD_SIZE = 1;
|
const int STEP_LOD_SIZE = 1;
|
||||||
const int PAGE_STEP_LOD_SIZE = 100;
|
const int PAGE_STEP_LOD_SIZE = 100;
|
||||||
const int SLIDER_WIDTH = 300;
|
const int SLIDER_WIDTH = 300;
|
||||||
|
@ -50,56 +65,9 @@ LodToolsDialog::LodToolsDialog(QWidget* parent) :
|
||||||
_lodSize->setPageStep(PAGE_STEP_LOD_SIZE);
|
_lodSize->setPageStep(PAGE_STEP_LOD_SIZE);
|
||||||
int sliderValue = lodManager->getOctreeSizeScale() / TREE_SCALE;
|
int sliderValue = lodManager->getOctreeSizeScale() / TREE_SCALE;
|
||||||
_lodSize->setValue(sliderValue);
|
_lodSize->setValue(sliderValue);
|
||||||
form->addRow("LOD Size Scale:", _lodSize);
|
form->addRow("Level of Detail:", _lodSize);
|
||||||
connect(_lodSize,SIGNAL(valueChanged(int)),this,SLOT(sizeScaleValueChanged(int)));
|
connect(_lodSize,SIGNAL(valueChanged(int)),this,SLOT(sizeScaleValueChanged(int)));
|
||||||
|
|
||||||
_boundaryLevelAdjust = new QSlider(Qt::Horizontal, this);
|
|
||||||
const int MAX_ADJUST = 10;
|
|
||||||
const int MIN_ADJUST = 0;
|
|
||||||
const int STEP_ADJUST = 1;
|
|
||||||
_boundaryLevelAdjust->setMaximum(MAX_ADJUST);
|
|
||||||
_boundaryLevelAdjust->setMinimum(MIN_ADJUST);
|
|
||||||
_boundaryLevelAdjust->setSingleStep(STEP_ADJUST);
|
|
||||||
_boundaryLevelAdjust->setTickInterval(STEP_ADJUST);
|
|
||||||
_boundaryLevelAdjust->setTickPosition(QSlider::TicksBelow);
|
|
||||||
_boundaryLevelAdjust->setFixedWidth(SLIDER_WIDTH);
|
|
||||||
sliderValue = lodManager->getBoundaryLevelAdjust();
|
|
||||||
_boundaryLevelAdjust->setValue(sliderValue);
|
|
||||||
form->addRow("Boundary Level Adjust:", _boundaryLevelAdjust);
|
|
||||||
connect(_boundaryLevelAdjust,SIGNAL(valueChanged(int)),this,SLOT(boundaryLevelValueChanged(int)));
|
|
||||||
|
|
||||||
// Create a label with feedback...
|
|
||||||
_feedback = new QLabel(this);
|
|
||||||
QPalette palette = _feedback->palette();
|
|
||||||
const unsigned redish = 0xfff00000;
|
|
||||||
palette.setColor(QPalette::WindowText, QColor::fromRgb(redish));
|
|
||||||
_feedback->setPalette(palette);
|
|
||||||
_feedback->setText(lodManager->getLODFeedbackText());
|
|
||||||
const int FEEDBACK_WIDTH = 350;
|
|
||||||
_feedback->setFixedWidth(FEEDBACK_WIDTH);
|
|
||||||
form->addRow("You can see... ", _feedback);
|
|
||||||
|
|
||||||
form->addRow("Automatic Avatar LOD Adjustment:", _automaticAvatarLOD = new QCheckBox(this));
|
|
||||||
_automaticAvatarLOD->setChecked(lodManager->getAutomaticAvatarLOD());
|
|
||||||
connect(_automaticAvatarLOD, SIGNAL(toggled(bool)), SLOT(updateAvatarLODControls()));
|
|
||||||
|
|
||||||
form->addRow("Decrease Avatar LOD Below FPS:", _avatarLODDecreaseFPS = new QDoubleSpinBox(this));
|
|
||||||
_avatarLODDecreaseFPS->setValue(lodManager->getAvatarLODDecreaseFPS());
|
|
||||||
_avatarLODDecreaseFPS->setDecimals(0);
|
|
||||||
connect(_avatarLODDecreaseFPS, SIGNAL(valueChanged(double)), SLOT(updateAvatarLODValues()));
|
|
||||||
|
|
||||||
form->addRow("Increase Avatar LOD Above FPS:", _avatarLODIncreaseFPS = new QDoubleSpinBox(this));
|
|
||||||
_avatarLODIncreaseFPS->setValue(lodManager->getAvatarLODIncreaseFPS());
|
|
||||||
_avatarLODIncreaseFPS->setDecimals(0);
|
|
||||||
connect(_avatarLODIncreaseFPS, SIGNAL(valueChanged(double)), SLOT(updateAvatarLODValues()));
|
|
||||||
|
|
||||||
form->addRow("Avatar LOD:", _avatarLOD = new QDoubleSpinBox(this));
|
|
||||||
_avatarLOD->setDecimals(3);
|
|
||||||
_avatarLOD->setRange(1.0 / MAXIMUM_AVATAR_LOD_DISTANCE_MULTIPLIER, 1.0 / MINIMUM_AVATAR_LOD_DISTANCE_MULTIPLIER);
|
|
||||||
_avatarLOD->setSingleStep(0.001);
|
|
||||||
_avatarLOD->setValue(1.0 / lodManager->getAvatarLODDistanceMultiplier());
|
|
||||||
connect(_avatarLOD, SIGNAL(valueChanged(double)), SLOT(updateAvatarLODValues()));
|
|
||||||
|
|
||||||
// Add a button to reset
|
// Add a button to reset
|
||||||
QPushButton* resetButton = new QPushButton("Reset", this);
|
QPushButton* resetButton = new QPushButton("Reset", this);
|
||||||
form->addRow("", resetButton);
|
form->addRow("", resetButton);
|
||||||
|
@ -107,49 +75,19 @@ LodToolsDialog::LodToolsDialog(QWidget* parent) :
|
||||||
|
|
||||||
this->QDialog::setLayout(form);
|
this->QDialog::setLayout(form);
|
||||||
|
|
||||||
updateAvatarLODControls();
|
updateAutomaticLODAdjust();
|
||||||
}
|
}
|
||||||
|
|
||||||
void LodToolsDialog::reloadSliders() {
|
void LodToolsDialog::reloadSliders() {
|
||||||
auto lodManager = DependencyManager::get<LODManager>();
|
auto lodManager = DependencyManager::get<LODManager>();
|
||||||
_lodSize->setValue(lodManager->getOctreeSizeScale() / TREE_SCALE);
|
_lodSize->setValue(lodManager->getOctreeSizeScale() / TREE_SCALE);
|
||||||
_boundaryLevelAdjust->setValue(lodManager->getBoundaryLevelAdjust());
|
|
||||||
_feedback->setText(lodManager->getLODFeedbackText());
|
_feedback->setText(lodManager->getLODFeedbackText());
|
||||||
}
|
}
|
||||||
|
|
||||||
void LodToolsDialog::updateAvatarLODControls() {
|
void LodToolsDialog::updateAutomaticLODAdjust() {
|
||||||
QFormLayout* form = static_cast<QFormLayout*>(layout());
|
|
||||||
|
|
||||||
auto lodManager = DependencyManager::get<LODManager>();
|
auto lodManager = DependencyManager::get<LODManager>();
|
||||||
lodManager->setAutomaticAvatarLOD(_automaticAvatarLOD->isChecked());
|
lodManager->setAutomaticLODAdjust(!_manualLODAdjust->isChecked());
|
||||||
|
_lodSize->setEnabled(_manualLODAdjust->isChecked());
|
||||||
_avatarLODDecreaseFPS->setVisible(_automaticAvatarLOD->isChecked());
|
|
||||||
form->labelForField(_avatarLODDecreaseFPS)->setVisible(_automaticAvatarLOD->isChecked());
|
|
||||||
|
|
||||||
_avatarLODIncreaseFPS->setVisible(_automaticAvatarLOD->isChecked());
|
|
||||||
form->labelForField(_avatarLODIncreaseFPS)->setVisible(_automaticAvatarLOD->isChecked());
|
|
||||||
|
|
||||||
_avatarLOD->setVisible(!_automaticAvatarLOD->isChecked());
|
|
||||||
form->labelForField(_avatarLOD)->setVisible(!_automaticAvatarLOD->isChecked());
|
|
||||||
|
|
||||||
if (!_automaticAvatarLOD->isChecked()) {
|
|
||||||
_avatarLOD->setValue(1.0 / lodManager->getAvatarLODDistanceMultiplier());
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isVisible()) {
|
|
||||||
adjustSize();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void LodToolsDialog::updateAvatarLODValues() {
|
|
||||||
auto lodManager = DependencyManager::get<LODManager>();
|
|
||||||
if (_automaticAvatarLOD->isChecked()) {
|
|
||||||
lodManager->setAvatarLODDecreaseFPS(_avatarLODDecreaseFPS->value());
|
|
||||||
lodManager->setAvatarLODIncreaseFPS(_avatarLODIncreaseFPS->value());
|
|
||||||
|
|
||||||
} else {
|
|
||||||
lodManager->setAvatarLODDistanceMultiplier(1.0 / _avatarLOD->value());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void LodToolsDialog::sizeScaleValueChanged(int value) {
|
void LodToolsDialog::sizeScaleValueChanged(int value) {
|
||||||
|
@ -160,20 +98,13 @@ void LodToolsDialog::sizeScaleValueChanged(int value) {
|
||||||
_feedback->setText(lodManager->getLODFeedbackText());
|
_feedback->setText(lodManager->getLODFeedbackText());
|
||||||
}
|
}
|
||||||
|
|
||||||
void LodToolsDialog::boundaryLevelValueChanged(int value) {
|
|
||||||
auto lodManager = DependencyManager::get<LODManager>();
|
|
||||||
lodManager->setBoundaryLevelAdjust(value);
|
|
||||||
_feedback->setText(lodManager->getLODFeedbackText());
|
|
||||||
}
|
|
||||||
|
|
||||||
void LodToolsDialog::resetClicked(bool checked) {
|
void LodToolsDialog::resetClicked(bool checked) {
|
||||||
|
|
||||||
int sliderValue = DEFAULT_OCTREE_SIZE_SCALE / TREE_SCALE;
|
int sliderValue = DEFAULT_OCTREE_SIZE_SCALE / TREE_SCALE;
|
||||||
//sizeScaleValueChanged(sliderValue);
|
|
||||||
_lodSize->setValue(sliderValue);
|
_lodSize->setValue(sliderValue);
|
||||||
_boundaryLevelAdjust->setValue(0);
|
_manualLODAdjust->setChecked(false);
|
||||||
_automaticAvatarLOD->setChecked(true);
|
|
||||||
_avatarLODDecreaseFPS->setValue(DEFAULT_ADJUST_AVATAR_LOD_DOWN_FPS);
|
updateAutomaticLODAdjust(); // tell our LOD manager about the reset
|
||||||
_avatarLODIncreaseFPS->setValue(ADJUST_LOD_UP_FPS);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void LodToolsDialog::reject() {
|
void LodToolsDialog::reject() {
|
||||||
|
@ -184,6 +115,15 @@ void LodToolsDialog::reject() {
|
||||||
void LodToolsDialog::closeEvent(QCloseEvent* event) {
|
void LodToolsDialog::closeEvent(QCloseEvent* event) {
|
||||||
this->QDialog::closeEvent(event);
|
this->QDialog::closeEvent(event);
|
||||||
emit closed();
|
emit closed();
|
||||||
|
auto lodManager = DependencyManager::get<LODManager>();
|
||||||
|
|
||||||
|
// always revert back to automatic LOD adjustment when closed
|
||||||
|
lodManager->setAutomaticLODAdjust(true);
|
||||||
|
|
||||||
|
// if the user adjusted the LOD above "normal" then always revert back to default
|
||||||
|
if (lodManager->getOctreeSizeScale() > DEFAULT_OCTREE_SIZE_SCALE) {
|
||||||
|
lodManager->setOctreeSizeScale(DEFAULT_OCTREE_SIZE_SCALE);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -31,11 +31,9 @@ signals:
|
||||||
public slots:
|
public slots:
|
||||||
void reject();
|
void reject();
|
||||||
void sizeScaleValueChanged(int value);
|
void sizeScaleValueChanged(int value);
|
||||||
void boundaryLevelValueChanged(int value);
|
|
||||||
void resetClicked(bool checked);
|
void resetClicked(bool checked);
|
||||||
void reloadSliders();
|
void reloadSliders();
|
||||||
void updateAvatarLODControls();
|
void updateAutomaticLODAdjust();
|
||||||
void updateAvatarLODValues();
|
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
|
|
||||||
|
@ -44,11 +42,13 @@ protected:
|
||||||
|
|
||||||
private:
|
private:
|
||||||
QSlider* _lodSize;
|
QSlider* _lodSize;
|
||||||
QSlider* _boundaryLevelAdjust;
|
|
||||||
QCheckBox* _automaticAvatarLOD;
|
QCheckBox* _manualLODAdjust;
|
||||||
QDoubleSpinBox* _avatarLODDecreaseFPS;
|
|
||||||
QDoubleSpinBox* _avatarLODIncreaseFPS;
|
QDoubleSpinBox* _desktopLODDecreaseFPS;
|
||||||
QDoubleSpinBox* _avatarLOD;
|
|
||||||
|
QDoubleSpinBox* _hmdLODDecreaseFPS;
|
||||||
|
|
||||||
QLabel* _feedback;
|
QLabel* _feedback;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -19,6 +19,7 @@
|
||||||
|
|
||||||
#include "Application.h"
|
#include "Application.h"
|
||||||
#include "MainWindow.h"
|
#include "MainWindow.h"
|
||||||
|
#include "LODManager.h"
|
||||||
#include "Menu.h"
|
#include "Menu.h"
|
||||||
#include "ModelsBrowser.h"
|
#include "ModelsBrowser.h"
|
||||||
#include "PreferencesDialog.h"
|
#include "PreferencesDialog.h"
|
||||||
|
@ -174,6 +175,10 @@ void PreferencesDialog::loadPreferences() {
|
||||||
ui.sixenseReticleMoveSpeedSpin->setValue(sixense.getReticleMoveSpeed());
|
ui.sixenseReticleMoveSpeedSpin->setValue(sixense.getReticleMoveSpeed());
|
||||||
ui.invertSixenseButtonsCheckBox->setChecked(sixense.getInvertButtons());
|
ui.invertSixenseButtonsCheckBox->setChecked(sixense.getInvertButtons());
|
||||||
|
|
||||||
|
// LOD items
|
||||||
|
auto lodManager = DependencyManager::get<LODManager>();
|
||||||
|
ui.desktopMinimumFPSSpin->setValue(lodManager->getDesktopLODDecreaseFPS());
|
||||||
|
ui.hmdMinimumFPSSpin->setValue(lodManager->getHMDLODDecreaseFPS());
|
||||||
}
|
}
|
||||||
|
|
||||||
void PreferencesDialog::savePreferences() {
|
void PreferencesDialog::savePreferences() {
|
||||||
|
@ -275,4 +280,9 @@ void PreferencesDialog::savePreferences() {
|
||||||
audio->setOutputStarveDetectionPeriod(ui.outputStarveDetectionPeriodSpinner->value());
|
audio->setOutputStarveDetectionPeriod(ui.outputStarveDetectionPeriodSpinner->value());
|
||||||
|
|
||||||
Application::getInstance()->resizeGL(glCanvas->width(), glCanvas->height());
|
Application::getInstance()->resizeGL(glCanvas->width(), glCanvas->height());
|
||||||
|
|
||||||
|
// LOD items
|
||||||
|
auto lodManager = DependencyManager::get<LODManager>();
|
||||||
|
lodManager->setDesktopLODDecreaseFPS(ui.desktopMinimumFPSSpin->value());
|
||||||
|
lodManager->setHMDLODDecreaseFPS(ui.hmdMinimumFPSSpin->value());
|
||||||
}
|
}
|
||||||
|
|
|
@ -701,6 +701,219 @@
|
||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<item>
|
||||||
|
<spacer name="verticalSpacer_10">
|
||||||
|
<property name="orientation">
|
||||||
|
<enum>Qt::Vertical</enum>
|
||||||
|
</property>
|
||||||
|
<property name="sizeType">
|
||||||
|
<enum>QSizePolicy::Fixed</enum>
|
||||||
|
</property>
|
||||||
|
<property name="sizeHint" stdset="0">
|
||||||
|
<size>
|
||||||
|
<width>20</width>
|
||||||
|
<height>20</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
|
</spacer>
|
||||||
|
</item>
|
||||||
|
|
||||||
|
<item>
|
||||||
|
<widget class="QLabel" name="levelOfDetailTitleLabel">
|
||||||
|
<property name="font">
|
||||||
|
<font>
|
||||||
|
<family>Arial</family>
|
||||||
|
<pointsize>18</pointsize>
|
||||||
|
<weight>75</weight>
|
||||||
|
<bold>true</bold>
|
||||||
|
</font>
|
||||||
|
</property>
|
||||||
|
<property name="styleSheet">
|
||||||
|
<string notr="true">color:#29967e</string>
|
||||||
|
</property>
|
||||||
|
<property name="text">
|
||||||
|
<string>Level of Detail Tuning</string>
|
||||||
|
</property>
|
||||||
|
<property name="alignment">
|
||||||
|
<set>Qt::AlignBottom|Qt::AlignLeading|Qt::AlignLeft</set>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
|
||||||
|
<item>
|
||||||
|
<layout class="QHBoxLayout" name="horizontalLayout_111x">
|
||||||
|
<property name="spacing">
|
||||||
|
<number>0</number>
|
||||||
|
</property>
|
||||||
|
<property name="topMargin">
|
||||||
|
<number>7</number>
|
||||||
|
</property>
|
||||||
|
<property name="rightMargin">
|
||||||
|
<number>0</number>
|
||||||
|
</property>
|
||||||
|
<property name="bottomMargin">
|
||||||
|
<number>7</number>
|
||||||
|
</property>
|
||||||
|
<item>
|
||||||
|
<widget class="QLabel" name="label_9x">
|
||||||
|
<property name="font">
|
||||||
|
<font>
|
||||||
|
<family>Arial</family>
|
||||||
|
</font>
|
||||||
|
</property>
|
||||||
|
<property name="styleSheet">
|
||||||
|
<string notr="true"/>
|
||||||
|
</property>
|
||||||
|
<property name="text">
|
||||||
|
<string>Minimum Desktop FPS</string>
|
||||||
|
</property>
|
||||||
|
<property name="indent">
|
||||||
|
<number>0</number>
|
||||||
|
</property>
|
||||||
|
<!--
|
||||||
|
<property name="buddy">
|
||||||
|
<cstring>fieldOfViewSpin</cstring>
|
||||||
|
</property>
|
||||||
|
-->
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<spacer name="horizontalSpacer_111x">
|
||||||
|
<property name="font">
|
||||||
|
<font>
|
||||||
|
<family>Arial</family>
|
||||||
|
</font>
|
||||||
|
</property>
|
||||||
|
<property name="orientation">
|
||||||
|
<enum>Qt::Horizontal</enum>
|
||||||
|
</property>
|
||||||
|
<property name="sizeHint" stdset="0">
|
||||||
|
<size>
|
||||||
|
<width>0</width>
|
||||||
|
<height>0</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
|
</spacer>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QSpinBox" name="desktopMinimumFPSSpin">
|
||||||
|
<property name="minimumSize">
|
||||||
|
<size>
|
||||||
|
<width>100</width>
|
||||||
|
<height>0</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
|
<property name="maximumSize">
|
||||||
|
<size>
|
||||||
|
<width>95</width>
|
||||||
|
<height>36</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
|
<property name="font">
|
||||||
|
<font>
|
||||||
|
<family>Arial</family>
|
||||||
|
</font>
|
||||||
|
</property>
|
||||||
|
<property name="minimum">
|
||||||
|
<number>0</number>
|
||||||
|
</property>
|
||||||
|
<property name="maximum">
|
||||||
|
<number>120</number>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</item>
|
||||||
|
|
||||||
|
<item>
|
||||||
|
<layout class="QHBoxLayout" name="horizontalLayout_111y">
|
||||||
|
<property name="spacing">
|
||||||
|
<number>0</number>
|
||||||
|
</property>
|
||||||
|
<property name="topMargin">
|
||||||
|
<number>7</number>
|
||||||
|
</property>
|
||||||
|
<property name="rightMargin">
|
||||||
|
<number>0</number>
|
||||||
|
</property>
|
||||||
|
<property name="bottomMargin">
|
||||||
|
<number>7</number>
|
||||||
|
</property>
|
||||||
|
<item>
|
||||||
|
<widget class="QLabel" name="label_9y">
|
||||||
|
<property name="font">
|
||||||
|
<font>
|
||||||
|
<family>Arial</family>
|
||||||
|
</font>
|
||||||
|
</property>
|
||||||
|
<property name="styleSheet">
|
||||||
|
<string notr="true"/>
|
||||||
|
</property>
|
||||||
|
<property name="text">
|
||||||
|
<string>Minimum HMD FPS</string>
|
||||||
|
</property>
|
||||||
|
<property name="indent">
|
||||||
|
<number>0</number>
|
||||||
|
</property>
|
||||||
|
<!--
|
||||||
|
<property name="buddy">
|
||||||
|
<cstring>fieldOfViewSpin</cstring>
|
||||||
|
</property>
|
||||||
|
-->
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<spacer name="horizontalSpacer_111y">
|
||||||
|
<property name="font">
|
||||||
|
<font>
|
||||||
|
<family>Arial</family>
|
||||||
|
</font>
|
||||||
|
</property>
|
||||||
|
<property name="orientation">
|
||||||
|
<enum>Qt::Horizontal</enum>
|
||||||
|
</property>
|
||||||
|
<property name="sizeHint" stdset="0">
|
||||||
|
<size>
|
||||||
|
<width>0</width>
|
||||||
|
<height>0</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
|
</spacer>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QSpinBox" name="hmdMinimumFPSSpin">
|
||||||
|
<property name="minimumSize">
|
||||||
|
<size>
|
||||||
|
<width>100</width>
|
||||||
|
<height>0</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
|
<property name="maximumSize">
|
||||||
|
<size>
|
||||||
|
<width>95</width>
|
||||||
|
<height>36</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
|
<property name="font">
|
||||||
|
<font>
|
||||||
|
<family>Arial</family>
|
||||||
|
</font>
|
||||||
|
</property>
|
||||||
|
<property name="minimum">
|
||||||
|
<number>0</number>
|
||||||
|
</property>
|
||||||
|
<property name="maximum">
|
||||||
|
<number>120</number>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</item>
|
||||||
|
|
||||||
|
|
||||||
<item>
|
<item>
|
||||||
<spacer name="verticalSpacer_8">
|
<spacer name="verticalSpacer_8">
|
||||||
<property name="orientation">
|
<property name="orientation">
|
||||||
|
@ -717,6 +930,7 @@
|
||||||
</property>
|
</property>
|
||||||
</spacer>
|
</spacer>
|
||||||
</item>
|
</item>
|
||||||
|
|
||||||
<item>
|
<item>
|
||||||
<widget class="QLabel" name="avatarTitleLabel">
|
<widget class="QLabel" name="avatarTitleLabel">
|
||||||
<property name="font">
|
<property name="font">
|
||||||
|
@ -738,6 +952,7 @@
|
||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
|
|
||||||
<item>
|
<item>
|
||||||
<layout class="QHBoxLayout" name="horizontalLayout_111">
|
<layout class="QHBoxLayout" name="horizontalLayout_111">
|
||||||
<property name="spacing">
|
<property name="spacing">
|
||||||
|
@ -820,6 +1035,9 @@
|
||||||
</item>
|
</item>
|
||||||
</layout>
|
</layout>
|
||||||
</item>
|
</item>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<item>
|
<item>
|
||||||
<layout class="QHBoxLayout" name="horizontalLayout_3">
|
<layout class="QHBoxLayout" name="horizontalLayout_3">
|
||||||
<property name="spacing">
|
<property name="spacing">
|
||||||
|
|
|
@ -23,7 +23,7 @@ const int TREE_SCALE = 16384; // ~10 miles.. This is the number of meters of t
|
||||||
const float DEFAULT_OCTREE_SIZE_SCALE = TREE_SCALE * 400.0f;
|
const float DEFAULT_OCTREE_SIZE_SCALE = TREE_SCALE * 400.0f;
|
||||||
|
|
||||||
// This is used in the LOD Tools to translate between the size scale slider and the values used to set the OctreeSizeScale
|
// This is used in the LOD Tools to translate between the size scale slider and the values used to set the OctreeSizeScale
|
||||||
const float MAX_LOD_SIZE_MULTIPLIER = 2000.0f;
|
const float MAX_LOD_SIZE_MULTIPLIER = 800.0f;
|
||||||
|
|
||||||
const int NUMBER_OF_CHILDREN = 8;
|
const int NUMBER_OF_CHILDREN = 8;
|
||||||
|
|
||||||
|
|
|
@ -16,10 +16,9 @@ subject to the following restrictions:
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
|
||||||
//#include <stdio.h>
|
|
||||||
|
|
||||||
#include "BulletCollision/CollisionDispatch/btGhostObject.h"
|
#include "BulletCollision/CollisionDispatch/btGhostObject.h"
|
||||||
|
|
||||||
|
#include "BulletUtil.h"
|
||||||
#include "CharacterController.h"
|
#include "CharacterController.h"
|
||||||
|
|
||||||
|
|
||||||
|
@ -54,7 +53,7 @@ m_me = me;
|
||||||
|
|
||||||
virtual btScalar addSingleResult(btCollisionWorld::LocalRayResult& rayResult, bool normalInWorldSpace)
|
virtual btScalar addSingleResult(btCollisionWorld::LocalRayResult& rayResult, bool normalInWorldSpace)
|
||||||
{
|
{
|
||||||
if(rayResult.m_collisionObject == m_me)
|
if (rayResult.m_collisionObject == m_me)
|
||||||
return 1.0;
|
return 1.0;
|
||||||
|
|
||||||
return ClosestRayResultCallback::addSingleResult(rayResult, normalInWorldSpace);
|
return ClosestRayResultCallback::addSingleResult(rayResult, normalInWorldSpace);
|
||||||
|
@ -64,8 +63,7 @@ btCollisionObject* m_me;
|
||||||
};
|
};
|
||||||
*/
|
*/
|
||||||
|
|
||||||
class btKinematicClosestNotMeConvexResultCallback : public btCollisionWorld::ClosestConvexResultCallback
|
class btKinematicClosestNotMeConvexResultCallback : public btCollisionWorld::ClosestConvexResultCallback {
|
||||||
{
|
|
||||||
public:
|
public:
|
||||||
btKinematicClosestNotMeConvexResultCallback(btCollisionObject* me, const btVector3& up, btScalar minSlopeDot)
|
btKinematicClosestNotMeConvexResultCallback(btCollisionObject* me, const btVector3& up, btScalar minSlopeDot)
|
||||||
: btCollisionWorld::ClosestConvexResultCallback(btVector3(0.0, 0.0, 0.0), btVector3(0.0, 0.0, 0.0))
|
: btCollisionWorld::ClosestConvexResultCallback(btVector3(0.0, 0.0, 0.0), btVector3(0.0, 0.0, 0.0))
|
||||||
|
@ -92,6 +90,9 @@ class btKinematicClosestNotMeConvexResultCallback : public btCollisionWorld::Clo
|
||||||
hitNormalWorld = convexResult.m_hitCollisionObject->getWorldTransform().getBasis()*convexResult.m_hitNormalLocal;
|
hitNormalWorld = convexResult.m_hitCollisionObject->getWorldTransform().getBasis()*convexResult.m_hitNormalLocal;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Note: hitNormalWorld points into character, away from object
|
||||||
|
// and m_up points opposite to movement
|
||||||
|
|
||||||
btScalar dotUp = m_up.dot(hitNormalWorld);
|
btScalar dotUp = m_up.dot(hitNormalWorld);
|
||||||
if (dotUp < m_minSlopeDot) {
|
if (dotUp < m_minSlopeDot) {
|
||||||
return btScalar(1.0);
|
return btScalar(1.0);
|
||||||
|
@ -106,21 +107,104 @@ protected:
|
||||||
btScalar m_minSlopeDot;
|
btScalar m_minSlopeDot;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
class StepDownConvexResultCallback : public btCollisionWorld::ClosestConvexResultCallback {
|
||||||
|
// special convex sweep callback for character during the stepDown() phase
|
||||||
|
public:
|
||||||
|
StepDownConvexResultCallback(btCollisionObject* me,
|
||||||
|
const btVector3& up,
|
||||||
|
const btVector3& start,
|
||||||
|
const btVector3& step,
|
||||||
|
const btVector3& pushDirection,
|
||||||
|
btScalar minSlopeDot,
|
||||||
|
btScalar radius,
|
||||||
|
btScalar halfHeight)
|
||||||
|
: btCollisionWorld::ClosestConvexResultCallback(btVector3(0.0, 0.0, 0.0), btVector3(0.0, 0.0, 0.0))
|
||||||
|
, m_me(me)
|
||||||
|
, m_up(up)
|
||||||
|
, m_start(start)
|
||||||
|
, m_step(step)
|
||||||
|
, m_pushDirection(pushDirection)
|
||||||
|
, m_minSlopeDot(minSlopeDot)
|
||||||
|
, m_radius(radius)
|
||||||
|
, m_halfHeight(halfHeight)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual btScalar addSingleResult(btCollisionWorld::LocalConvexResult& convexResult, bool normalInWorldSpace) {
|
||||||
|
if (convexResult.m_hitCollisionObject == m_me) {
|
||||||
|
return btScalar(1.0);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!convexResult.m_hitCollisionObject->hasContactResponse()) {
|
||||||
|
return btScalar(1.0);
|
||||||
|
}
|
||||||
|
|
||||||
|
btVector3 hitNormalWorld;
|
||||||
|
if (normalInWorldSpace) {
|
||||||
|
hitNormalWorld = convexResult.m_hitNormalLocal;
|
||||||
|
} else {
|
||||||
|
///need to transform normal into worldspace
|
||||||
|
hitNormalWorld = convexResult.m_hitCollisionObject->getWorldTransform().getBasis() * convexResult.m_hitNormalLocal;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Note: hitNormalWorld points into character, away from object
|
||||||
|
// and m_up points opposite to movement
|
||||||
|
|
||||||
|
btScalar dotUp = m_up.dot(hitNormalWorld);
|
||||||
|
if (dotUp < m_minSlopeDot) {
|
||||||
|
if (hitNormalWorld.dot(m_pushDirection) > 0.0f) {
|
||||||
|
// ignore hits that push in same direction as character is moving
|
||||||
|
// which helps character NOT snag when stepping off ledges
|
||||||
|
return btScalar(1.0f);
|
||||||
|
}
|
||||||
|
|
||||||
|
// compute the angle between "down" and the line from character center to "hit" point
|
||||||
|
btVector3 fractionalStep = convexResult.m_hitFraction * m_step;
|
||||||
|
btVector3 localHit = convexResult.m_hitPointLocal - m_start + fractionalStep;
|
||||||
|
btScalar angle = localHit.angle(-m_up);
|
||||||
|
|
||||||
|
// compute a maxAngle based on size of m_step
|
||||||
|
btVector3 side(m_radius, - (m_halfHeight - m_step.length() + fractionalStep.dot(m_up)), 0.0f);
|
||||||
|
btScalar maxAngle = side.angle(-m_up);
|
||||||
|
|
||||||
|
// Ignore hits that are larger than maxAngle. Effectively what is happening here is:
|
||||||
|
// we're ignoring hits at contacts that have non-vertical normals... if they hit higher
|
||||||
|
// than the character's "feet". Ignoring the contact allows the character to slide down
|
||||||
|
// for these hits. In other words, vertical walls against the character's torso will
|
||||||
|
// not prevent them from "stepping down" to find the floor.
|
||||||
|
if (angle > maxAngle) {
|
||||||
|
return btScalar(1.0f);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
btScalar fraction = ClosestConvexResultCallback::addSingleResult(convexResult, normalInWorldSpace);
|
||||||
|
return fraction;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected:
|
||||||
|
btCollisionObject* m_me;
|
||||||
|
const btVector3 m_up;
|
||||||
|
btVector3 m_start;
|
||||||
|
btVector3 m_step;
|
||||||
|
btVector3 m_pushDirection;
|
||||||
|
btScalar m_minSlopeDot;
|
||||||
|
btScalar m_radius;
|
||||||
|
btScalar m_halfHeight;
|
||||||
|
};
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Returns the reflection direction of a ray going 'direction' hitting a surface with normal 'normal'
|
* Returns the reflection direction of a ray going 'direction' hitting a surface with normal 'normal'
|
||||||
*
|
*
|
||||||
* from: http://www-cs-students.stanford.edu/~adityagp/final/node3.html
|
* from: http://www-cs-students.stanford.edu/~adityagp/final/node3.html
|
||||||
*/
|
*/
|
||||||
btVector3 CharacterController::computeReflectionDirection(const btVector3& direction, const btVector3& normal)
|
btVector3 CharacterController::computeReflectionDirection(const btVector3& direction, const btVector3& normal) {
|
||||||
{
|
|
||||||
return direction - (btScalar(2.0) * direction.dot(normal)) * normal;
|
return direction - (btScalar(2.0) * direction.dot(normal)) * normal;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Returns the portion of 'direction' that is parallel to 'normal'
|
* Returns the portion of 'direction' that is parallel to 'normal'
|
||||||
*/
|
*/
|
||||||
btVector3 CharacterController::parallelComponent(const btVector3& direction, const btVector3& normal)
|
btVector3 CharacterController::parallelComponent(const btVector3& direction, const btVector3& normal) {
|
||||||
{
|
|
||||||
btScalar magnitude = direction.dot(normal);
|
btScalar magnitude = direction.dot(normal);
|
||||||
return normal * magnitude;
|
return normal * magnitude;
|
||||||
}
|
}
|
||||||
|
@ -128,36 +212,38 @@ btVector3 CharacterController::parallelComponent(const btVector3& direction, con
|
||||||
/*
|
/*
|
||||||
* Returns the portion of 'direction' that is perpindicular to 'normal'
|
* Returns the portion of 'direction' that is perpindicular to 'normal'
|
||||||
*/
|
*/
|
||||||
btVector3 CharacterController::perpindicularComponent(const btVector3& direction, const btVector3& normal)
|
btVector3 CharacterController::perpindicularComponent(const btVector3& direction, const btVector3& normal) {
|
||||||
{
|
|
||||||
return direction - parallelComponent(direction, normal);
|
return direction - parallelComponent(direction, normal);
|
||||||
}
|
}
|
||||||
|
|
||||||
CharacterController::CharacterController(
|
CharacterController::CharacterController(AvatarData* avatarData) {
|
||||||
btPairCachingGhostObject* ghostObject,
|
assert(avatarData);
|
||||||
btConvexShape* convexShape,
|
m_avatarData = avatarData;
|
||||||
btScalar stepHeight,
|
|
||||||
int upAxis) {
|
// cache the "PhysicsEnabled" state of m_avatarData
|
||||||
m_upAxis = upAxis;
|
m_avatarData->lockForRead();
|
||||||
m_addedMargin = 0.02;
|
m_enabled = m_avatarData->isPhysicsEnabled();
|
||||||
m_walkDirection.setValue(0,0,0);
|
m_avatarData->unlock();
|
||||||
m_useGhostObjectSweepTest = true;
|
|
||||||
m_ghostObject = ghostObject;
|
createShapeAndGhost();
|
||||||
m_stepHeight = stepHeight;
|
|
||||||
m_turnAngle = btScalar(0.0);
|
m_upAxis = 1; // HACK: hard coded to be 1 for now (yAxis)
|
||||||
m_convexShape = convexShape;
|
|
||||||
|
m_addedMargin = 0.02f;
|
||||||
|
m_walkDirection.setValue(0.0f,0.0f,0.0f);
|
||||||
|
m_turnAngle = btScalar(0.0f);
|
||||||
m_useWalkDirection = true; // use walk direction by default, legacy behavior
|
m_useWalkDirection = true; // use walk direction by default, legacy behavior
|
||||||
m_velocityTimeInterval = 0.0;
|
m_velocityTimeInterval = 0.0f;
|
||||||
m_verticalVelocity = 0.0;
|
m_verticalVelocity = 0.0f;
|
||||||
m_verticalOffset = 0.0;
|
m_verticalOffset = 0.0f;
|
||||||
m_gravity = 9.8 * 3 ; // 3G acceleration.
|
m_gravity = 9.8f;
|
||||||
m_fallSpeed = 55.0; // Terminal velocity of a sky diver in m/s.
|
m_maxFallSpeed = 55.0f; // Terminal velocity of a sky diver in m/s.
|
||||||
m_jumpSpeed = 10.0; // ?
|
m_jumpSpeed = 10.0f; // ?
|
||||||
m_wasOnGround = false;
|
m_wasOnGround = false;
|
||||||
m_wasJumping = false;
|
m_wasJumping = false;
|
||||||
m_interpolateUp = true;
|
m_interpolateUp = true;
|
||||||
setMaxSlope(btRadians(45.0));
|
setMaxSlope(btRadians(45.0f));
|
||||||
m_currentStepOffset = 0;
|
m_lastStepUp = 0.0f;
|
||||||
|
|
||||||
// internal state data members
|
// internal state data members
|
||||||
full_drop = false;
|
full_drop = false;
|
||||||
|
@ -192,6 +278,9 @@ bool CharacterController::recoverFromPenetration(btCollisionWorld* collisionWorl
|
||||||
collisionWorld->getDispatcher()->dispatchAllCollisionPairs(m_ghostObject->getOverlappingPairCache(), collisionWorld->getDispatchInfo(), collisionWorld->getDispatcher());
|
collisionWorld->getDispatcher()->dispatchAllCollisionPairs(m_ghostObject->getOverlappingPairCache(), collisionWorld->getDispatchInfo(), collisionWorld->getDispatcher());
|
||||||
|
|
||||||
m_currentPosition = m_ghostObject->getWorldTransform().getOrigin();
|
m_currentPosition = m_ghostObject->getWorldTransform().getOrigin();
|
||||||
|
btVector3 up = getUpAxisDirections()[m_upAxis];
|
||||||
|
|
||||||
|
btVector3 currentPosition = m_currentPosition;
|
||||||
|
|
||||||
btScalar maxPen = btScalar(0.0);
|
btScalar maxPen = btScalar(0.0);
|
||||||
for (int i = 0; i < m_ghostObject->getOverlappingPairCache()->getNumOverlappingPairs(); i++) {
|
for (int i = 0; i < m_ghostObject->getOverlappingPairCache()->getNumOverlappingPairs(); i++) {
|
||||||
|
@ -210,83 +299,109 @@ bool CharacterController::recoverFromPenetration(btCollisionWorld* collisionWorl
|
||||||
collisionPair->m_algorithm->getAllContactManifolds(m_manifoldArray);
|
collisionPair->m_algorithm->getAllContactManifolds(m_manifoldArray);
|
||||||
}
|
}
|
||||||
|
|
||||||
for (int j=0;j<m_manifoldArray.size();j++) {
|
for (int j = 0;j < m_manifoldArray.size(); j++) {
|
||||||
btPersistentManifold* manifold = m_manifoldArray[j];
|
btPersistentManifold* manifold = m_manifoldArray[j];
|
||||||
btScalar directionSign = manifold->getBody0() == m_ghostObject ? btScalar(-1.0) : btScalar(1.0);
|
btScalar directionSign = (manifold->getBody0() == m_ghostObject) ? btScalar(1.0) : btScalar(-1.0);
|
||||||
for (int p=0;p<manifold->getNumContacts();p++) {
|
for (int p = 0;p < manifold->getNumContacts(); p++) {
|
||||||
const btManifoldPoint&pt = manifold->getContactPoint(p);
|
const btManifoldPoint&pt = manifold->getContactPoint(p);
|
||||||
|
|
||||||
btScalar dist = pt.getDistance();
|
btScalar dist = pt.getDistance();
|
||||||
|
|
||||||
if (dist < 0.0) {
|
if (dist < 0.0) {
|
||||||
if (dist < maxPen) {
|
bool useContact = true;
|
||||||
maxPen = dist;
|
btVector3 normal = pt.m_normalWorldOnB;
|
||||||
m_touchingNormal = pt.m_normalWorldOnB * directionSign;//??
|
normal *= directionSign; // always points from object to character
|
||||||
|
|
||||||
|
btScalar normalDotUp = normal.dot(up);
|
||||||
|
if (normalDotUp < m_maxSlopeCosine) {
|
||||||
|
// this contact has a non-vertical normal... might need to ignored
|
||||||
|
btVector3 collisionPoint;
|
||||||
|
if (directionSign > 0.0) {
|
||||||
|
collisionPoint = pt.getPositionWorldOnB();
|
||||||
|
} else {
|
||||||
|
collisionPoint = pt.getPositionWorldOnA();
|
||||||
|
}
|
||||||
|
|
||||||
|
// we do math in frame where character base is origin
|
||||||
|
btVector3 characterBase = currentPosition - (m_radius + m_halfHeight) * up;
|
||||||
|
collisionPoint -= characterBase;
|
||||||
|
btScalar collisionHeight = collisionPoint.dot(up);
|
||||||
|
|
||||||
|
if (collisionHeight < m_lastStepUp) {
|
||||||
|
// This contact is below the lastStepUp, so we ignore it for penetration resolution,
|
||||||
|
// otherwise it may prevent the character from getting close enough to find any available
|
||||||
|
// horizontal foothold that would allow it to climbe the ledge. In other words, we're
|
||||||
|
// making the character's "feet" soft for collisions against steps, but not floors.
|
||||||
|
useContact = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (useContact) {
|
||||||
|
|
||||||
|
if (dist < maxPen) {
|
||||||
|
maxPen = dist;
|
||||||
|
m_floorNormal = normal;
|
||||||
|
}
|
||||||
|
const btScalar INCREMENTAL_RESOLUTION_FACTOR = 0.2f;
|
||||||
|
m_currentPosition += normal * (fabsf(dist) * INCREMENTAL_RESOLUTION_FACTOR);
|
||||||
|
penetration = true;
|
||||||
}
|
}
|
||||||
m_currentPosition += pt.m_normalWorldOnB * directionSign * dist * btScalar(0.2);
|
|
||||||
penetration = true;
|
|
||||||
} else {
|
|
||||||
//printf("touching %f\n", dist);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//manifold->clearManifold();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
btTransform newTrans = m_ghostObject->getWorldTransform();
|
btTransform newTrans = m_ghostObject->getWorldTransform();
|
||||||
newTrans.setOrigin(m_currentPosition);
|
newTrans.setOrigin(m_currentPosition);
|
||||||
m_ghostObject->setWorldTransform(newTrans);
|
m_ghostObject->setWorldTransform(newTrans);
|
||||||
//printf("m_touchingNormal = %f,%f,%f\n", m_touchingNormal[0], m_touchingNormal[1], m_touchingNormal[2]);
|
|
||||||
return penetration;
|
return penetration;
|
||||||
}
|
}
|
||||||
|
|
||||||
void CharacterController::stepUp( btCollisionWorld* world) {
|
void CharacterController::stepUp( btCollisionWorld* world) {
|
||||||
// phase 1: up
|
// phase 1: up
|
||||||
|
|
||||||
|
// compute start and end
|
||||||
btTransform start, end;
|
btTransform start, end;
|
||||||
m_targetPosition = m_currentPosition + getUpAxisDirections()[m_upAxis] * (m_stepHeight + (m_verticalOffset > 0.f?m_verticalOffset:0.f));
|
|
||||||
|
|
||||||
start.setIdentity();
|
start.setIdentity();
|
||||||
end.setIdentity();
|
|
||||||
|
|
||||||
/* FIXME: Handle penetration properly */
|
|
||||||
start.setOrigin(m_currentPosition + getUpAxisDirections()[m_upAxis] * (m_convexShape->getMargin() + m_addedMargin));
|
start.setOrigin(m_currentPosition + getUpAxisDirections()[m_upAxis] * (m_convexShape->getMargin() + m_addedMargin));
|
||||||
|
|
||||||
|
//m_targetPosition = m_currentPosition + getUpAxisDirections()[m_upAxis] * (m_stepHeight + (m_verticalOffset > 0.0f ? m_verticalOffset : 0.0f));
|
||||||
|
m_targetPosition = m_currentPosition + getUpAxisDirections()[m_upAxis] * m_stepHeight;
|
||||||
|
end.setIdentity();
|
||||||
end.setOrigin(m_targetPosition);
|
end.setOrigin(m_targetPosition);
|
||||||
|
|
||||||
btKinematicClosestNotMeConvexResultCallback callback(m_ghostObject, -getUpAxisDirections()[m_upAxis], btScalar(0.7071));
|
// sweep up
|
||||||
|
btVector3 sweepDirNegative = -getUpAxisDirections()[m_upAxis];
|
||||||
|
btKinematicClosestNotMeConvexResultCallback callback(m_ghostObject, sweepDirNegative, btScalar(0.7071));
|
||||||
callback.m_collisionFilterGroup = getGhostObject()->getBroadphaseHandle()->m_collisionFilterGroup;
|
callback.m_collisionFilterGroup = getGhostObject()->getBroadphaseHandle()->m_collisionFilterGroup;
|
||||||
callback.m_collisionFilterMask = getGhostObject()->getBroadphaseHandle()->m_collisionFilterMask;
|
callback.m_collisionFilterMask = getGhostObject()->getBroadphaseHandle()->m_collisionFilterMask;
|
||||||
|
m_ghostObject->convexSweepTest(m_convexShape, start, end, callback, world->getDispatchInfo().m_allowedCcdPenetration);
|
||||||
if (m_useGhostObjectSweepTest) {
|
|
||||||
m_ghostObject->convexSweepTest(m_convexShape, start, end, callback, world->getDispatchInfo().m_allowedCcdPenetration);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
world->convexSweepTest(m_convexShape, start, end, callback);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (callback.hasHit()) {
|
if (callback.hasHit()) {
|
||||||
|
// we hit something, so zero our vertical velocity
|
||||||
|
m_verticalVelocity = 0.0;
|
||||||
|
m_verticalOffset = 0.0;
|
||||||
|
|
||||||
// Only modify the position if the hit was a slope and not a wall or ceiling.
|
// Only modify the position if the hit was a slope and not a wall or ceiling.
|
||||||
if (callback.m_hitNormalWorld.dot(getUpAxisDirections()[m_upAxis]) > 0.0) {
|
if (callback.m_hitNormalWorld.dot(getUpAxisDirections()[m_upAxis]) > 0.0) {
|
||||||
// we moved up only a fraction of the step height
|
m_lastStepUp = m_stepHeight * callback.m_closestHitFraction;
|
||||||
m_currentStepOffset = m_stepHeight * callback.m_closestHitFraction;
|
|
||||||
if (m_interpolateUp == true) {
|
if (m_interpolateUp == true) {
|
||||||
m_currentPosition.setInterpolate3 (m_currentPosition, m_targetPosition, callback.m_closestHitFraction);
|
m_currentPosition.setInterpolate3 (m_currentPosition, m_targetPosition, callback.m_closestHitFraction);
|
||||||
} else {
|
} else {
|
||||||
m_currentPosition = m_targetPosition;
|
m_currentPosition = m_targetPosition;
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
m_lastStepUp = m_stepHeight;
|
||||||
|
m_currentPosition = m_targetPosition;
|
||||||
}
|
}
|
||||||
m_verticalVelocity = 0.0;
|
|
||||||
m_verticalOffset = 0.0;
|
|
||||||
} else {
|
} else {
|
||||||
m_currentStepOffset = m_stepHeight;
|
|
||||||
m_currentPosition = m_targetPosition;
|
m_currentPosition = m_targetPosition;
|
||||||
|
m_lastStepUp = m_stepHeight;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void CharacterController::updateTargetPositionBasedOnCollision(const btVector3& hitNormal, btScalar tangentMag, btScalar normalMag) {
|
void CharacterController::updateTargetPositionBasedOnCollision(const btVector3& hitNormal, btScalar tangentMag, btScalar normalMag) {
|
||||||
btVector3 movementDirection = m_targetPosition - m_currentPosition;
|
btVector3 movementDirection = m_targetPosition - m_currentPosition;
|
||||||
btScalar movementLength = movementDirection.length();
|
btScalar movementLength = movementDirection.length();
|
||||||
if (movementLength>SIMD_EPSILON) {
|
if (movementLength > SIMD_EPSILON) {
|
||||||
movementDirection.normalize();
|
movementDirection.normalize();
|
||||||
|
|
||||||
btVector3 reflectDir = computeReflectionDirection(movementDirection, hitNormal);
|
btVector3 reflectDir = computeReflectionDirection(movementDirection, hitNormal);
|
||||||
|
@ -300,241 +415,152 @@ void CharacterController::updateTargetPositionBasedOnCollision(const btVector3&
|
||||||
m_targetPosition = m_currentPosition;
|
m_targetPosition = m_currentPosition;
|
||||||
//if (tangentMag != 0.0) {
|
//if (tangentMag != 0.0) {
|
||||||
if (0) {
|
if (0) {
|
||||||
btVector3 parComponent = parallelDir * btScalar(tangentMag*movementLength);
|
btVector3 parComponent = parallelDir * btScalar(tangentMag * movementLength);
|
||||||
//printf("parComponent=%f,%f,%f\n", parComponent[0], parComponent[1], parComponent[2]);
|
|
||||||
m_targetPosition += parComponent;
|
m_targetPosition += parComponent;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (normalMag != 0.0) {
|
if (normalMag != 0.0) {
|
||||||
btVector3 perpComponent = perpindicularDir * btScalar(normalMag*movementLength);
|
btVector3 perpComponent = perpindicularDir * btScalar(normalMag * movementLength);
|
||||||
//printf("perpComponent=%f,%f,%f\n", perpComponent[0], perpComponent[1], perpComponent[2]);
|
|
||||||
m_targetPosition += perpComponent;
|
m_targetPosition += perpComponent;
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
//printf("movementLength don't normalize a zero vector\n");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void CharacterController::stepForwardAndStrafe( btCollisionWorld* collisionWorld, const btVector3& walkMove) {
|
void CharacterController::stepForward( btCollisionWorld* collisionWorld, const btVector3& movement) {
|
||||||
//printf("m_normalizedDirection=%f,%f,%f\n",
|
// phase 2: forward
|
||||||
// m_normalizedDirection[0], m_normalizedDirection[1], m_normalizedDirection[2]);
|
m_targetPosition = m_currentPosition + movement;
|
||||||
// phase 2: forward and strafe
|
|
||||||
btTransform start, end;
|
|
||||||
m_targetPosition = m_currentPosition + walkMove;
|
|
||||||
|
|
||||||
|
btTransform start, end;
|
||||||
start.setIdentity();
|
start.setIdentity();
|
||||||
end.setIdentity();
|
end.setIdentity();
|
||||||
|
|
||||||
btScalar fraction = 1.0;
|
/* TODO: experiment with this to see if we can use this to help direct motion when a floor is available
|
||||||
btScalar distance2 = (m_currentPosition-m_targetPosition).length2();
|
|
||||||
//printf("distance2=%f\n", distance2);
|
|
||||||
|
|
||||||
if (m_touchingContact) {
|
if (m_touchingContact) {
|
||||||
if (m_normalizedDirection.dot(m_touchingNormal) > btScalar(0.0)) {
|
if (m_normalizedDirection.dot(m_floorNormal) < btScalar(0.0)) {
|
||||||
//interferes with step movement
|
updateTargetPositionBasedOnCollision(m_floorNormal, 1.0f, 1.0f);
|
||||||
//updateTargetPositionBasedOnCollision(m_touchingNormal);
|
|
||||||
}
|
}
|
||||||
}
|
}*/
|
||||||
|
|
||||||
|
// modify shape's margin for the sweeps
|
||||||
|
btScalar margin = m_convexShape->getMargin();
|
||||||
|
m_convexShape->setMargin(margin + m_addedMargin);
|
||||||
|
|
||||||
|
const btScalar MIN_STEP_DISTANCE = 0.0001f;
|
||||||
|
btVector3 step = m_targetPosition - m_currentPosition;
|
||||||
|
btScalar stepLength2 = step.length2();
|
||||||
int maxIter = 10;
|
int maxIter = 10;
|
||||||
|
|
||||||
while (fraction > btScalar(0.01) && maxIter-- > 0) {
|
while (stepLength2 > MIN_STEP_DISTANCE && maxIter-- > 0) {
|
||||||
start.setOrigin(m_currentPosition);
|
start.setOrigin(m_currentPosition);
|
||||||
end.setOrigin(m_targetPosition);
|
end.setOrigin(m_targetPosition);
|
||||||
btVector3 sweepDirNegative(m_currentPosition - m_targetPosition);
|
|
||||||
|
|
||||||
|
// sweep forward
|
||||||
|
btVector3 sweepDirNegative(m_currentPosition - m_targetPosition);
|
||||||
btKinematicClosestNotMeConvexResultCallback callback(m_ghostObject, sweepDirNegative, btScalar(0.0));
|
btKinematicClosestNotMeConvexResultCallback callback(m_ghostObject, sweepDirNegative, btScalar(0.0));
|
||||||
callback.m_collisionFilterGroup = getGhostObject()->getBroadphaseHandle()->m_collisionFilterGroup;
|
callback.m_collisionFilterGroup = getGhostObject()->getBroadphaseHandle()->m_collisionFilterGroup;
|
||||||
callback.m_collisionFilterMask = getGhostObject()->getBroadphaseHandle()->m_collisionFilterMask;
|
callback.m_collisionFilterMask = getGhostObject()->getBroadphaseHandle()->m_collisionFilterMask;
|
||||||
|
m_ghostObject->convexSweepTest(m_convexShape, start, end, callback, collisionWorld->getDispatchInfo().m_allowedCcdPenetration);
|
||||||
|
|
||||||
btScalar margin = m_convexShape->getMargin();
|
|
||||||
m_convexShape->setMargin(margin + m_addedMargin);
|
|
||||||
|
|
||||||
|
|
||||||
if (m_useGhostObjectSweepTest) {
|
|
||||||
m_ghostObject->convexSweepTest(m_convexShape, start, end, callback, collisionWorld->getDispatchInfo().m_allowedCcdPenetration);
|
|
||||||
} else {
|
|
||||||
collisionWorld->convexSweepTest(m_convexShape, start, end, callback, collisionWorld->getDispatchInfo().m_allowedCcdPenetration);
|
|
||||||
}
|
|
||||||
|
|
||||||
m_convexShape->setMargin(margin);
|
|
||||||
|
|
||||||
|
|
||||||
fraction -= callback.m_closestHitFraction;
|
|
||||||
|
|
||||||
if (callback.hasHit()) {
|
if (callback.hasHit()) {
|
||||||
// we moved only a fraction
|
// we hit soemthing!
|
||||||
//btScalar hitDistance;
|
// Compute new target position by removing portion cut-off by collision, which will produce a new target
|
||||||
//hitDistance = (callback.m_hitPointWorld - m_currentPosition).length();
|
// that is the closest approach of the the obstacle plane to the original target.
|
||||||
|
step = m_targetPosition - m_currentPosition;
|
||||||
|
btScalar stepDotNormal = step.dot(callback.m_hitNormalWorld); // we expect this dot to be negative
|
||||||
|
step += (stepDotNormal * (1.0f - callback.m_closestHitFraction)) * callback.m_hitNormalWorld;
|
||||||
|
m_targetPosition = m_currentPosition + step;
|
||||||
|
|
||||||
//m_currentPosition.setInterpolate3 (m_currentPosition, m_targetPosition, callback.m_closestHitFraction);
|
stepLength2 = step.length2();
|
||||||
|
|
||||||
updateTargetPositionBasedOnCollision(callback.m_hitNormalWorld);
|
|
||||||
btVector3 currentDir = m_targetPosition - m_currentPosition;
|
|
||||||
distance2 = currentDir.length2();
|
|
||||||
if (distance2 > SIMD_EPSILON) {
|
|
||||||
currentDir.normalize();
|
|
||||||
/* See Quake2: "If velocity is against original velocity, stop ead to avoid tiny oscilations in sloping corners." */
|
|
||||||
if (currentDir.dot(m_normalizedDirection) <= btScalar(0.0)) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
//printf("currentDir: don't normalize a zero vector\n");
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
// we moved whole way
|
// we swept to the end without hitting anything
|
||||||
m_currentPosition = m_targetPosition;
|
m_currentPosition = m_targetPosition;
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
//if (callback.m_closestHitFraction == 0.f) {
|
|
||||||
// break;
|
|
||||||
//}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// restore shape's margin
|
||||||
|
m_convexShape->setMargin(margin);
|
||||||
}
|
}
|
||||||
|
|
||||||
void CharacterController::stepDown( btCollisionWorld* collisionWorld, btScalar dt) {
|
void CharacterController::stepDown( btCollisionWorld* collisionWorld, btScalar dt) {
|
||||||
btTransform start, end, end_double;
|
|
||||||
bool runonce = false;
|
|
||||||
|
|
||||||
// phase 3: down
|
// phase 3: down
|
||||||
/*btScalar additionalDownStep = (m_wasOnGround && !onGround()) ? m_stepHeight : 0.0;
|
//
|
||||||
btVector3 step_drop = getUpAxisDirections()[m_upAxis] * (m_currentStepOffset + additionalDownStep);
|
// The "stepDown" phase first makes a normal sweep down that cancels the lift from the "stepUp" phase.
|
||||||
btScalar downVelocity = (additionalDownStep == 0.0 && m_verticalVelocity<0.0?-m_verticalVelocity:0.0) * dt;
|
// If it hits a ledge then it stops otherwise it makes another sweep down in search of a floor within
|
||||||
btVector3 gravity_drop = getUpAxisDirections()[m_upAxis] * downVelocity;
|
// reach of the character's feet.
|
||||||
m_targetPosition -= (step_drop + gravity_drop);*/
|
|
||||||
|
|
||||||
btVector3 orig_position = m_targetPosition;
|
btScalar downSpeed = (m_verticalVelocity < 0.0f) ? -m_verticalVelocity : 0.0f;
|
||||||
|
if (downSpeed > 0.0f && downSpeed > m_maxFallSpeed && (m_wasOnGround || !m_wasJumping)) {
|
||||||
btScalar downVelocity = (m_verticalVelocity<0.f?-m_verticalVelocity:0.f) * dt;
|
downSpeed = m_maxFallSpeed;
|
||||||
|
|
||||||
if (downVelocity > 0.0 && downVelocity > m_fallSpeed && (m_wasOnGround || !m_wasJumping)) {
|
|
||||||
downVelocity = m_fallSpeed;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
btVector3 step_drop = getUpAxisDirections()[m_upAxis] * (m_currentStepOffset + downVelocity);
|
// first sweep for ledge
|
||||||
m_targetPosition -= step_drop;
|
btVector3 step = getUpAxisDirections()[m_upAxis] * (-(m_lastStepUp + downSpeed * dt));
|
||||||
|
|
||||||
btKinematicClosestNotMeConvexResultCallback callback(m_ghostObject, getUpAxisDirections()[m_upAxis], m_maxSlopeCosine);
|
StepDownConvexResultCallback callback(m_ghostObject,
|
||||||
|
getUpAxisDirections()[m_upAxis],
|
||||||
|
m_currentPosition, step,
|
||||||
|
m_walkDirection,
|
||||||
|
m_maxSlopeCosine,
|
||||||
|
m_radius, m_halfHeight);
|
||||||
callback.m_collisionFilterGroup = getGhostObject()->getBroadphaseHandle()->m_collisionFilterGroup;
|
callback.m_collisionFilterGroup = getGhostObject()->getBroadphaseHandle()->m_collisionFilterGroup;
|
||||||
callback.m_collisionFilterMask = getGhostObject()->getBroadphaseHandle()->m_collisionFilterMask;
|
callback.m_collisionFilterMask = getGhostObject()->getBroadphaseHandle()->m_collisionFilterMask;
|
||||||
|
|
||||||
btKinematicClosestNotMeConvexResultCallback callback2 (m_ghostObject, getUpAxisDirections()[m_upAxis], m_maxSlopeCosine);
|
btTransform start, end;
|
||||||
callback2.m_collisionFilterGroup = getGhostObject()->getBroadphaseHandle()->m_collisionFilterGroup;
|
start.setIdentity();
|
||||||
callback2.m_collisionFilterMask = getGhostObject()->getBroadphaseHandle()->m_collisionFilterMask;
|
end.setIdentity();
|
||||||
|
|
||||||
while (1) {
|
start.setOrigin(m_currentPosition);
|
||||||
start.setIdentity();
|
m_targetPosition = m_currentPosition + step;
|
||||||
end.setIdentity();
|
end.setOrigin(m_targetPosition);
|
||||||
|
m_ghostObject->convexSweepTest(m_convexShape, start, end, callback, collisionWorld->getDispatchInfo().m_allowedCcdPenetration);
|
||||||
|
|
||||||
end_double.setIdentity();
|
if (callback.hasHit()) {
|
||||||
|
m_currentPosition += callback.m_closestHitFraction * step;
|
||||||
|
m_verticalVelocity = 0.0f;
|
||||||
|
m_verticalOffset = 0.0f;
|
||||||
|
m_wasJumping = false;
|
||||||
|
} else {
|
||||||
|
// sweep again for floor within downStep threshold
|
||||||
|
StepDownConvexResultCallback callback2 (m_ghostObject,
|
||||||
|
getUpAxisDirections()[m_upAxis],
|
||||||
|
m_currentPosition, step,
|
||||||
|
m_walkDirection,
|
||||||
|
m_maxSlopeCosine,
|
||||||
|
m_radius, m_halfHeight);
|
||||||
|
|
||||||
|
callback2.m_collisionFilterGroup = getGhostObject()->getBroadphaseHandle()->m_collisionFilterGroup;
|
||||||
|
callback2.m_collisionFilterMask = getGhostObject()->getBroadphaseHandle()->m_collisionFilterMask;
|
||||||
|
|
||||||
|
m_currentPosition = m_targetPosition;
|
||||||
|
btVector3 oldPosition = m_currentPosition;
|
||||||
|
step = (- m_stepHeight) * getUpAxisDirections()[m_upAxis];
|
||||||
|
m_targetPosition = m_currentPosition + step;
|
||||||
|
|
||||||
start.setOrigin(m_currentPosition);
|
start.setOrigin(m_currentPosition);
|
||||||
end.setOrigin(m_targetPosition);
|
end.setOrigin(m_targetPosition);
|
||||||
|
m_ghostObject->convexSweepTest(m_convexShape, start, end, callback2, collisionWorld->getDispatchInfo().m_allowedCcdPenetration);
|
||||||
|
|
||||||
//set double test for 2x the step drop, to check for a large drop vs small drop
|
if (callback2.hasHit()) {
|
||||||
end_double.setOrigin(m_targetPosition - step_drop);
|
m_currentPosition += callback2.m_closestHitFraction * step;
|
||||||
|
m_verticalVelocity = 0.0f;
|
||||||
if (m_useGhostObjectSweepTest) {
|
m_verticalOffset = 0.0f;
|
||||||
m_ghostObject->convexSweepTest(m_convexShape, start, end, callback, collisionWorld->getDispatchInfo().m_allowedCcdPenetration);
|
m_wasJumping = false;
|
||||||
|
|
||||||
if (!callback.hasHit()) {
|
|
||||||
//test a double fall height, to see if the character should interpolate it's fall (full) or not (partial)
|
|
||||||
m_ghostObject->convexSweepTest(m_convexShape, start, end_double, callback2, collisionWorld->getDispatchInfo().m_allowedCcdPenetration);
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
collisionWorld->convexSweepTest(m_convexShape, start, end, callback, collisionWorld->getDispatchInfo().m_allowedCcdPenetration);
|
// nothing to step down on, so remove the stepUp effect
|
||||||
|
m_currentPosition = oldPosition - m_lastStepUp * getUpAxisDirections()[m_upAxis];
|
||||||
if (!callback.hasHit()) {
|
m_lastStepUp = 0.0f;
|
||||||
//test a double fall height, to see if the character should interpolate it's fall (large) or not (small)
|
|
||||||
collisionWorld->convexSweepTest(m_convexShape, start, end_double, callback2, collisionWorld->getDispatchInfo().m_allowedCcdPenetration);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
btScalar downVelocity2 = (m_verticalVelocity<0.f?-m_verticalVelocity:0.f) * dt;
|
|
||||||
bool has_hit = false;
|
|
||||||
if(bounce_fix == true) {
|
|
||||||
has_hit = callback.hasHit() || callback2.hasHit();
|
|
||||||
} else {
|
|
||||||
has_hit = callback2.hasHit();
|
|
||||||
}
|
|
||||||
|
|
||||||
if(downVelocity2 > 0.0 && downVelocity2 < m_stepHeight && has_hit == true && runonce == false
|
|
||||||
&& (m_wasOnGround || !m_wasJumping)) {
|
|
||||||
//redo the velocity calculation when falling a small amount, for fast stairs motion
|
|
||||||
//for larger falls, use the smoother/slower interpolated movement by not touching the target position
|
|
||||||
|
|
||||||
m_targetPosition = orig_position;
|
|
||||||
downVelocity = m_stepHeight;
|
|
||||||
|
|
||||||
btVector3 step_drop = getUpAxisDirections()[m_upAxis] * (m_currentStepOffset + downVelocity);
|
|
||||||
m_targetPosition -= step_drop;
|
|
||||||
runonce = true;
|
|
||||||
continue; //re-run previous tests
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (callback.hasHit() || runonce == true) {
|
|
||||||
// we dropped a fraction of the height -> hit floor
|
|
||||||
|
|
||||||
btScalar fraction = (m_currentPosition.getY() - callback.m_hitPointWorld.getY()) / 2;
|
|
||||||
|
|
||||||
//printf("hitpoint: %g - pos %g\n", callback.m_hitPointWorld.getY(), m_currentPosition.getY());
|
|
||||||
|
|
||||||
if (bounce_fix == true) {
|
|
||||||
if (full_drop == true) {
|
|
||||||
m_currentPosition.setInterpolate3 (m_currentPosition, m_targetPosition, callback.m_closestHitFraction);
|
|
||||||
} else {
|
|
||||||
//due to errors in the closestHitFraction variable when used with large polygons, calculate the hit fraction manually
|
|
||||||
m_currentPosition.setInterpolate3 (m_currentPosition, m_targetPosition, fraction);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
m_currentPosition.setInterpolate3 (m_currentPosition, m_targetPosition, callback.m_closestHitFraction);
|
|
||||||
|
|
||||||
full_drop = false;
|
|
||||||
|
|
||||||
m_verticalVelocity = 0.0;
|
|
||||||
m_verticalOffset = 0.0;
|
|
||||||
m_wasJumping = false;
|
|
||||||
} else {
|
|
||||||
// we dropped the full height
|
|
||||||
|
|
||||||
full_drop = true;
|
|
||||||
|
|
||||||
if (bounce_fix == true) {
|
|
||||||
downVelocity = (m_verticalVelocity<0.f?-m_verticalVelocity:0.f) * dt;
|
|
||||||
if (downVelocity > m_fallSpeed && (m_wasOnGround || !m_wasJumping)) {
|
|
||||||
m_targetPosition += step_drop; //undo previous target change
|
|
||||||
downVelocity = m_fallSpeed;
|
|
||||||
step_drop = getUpAxisDirections()[m_upAxis] * (m_currentStepOffset + downVelocity);
|
|
||||||
m_targetPosition -= step_drop;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
//printf("full drop - %g, %g\n", m_currentPosition.getY(), m_targetPosition.getY());
|
|
||||||
|
|
||||||
m_currentPosition = m_targetPosition;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
void CharacterController::setWalkDirection(const btVector3& walkDirection) {
|
void CharacterController::setWalkDirection(const btVector3& walkDirection) {
|
||||||
m_useWalkDirection = true;
|
m_useWalkDirection = true;
|
||||||
m_walkDirection = walkDirection;
|
m_walkDirection = walkDirection;
|
||||||
m_normalizedDirection = getNormalizedVector(m_walkDirection);
|
m_normalizedDirection = getNormalizedVector(m_walkDirection);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
void CharacterController::setVelocityForTimeInterval(const btVector3& velocity, btScalar timeInterval) {
|
void CharacterController::setVelocityForTimeInterval(const btVector3& velocity, btScalar timeInterval) {
|
||||||
//printf("setVelocity!\n");
|
|
||||||
//printf(" interval: %f\n", timeInterval);
|
|
||||||
//printf(" velocity: (%f, %f, %f)\n", velocity.x(), velocity.y(), velocity.z());
|
|
||||||
|
|
||||||
m_useWalkDirection = false;
|
m_useWalkDirection = false;
|
||||||
m_walkDirection = velocity;
|
m_walkDirection = velocity;
|
||||||
m_normalizedDirection = getNormalizedVector(m_walkDirection);
|
m_normalizedDirection = getNormalizedVector(m_walkDirection);
|
||||||
|
@ -565,29 +591,25 @@ void CharacterController::warp(const btVector3& origin) {
|
||||||
|
|
||||||
|
|
||||||
void CharacterController::preStep( btCollisionWorld* collisionWorld) {
|
void CharacterController::preStep( btCollisionWorld* collisionWorld) {
|
||||||
|
if (!m_enabled) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
int numPenetrationLoops = 0;
|
int numPenetrationLoops = 0;
|
||||||
m_touchingContact = false;
|
m_touchingContact = false;
|
||||||
while (recoverFromPenetration(collisionWorld)) {
|
while (recoverFromPenetration(collisionWorld)) {
|
||||||
numPenetrationLoops++;
|
numPenetrationLoops++;
|
||||||
m_touchingContact = true;
|
m_touchingContact = true;
|
||||||
if (numPenetrationLoops > 4) {
|
if (numPenetrationLoops > 4) {
|
||||||
//printf("character could not recover from penetration = %d\n", numPenetrationLoops);
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
m_currentPosition = m_ghostObject->getWorldTransform().getOrigin();
|
m_currentPosition = m_ghostObject->getWorldTransform().getOrigin();
|
||||||
m_targetPosition = m_currentPosition;
|
m_targetPosition = m_currentPosition;
|
||||||
//printf("m_targetPosition=%f,%f,%f\n", m_targetPosition[0], m_targetPosition[1], m_targetPosition[2]);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void CharacterController::playerStep( btCollisionWorld* collisionWorld, btScalar dt) {
|
void CharacterController::playerStep( btCollisionWorld* collisionWorld, btScalar dt) {
|
||||||
//printf("playerStep(): ");
|
if (!m_enabled || (!m_useWalkDirection && m_velocityTimeInterval <= 0.0)) {
|
||||||
//printf(" dt = %f", dt);
|
|
||||||
|
|
||||||
// quick check...
|
|
||||||
if (!m_useWalkDirection && m_velocityTimeInterval <= 0.0) {
|
|
||||||
//printf("\n");
|
|
||||||
return; // no motion
|
return; // no motion
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -595,49 +617,41 @@ void CharacterController::playerStep( btCollisionWorld* collisionWorld, btScala
|
||||||
|
|
||||||
// Update fall velocity.
|
// Update fall velocity.
|
||||||
m_verticalVelocity -= m_gravity * dt;
|
m_verticalVelocity -= m_gravity * dt;
|
||||||
if (m_verticalVelocity > 0.0 && m_verticalVelocity > m_jumpSpeed) {
|
if (m_verticalVelocity > m_jumpSpeed) {
|
||||||
m_verticalVelocity = m_jumpSpeed;
|
m_verticalVelocity = m_jumpSpeed;
|
||||||
}
|
} else if (m_verticalVelocity < -m_maxFallSpeed) {
|
||||||
if (m_verticalVelocity < 0.0 && btFabs(m_verticalVelocity) > btFabs(m_fallSpeed)) {
|
m_verticalVelocity = -m_maxFallSpeed;
|
||||||
m_verticalVelocity = -btFabs(m_fallSpeed);
|
|
||||||
}
|
}
|
||||||
m_verticalOffset = m_verticalVelocity * dt;
|
m_verticalOffset = m_verticalVelocity * dt;
|
||||||
|
|
||||||
|
|
||||||
btTransform xform;
|
btTransform xform;
|
||||||
xform = m_ghostObject->getWorldTransform();
|
xform = m_ghostObject->getWorldTransform();
|
||||||
|
|
||||||
//printf("walkDirection(%f,%f,%f)\n", walkDirection[0], walkDirection[1], walkDirection[2]);
|
// the algorithm is as follows:
|
||||||
//printf("walkSpeed=%f\n", walkSpeed);
|
// (1) step the character up a little bit so that its forward step doesn't hit the floor
|
||||||
|
// (2) step the character forward
|
||||||
|
// (3) step the character down looking for new ledges, the original floor, or a floor one step below where we started
|
||||||
|
|
||||||
stepUp (collisionWorld);
|
stepUp(collisionWorld);
|
||||||
if (m_useWalkDirection) {
|
if (m_useWalkDirection) {
|
||||||
stepForwardAndStrafe(collisionWorld, m_walkDirection);
|
stepForward(collisionWorld, m_walkDirection);
|
||||||
} else {
|
} else {
|
||||||
//printf(" time: %f", m_velocityTimeInterval);
|
// compute substep and decrement total interval
|
||||||
// still have some time left for moving!
|
btScalar dtMoving = (dt < m_velocityTimeInterval) ? dt : m_velocityTimeInterval;
|
||||||
btScalar dtMoving =
|
|
||||||
(dt < m_velocityTimeInterval) ? dt : m_velocityTimeInterval;
|
|
||||||
m_velocityTimeInterval -= dt;
|
m_velocityTimeInterval -= dt;
|
||||||
|
|
||||||
// how far will we move while we are moving?
|
// stepForward substep
|
||||||
btVector3 move = m_walkDirection * dtMoving;
|
btVector3 move = m_walkDirection * dtMoving;
|
||||||
|
stepForward(collisionWorld, move);
|
||||||
//printf(" dtMoving: %f", dtMoving);
|
|
||||||
|
|
||||||
// okay, step
|
|
||||||
stepForwardAndStrafe(collisionWorld, move);
|
|
||||||
}
|
}
|
||||||
stepDown(collisionWorld, dt);
|
stepDown(collisionWorld, dt);
|
||||||
|
|
||||||
//printf("\n");
|
|
||||||
|
|
||||||
xform.setOrigin(m_currentPosition);
|
xform.setOrigin(m_currentPosition);
|
||||||
m_ghostObject->setWorldTransform(xform);
|
m_ghostObject->setWorldTransform(xform);
|
||||||
}
|
}
|
||||||
|
|
||||||
void CharacterController::setFallSpeed(btScalar fallSpeed) {
|
void CharacterController::setMaxFallSpeed(btScalar speed) {
|
||||||
m_fallSpeed = fallSpeed;
|
m_maxFallSpeed = speed;
|
||||||
}
|
}
|
||||||
|
|
||||||
void CharacterController::setJumpSpeed(btScalar jumpSpeed) {
|
void CharacterController::setJumpSpeed(btScalar jumpSpeed) {
|
||||||
|
@ -662,7 +676,7 @@ void CharacterController::jump() {
|
||||||
|
|
||||||
#if 0
|
#if 0
|
||||||
currently no jumping.
|
currently no jumping.
|
||||||
btTransform xform;
|
btTransform xform;
|
||||||
m_rigidBody->getMotionState()->getWorldTransform(xform);
|
m_rigidBody->getMotionState()->getWorldTransform(xform);
|
||||||
btVector3 up = xform.getBasis()[1];
|
btVector3 up = xform.getBasis()[1];
|
||||||
up.normalize();
|
up.normalize();
|
||||||
|
@ -689,7 +703,7 @@ btScalar CharacterController::getMaxSlope() const {
|
||||||
}
|
}
|
||||||
|
|
||||||
bool CharacterController::onGround() const {
|
bool CharacterController::onGround() const {
|
||||||
return m_verticalVelocity == 0.0 && m_verticalOffset == 0.0;
|
return m_enabled && m_verticalVelocity == 0.0 && m_verticalOffset == 0.0;
|
||||||
}
|
}
|
||||||
|
|
||||||
btVector3* CharacterController::getUpAxisDirections() {
|
btVector3* CharacterController::getUpAxisDirections() {
|
||||||
|
@ -704,3 +718,109 @@ void CharacterController::debugDraw(btIDebugDraw* debugDrawer) {
|
||||||
void CharacterController::setUpInterpolate(bool value) {
|
void CharacterController::setUpInterpolate(bool value) {
|
||||||
m_interpolateUp = value;
|
m_interpolateUp = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// protected
|
||||||
|
void CharacterController::createShapeAndGhost() {
|
||||||
|
// get new dimensions from avatar
|
||||||
|
m_avatarData->lockForRead();
|
||||||
|
AABox box = m_avatarData->getLocalAABox();
|
||||||
|
|
||||||
|
// create new ghost
|
||||||
|
m_ghostObject = new btPairCachingGhostObject();
|
||||||
|
m_ghostObject->setWorldTransform(btTransform(glmToBullet(m_avatarData->getOrientation()),
|
||||||
|
glmToBullet(m_avatarData->getPosition())));
|
||||||
|
m_avatarData->unlock();
|
||||||
|
|
||||||
|
const glm::vec3& diagonal = box.getScale();
|
||||||
|
m_radius = 0.5f * sqrtf(0.5f * (diagonal.x * diagonal.x + diagonal.z * diagonal.z));
|
||||||
|
m_halfHeight = 0.5f * diagonal.y - m_radius;
|
||||||
|
float MIN_HALF_HEIGHT = 0.1f;
|
||||||
|
if (m_halfHeight < MIN_HALF_HEIGHT) {
|
||||||
|
m_halfHeight = MIN_HALF_HEIGHT;
|
||||||
|
}
|
||||||
|
glm::vec3 offset = box.getCorner() + 0.5f * diagonal;
|
||||||
|
m_shapeLocalOffset = offset;
|
||||||
|
|
||||||
|
// stepHeight affects the heights of ledges that the character can ascend
|
||||||
|
// however the actual ledge height is some function of m_stepHeight
|
||||||
|
// due to character shape and this CharacterController algorithm
|
||||||
|
// (the function is approximately 2*m_stepHeight)
|
||||||
|
m_stepHeight = 0.1f * (m_radius + m_halfHeight) + 0.1f;
|
||||||
|
|
||||||
|
// create new shape
|
||||||
|
m_convexShape = new btCapsuleShape(m_radius, 2.0f * m_halfHeight);
|
||||||
|
m_ghostObject->setCollisionShape(m_convexShape);
|
||||||
|
m_ghostObject->setCollisionFlags(btCollisionObject::CF_CHARACTER_OBJECT);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool CharacterController::needsShapeUpdate() {
|
||||||
|
// get new dimensions from avatar
|
||||||
|
m_avatarData->lockForRead();
|
||||||
|
AABox box = m_avatarData->getLocalAABox();
|
||||||
|
m_avatarData->unlock();
|
||||||
|
|
||||||
|
const glm::vec3& diagonal = box.getScale();
|
||||||
|
float radius = 0.5f * sqrtf(0.5f * (diagonal.x * diagonal.x + diagonal.z * diagonal.z));
|
||||||
|
float halfHeight = 0.5f * diagonal.y - radius;
|
||||||
|
float MIN_HALF_HEIGHT = 0.1f;
|
||||||
|
if (halfHeight < MIN_HALF_HEIGHT) {
|
||||||
|
halfHeight = MIN_HALF_HEIGHT;
|
||||||
|
}
|
||||||
|
glm::vec3 offset = box.getCorner() + 0.5f * diagonal;
|
||||||
|
|
||||||
|
// compare dimensions (and offset)
|
||||||
|
float radiusDelta = glm::abs(radius - m_radius);
|
||||||
|
float heightDelta = glm::abs(halfHeight - m_halfHeight);
|
||||||
|
if (radiusDelta < FLT_EPSILON && heightDelta < FLT_EPSILON) {
|
||||||
|
// shape hasn't changed --> nothing to do
|
||||||
|
float offsetDelta = glm::distance(offset, m_shapeLocalOffset);
|
||||||
|
if (offsetDelta > FLT_EPSILON) {
|
||||||
|
// if only the offset changes then we can update it --> no need to rebuild shape
|
||||||
|
m_shapeLocalOffset = offset;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void CharacterController::updateShape() {
|
||||||
|
// DANGER: make sure this CharacterController and its GhostShape have been removed from
|
||||||
|
// the PhysicsEngine before calling this.
|
||||||
|
|
||||||
|
// delete shape and GhostObject
|
||||||
|
delete m_ghostObject;
|
||||||
|
m_ghostObject = NULL;
|
||||||
|
delete m_convexShape;
|
||||||
|
m_convexShape = NULL;
|
||||||
|
|
||||||
|
createShapeAndGhost();
|
||||||
|
}
|
||||||
|
|
||||||
|
void CharacterController::preSimulation(btScalar timeStep) {
|
||||||
|
m_avatarData->lockForRead();
|
||||||
|
|
||||||
|
// cache the "PhysicsEnabled" state of m_avatarData here
|
||||||
|
// and use the cached value for the rest of the simulation step
|
||||||
|
m_enabled = m_avatarData->isPhysicsEnabled();
|
||||||
|
|
||||||
|
glm::quat rotation = m_avatarData->getOrientation();
|
||||||
|
glm::vec3 position = m_avatarData->getPosition() + rotation * m_shapeLocalOffset;
|
||||||
|
m_ghostObject->setWorldTransform(btTransform(glmToBullet(rotation), glmToBullet(position)));
|
||||||
|
btVector3 walkVelocity = glmToBullet(m_avatarData->getVelocity());
|
||||||
|
setVelocityForTimeInterval(walkVelocity, timeStep);
|
||||||
|
|
||||||
|
m_avatarData->unlock();
|
||||||
|
}
|
||||||
|
|
||||||
|
void CharacterController::postSimulation() {
|
||||||
|
if (m_enabled) {
|
||||||
|
m_avatarData->lockForWrite();
|
||||||
|
const btTransform& avatarTransform = m_ghostObject->getWorldTransform();
|
||||||
|
glm::quat rotation = bulletToGLM(avatarTransform.getRotation());
|
||||||
|
glm::vec3 offset = rotation * m_shapeLocalOffset;
|
||||||
|
m_avatarData->setOrientation(rotation);
|
||||||
|
m_avatarData->setPosition(bulletToGLM(avatarTransform.getOrigin()) - offset);
|
||||||
|
m_avatarData->unlock();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
|
@ -19,6 +19,8 @@ subject to the following restrictions:
|
||||||
#ifndef hifi_CharacterController_h
|
#ifndef hifi_CharacterController_h
|
||||||
#define hifi_CharacterController_h
|
#define hifi_CharacterController_h
|
||||||
|
|
||||||
|
#include <AvatarData.h>
|
||||||
|
|
||||||
#include <btBulletDynamicsCommon.h>
|
#include <btBulletDynamicsCommon.h>
|
||||||
#include <BulletDynamics/Character/btCharacterControllerInterface.h>
|
#include <BulletDynamics/Character/btCharacterControllerInterface.h>
|
||||||
#include <BulletCollision/BroadphaseCollision/btCollisionAlgorithm.h>
|
#include <BulletCollision/BroadphaseCollision/btCollisionAlgorithm.h>
|
||||||
|
@ -39,14 +41,17 @@ ATTRIBUTE_ALIGNED16(class) CharacterController : public btCharacterControllerInt
|
||||||
{
|
{
|
||||||
protected:
|
protected:
|
||||||
|
|
||||||
|
AvatarData* m_avatarData = NULL;
|
||||||
|
btPairCachingGhostObject* m_ghostObject;
|
||||||
|
glm::vec3 m_shapeLocalOffset;
|
||||||
|
|
||||||
|
btConvexShape* m_convexShape;//is also in m_ghostObject, but it needs to be convex, so we store it here to avoid upcast
|
||||||
|
btScalar m_radius;
|
||||||
btScalar m_halfHeight;
|
btScalar m_halfHeight;
|
||||||
|
|
||||||
btPairCachingGhostObject* m_ghostObject;
|
|
||||||
btConvexShape* m_convexShape;//is also in m_ghostObject, but it needs to be convex, so we store it here to avoid upcast
|
|
||||||
|
|
||||||
btScalar m_verticalVelocity;
|
btScalar m_verticalVelocity;
|
||||||
btScalar m_verticalOffset;
|
btScalar m_verticalOffset; // fall distance from velocity this frame
|
||||||
btScalar m_fallSpeed;
|
btScalar m_maxFallSpeed;
|
||||||
btScalar m_jumpSpeed;
|
btScalar m_jumpSpeed;
|
||||||
btScalar m_maxJumpHeight;
|
btScalar m_maxJumpHeight;
|
||||||
btScalar m_maxSlopeRadians; // Slope angle that is set (used for returning the exact value)
|
btScalar m_maxSlopeRadians; // Slope angle that is set (used for returning the exact value)
|
||||||
|
@ -55,7 +60,7 @@ protected:
|
||||||
|
|
||||||
btScalar m_turnAngle;
|
btScalar m_turnAngle;
|
||||||
|
|
||||||
btScalar m_stepHeight;
|
btScalar m_stepHeight; // height of stepUp prior to stepForward
|
||||||
|
|
||||||
btScalar m_addedMargin;//@todo: remove this and fix the code
|
btScalar m_addedMargin;//@todo: remove this and fix the code
|
||||||
|
|
||||||
|
@ -65,18 +70,18 @@ protected:
|
||||||
|
|
||||||
//some internal variables
|
//some internal variables
|
||||||
btVector3 m_currentPosition;
|
btVector3 m_currentPosition;
|
||||||
btScalar m_currentStepOffset;
|
|
||||||
btVector3 m_targetPosition;
|
btVector3 m_targetPosition;
|
||||||
|
btScalar m_lastStepUp;
|
||||||
|
|
||||||
///keep track of the contact manifolds
|
///keep track of the contact manifolds
|
||||||
btManifoldArray m_manifoldArray;
|
btManifoldArray m_manifoldArray;
|
||||||
|
|
||||||
bool m_touchingContact;
|
bool m_touchingContact;
|
||||||
btVector3 m_touchingNormal;
|
btVector3 m_floorNormal; // points from object to character
|
||||||
|
|
||||||
bool m_wasOnGround;
|
bool m_enabled;
|
||||||
bool m_wasJumping;
|
bool m_wasOnGround;
|
||||||
bool m_useGhostObjectSweepTest;
|
bool m_wasJumping;
|
||||||
bool m_useWalkDirection;
|
bool m_useWalkDirection;
|
||||||
btScalar m_velocityTimeInterval;
|
btScalar m_velocityTimeInterval;
|
||||||
int m_upAxis;
|
int m_upAxis;
|
||||||
|
@ -93,17 +98,14 @@ protected:
|
||||||
bool recoverFromPenetration(btCollisionWorld* collisionWorld);
|
bool recoverFromPenetration(btCollisionWorld* collisionWorld);
|
||||||
void stepUp(btCollisionWorld* collisionWorld);
|
void stepUp(btCollisionWorld* collisionWorld);
|
||||||
void updateTargetPositionBasedOnCollision(const btVector3& hit_normal, btScalar tangentMag = btScalar(0.0), btScalar normalMag = btScalar(1.0));
|
void updateTargetPositionBasedOnCollision(const btVector3& hit_normal, btScalar tangentMag = btScalar(0.0), btScalar normalMag = btScalar(1.0));
|
||||||
void stepForwardAndStrafe(btCollisionWorld* collisionWorld, const btVector3& walkMove);
|
void stepForward(btCollisionWorld* collisionWorld, const btVector3& walkMove);
|
||||||
void stepDown(btCollisionWorld* collisionWorld, btScalar dt);
|
void stepDown(btCollisionWorld* collisionWorld, btScalar dt);
|
||||||
|
void createShapeAndGhost();
|
||||||
public:
|
public:
|
||||||
|
|
||||||
BT_DECLARE_ALIGNED_ALLOCATOR();
|
BT_DECLARE_ALIGNED_ALLOCATOR();
|
||||||
|
|
||||||
CharacterController(
|
CharacterController(AvatarData* avatarData);
|
||||||
btPairCachingGhostObject* ghostObject,
|
|
||||||
btConvexShape* convexShape,
|
|
||||||
btScalar stepHeight,
|
|
||||||
int upAxis = 1);
|
|
||||||
~CharacterController();
|
~CharacterController();
|
||||||
|
|
||||||
|
|
||||||
|
@ -145,7 +147,7 @@ public:
|
||||||
void preStep(btCollisionWorld* collisionWorld);
|
void preStep(btCollisionWorld* collisionWorld);
|
||||||
void playerStep(btCollisionWorld* collisionWorld, btScalar dt);
|
void playerStep(btCollisionWorld* collisionWorld, btScalar dt);
|
||||||
|
|
||||||
void setFallSpeed(btScalar fallSpeed);
|
void setMaxFallSpeed(btScalar speed);
|
||||||
void setJumpSpeed(btScalar jumpSpeed);
|
void setJumpSpeed(btScalar jumpSpeed);
|
||||||
void setMaxJumpHeight(btScalar maxJumpHeight);
|
void setMaxJumpHeight(btScalar maxJumpHeight);
|
||||||
bool canJump() const;
|
bool canJump() const;
|
||||||
|
@ -161,12 +163,15 @@ public:
|
||||||
btScalar getMaxSlope() const;
|
btScalar getMaxSlope() const;
|
||||||
|
|
||||||
btPairCachingGhostObject* getGhostObject();
|
btPairCachingGhostObject* getGhostObject();
|
||||||
void setUseGhostSweepTest(bool useGhostObjectSweepTest) {
|
|
||||||
m_useGhostObjectSweepTest = useGhostObjectSweepTest;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool onGround() const;
|
bool onGround() const;
|
||||||
void setUpInterpolate(bool value);
|
void setUpInterpolate(bool value);
|
||||||
|
|
||||||
|
bool needsShapeUpdate();
|
||||||
|
void updateShape();
|
||||||
|
|
||||||
|
void preSimulation(btScalar timeStep);
|
||||||
|
void postSimulation();
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // hifi_CharacterController_h
|
#endif // hifi_CharacterController_h
|
||||||
|
|
|
@ -24,8 +24,7 @@ uint32_t PhysicsEngine::getNumSubsteps() {
|
||||||
}
|
}
|
||||||
|
|
||||||
PhysicsEngine::PhysicsEngine(const glm::vec3& offset)
|
PhysicsEngine::PhysicsEngine(const glm::vec3& offset)
|
||||||
: _originOffset(offset),
|
: _originOffset(offset) {
|
||||||
_avatarShapeLocalOffset(0.0f) {
|
|
||||||
}
|
}
|
||||||
|
|
||||||
PhysicsEngine::~PhysicsEngine() {
|
PhysicsEngine::~PhysicsEngine() {
|
||||||
|
@ -279,9 +278,6 @@ void PhysicsEngine::init(EntityEditPacketSender* packetSender) {
|
||||||
}
|
}
|
||||||
|
|
||||||
void PhysicsEngine::stepSimulation() {
|
void PhysicsEngine::stepSimulation() {
|
||||||
// expect the engine to have an avatar (and hence: a character controller)
|
|
||||||
assert(_avatarData);
|
|
||||||
|
|
||||||
lock();
|
lock();
|
||||||
// NOTE: the grand order of operations is:
|
// NOTE: the grand order of operations is:
|
||||||
// (1) relay incoming changes
|
// (1) relay incoming changes
|
||||||
|
@ -298,19 +294,11 @@ void PhysicsEngine::stepSimulation() {
|
||||||
_clock.reset();
|
_clock.reset();
|
||||||
float timeStep = btMin(dt, MAX_TIMESTEP);
|
float timeStep = btMin(dt, MAX_TIMESTEP);
|
||||||
|
|
||||||
_avatarData->lockForRead();
|
|
||||||
if (_avatarData->isPhysicsEnabled()) {
|
|
||||||
// update character controller
|
|
||||||
glm::quat rotation = _avatarData->getOrientation();
|
|
||||||
glm::vec3 position = _avatarData->getPosition() + rotation * _avatarShapeLocalOffset;
|
|
||||||
_avatarGhostObject->setWorldTransform(btTransform(glmToBullet(rotation), glmToBullet(position)));
|
|
||||||
|
|
||||||
btVector3 walkVelocity = glmToBullet(_avatarData->getVelocity());
|
|
||||||
_characterController->setVelocityForTimeInterval(walkVelocity, timeStep);
|
|
||||||
}
|
|
||||||
_avatarData->unlock();
|
|
||||||
|
|
||||||
// This is step (2).
|
// This is step (2).
|
||||||
|
if (_characterController) {
|
||||||
|
_characterController->preSimulation(timeStep);
|
||||||
|
}
|
||||||
|
|
||||||
int numSubsteps = _dynamicsWorld->stepSimulation(timeStep, MAX_NUM_SUBSTEPS, PHYSICS_ENGINE_FIXED_SUBSTEP);
|
int numSubsteps = _dynamicsWorld->stepSimulation(timeStep, MAX_NUM_SUBSTEPS, PHYSICS_ENGINE_FIXED_SUBSTEP);
|
||||||
_numSubsteps += (uint32_t)numSubsteps;
|
_numSubsteps += (uint32_t)numSubsteps;
|
||||||
stepNonPhysicalKinematics(usecTimestampNow());
|
stepNonPhysicalKinematics(usecTimestampNow());
|
||||||
|
@ -326,21 +314,11 @@ void PhysicsEngine::stepSimulation() {
|
||||||
//
|
//
|
||||||
// TODO: untangle these lock sequences.
|
// TODO: untangle these lock sequences.
|
||||||
_entityTree->lockForWrite();
|
_entityTree->lockForWrite();
|
||||||
|
|
||||||
lock();
|
lock();
|
||||||
_dynamicsWorld->synchronizeMotionStates();
|
_dynamicsWorld->synchronizeMotionStates();
|
||||||
|
|
||||||
_avatarData->lockForRead();
|
if (_characterController) {
|
||||||
bool avatarHasPhysicsEnabled = _avatarData->isPhysicsEnabled();
|
_characterController->postSimulation();
|
||||||
_avatarData->unlock();
|
|
||||||
if (avatarHasPhysicsEnabled) {
|
|
||||||
const btTransform& avatarTransform = _avatarGhostObject->getWorldTransform();
|
|
||||||
glm::quat rotation = bulletToGLM(avatarTransform.getRotation());
|
|
||||||
glm::vec3 offset = rotation * _avatarShapeLocalOffset;
|
|
||||||
_avatarData->lockForWrite();
|
|
||||||
_avatarData->setOrientation(rotation);
|
|
||||||
_avatarData->setPosition(bulletToGLM(avatarTransform.getOrigin()) - offset);
|
|
||||||
_avatarData->unlock();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
unlock();
|
unlock();
|
||||||
|
@ -620,76 +598,35 @@ bool PhysicsEngine::updateObjectHard(btRigidBody* body, ObjectMotionState* motio
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
void PhysicsEngine::setAvatarData(AvatarData *avatarData) {
|
void PhysicsEngine::setAvatarData(AvatarData *avatarData) {
|
||||||
assert(avatarData); // don't pass NULL argument
|
if (_characterController) {
|
||||||
|
bool needsShapeUpdate = _characterController->needsShapeUpdate();
|
||||||
// compute capsule dimensions
|
if (needsShapeUpdate) {
|
||||||
AABox box = avatarData->getLocalAABox();
|
lock();
|
||||||
const glm::vec3& diagonal = box.getScale();
|
// remove old info
|
||||||
float radius = 0.5f * sqrtf(0.5f * (diagonal.x * diagonal.x + diagonal.z * diagonal.z));
|
_dynamicsWorld->removeCollisionObject(_characterController->getGhostObject());
|
||||||
float halfHeight = 0.5f * diagonal.y - radius;
|
_dynamicsWorld->removeAction(_characterController);
|
||||||
float MIN_HALF_HEIGHT = 0.1f;
|
// update shape
|
||||||
if (halfHeight < MIN_HALF_HEIGHT) {
|
_characterController->updateShape();
|
||||||
halfHeight = MIN_HALF_HEIGHT;
|
// insert new info
|
||||||
}
|
_dynamicsWorld->addCollisionObject(_characterController->getGhostObject(),
|
||||||
glm::vec3 offset = box.getCorner() + 0.5f * diagonal;
|
btBroadphaseProxy::CharacterFilter,
|
||||||
|
btBroadphaseProxy::StaticFilter | btBroadphaseProxy::DefaultFilter);
|
||||||
if (!_avatarData) {
|
_dynamicsWorld->addAction(_characterController);
|
||||||
// _avatarData is being initialized
|
_characterController->reset(_dynamicsWorld);
|
||||||
_avatarData = avatarData;
|
unlock();
|
||||||
} else {
|
|
||||||
// _avatarData is being updated
|
|
||||||
assert(_avatarData == avatarData);
|
|
||||||
|
|
||||||
// get old dimensions from shape
|
|
||||||
btCapsuleShape* capsule = static_cast<btCapsuleShape*>(_avatarGhostObject->getCollisionShape());
|
|
||||||
btScalar oldRadius = capsule->getRadius();
|
|
||||||
btScalar oldHalfHeight = capsule->getHalfHeight();
|
|
||||||
|
|
||||||
// compare dimensions (and offset)
|
|
||||||
float radiusDelta = glm::abs(radius - oldRadius);
|
|
||||||
float heightDelta = glm::abs(halfHeight - oldHalfHeight);
|
|
||||||
if (radiusDelta < FLT_EPSILON && heightDelta < FLT_EPSILON) {
|
|
||||||
// shape hasn't changed --> nothing to do
|
|
||||||
float offsetDelta = glm::distance(offset, _avatarShapeLocalOffset);
|
|
||||||
if (offsetDelta > FLT_EPSILON) {
|
|
||||||
// if only the offset changes then we can update it --> no need to rebuild shape
|
|
||||||
_avatarShapeLocalOffset = offset;
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
// delete old controller and friends
|
// initialize _characterController
|
||||||
_dynamicsWorld->removeCollisionObject(_avatarGhostObject);
|
assert(avatarData); // don't pass NULL argument
|
||||||
_dynamicsWorld->removeAction(_characterController);
|
lock();
|
||||||
delete _characterController;
|
_characterController = new CharacterController(avatarData);
|
||||||
_characterController = NULL;
|
_dynamicsWorld->addCollisionObject(_characterController->getGhostObject(),
|
||||||
delete _avatarGhostObject;
|
btBroadphaseProxy::CharacterFilter,
|
||||||
_avatarGhostObject = NULL;
|
btBroadphaseProxy::StaticFilter | btBroadphaseProxy::DefaultFilter);
|
||||||
delete capsule;
|
_dynamicsWorld->addAction(_characterController);
|
||||||
|
_characterController->reset(_dynamicsWorld);
|
||||||
|
unlock();
|
||||||
}
|
}
|
||||||
|
|
||||||
// set offset
|
|
||||||
_avatarShapeLocalOffset = offset;
|
|
||||||
|
|
||||||
// build ghost, shape, and controller
|
|
||||||
_avatarGhostObject = new btPairCachingGhostObject();
|
|
||||||
_avatarGhostObject->setWorldTransform(btTransform(glmToBullet(_avatarData->getOrientation()),
|
|
||||||
glmToBullet(_avatarData->getPosition())));
|
|
||||||
// ?TODO: use ShapeManager to get avatar's shape?
|
|
||||||
btCapsuleShape* capsule = new btCapsuleShape(radius, 2.0f * halfHeight);
|
|
||||||
|
|
||||||
_avatarGhostObject->setCollisionShape(capsule);
|
|
||||||
_avatarGhostObject->setCollisionFlags(btCollisionObject::CF_CHARACTER_OBJECT);
|
|
||||||
|
|
||||||
const float MIN_STEP_HEIGHT = 0.35f;
|
|
||||||
btScalar stepHeight = glm::max(MIN_STEP_HEIGHT, radius + 0.5f * halfHeight);
|
|
||||||
_characterController = new CharacterController(_avatarGhostObject, capsule, stepHeight);
|
|
||||||
|
|
||||||
_dynamicsWorld->addCollisionObject(_avatarGhostObject, btBroadphaseProxy::CharacterFilter,
|
|
||||||
btBroadphaseProxy::StaticFilter | btBroadphaseProxy::DefaultFilter);
|
|
||||||
_dynamicsWorld->addAction(_characterController);
|
|
||||||
_characterController->reset(_dynamicsWorld);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -123,9 +123,6 @@ private:
|
||||||
|
|
||||||
/// character collisions
|
/// character collisions
|
||||||
CharacterController* _characterController = NULL;
|
CharacterController* _characterController = NULL;
|
||||||
class btPairCachingGhostObject* _avatarGhostObject = NULL;
|
|
||||||
AvatarData* _avatarData = NULL;
|
|
||||||
glm::vec3 _avatarShapeLocalOffset;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // hifi_PhysicsEngine_h
|
#endif // hifi_PhysicsEngine_h
|
||||||
|
|
|
@ -725,21 +725,21 @@ bool Model::renderCore(float alpha, RenderMode mode, RenderArgs* args) {
|
||||||
const float DEFAULT_ALPHA_THRESHOLD = 0.5f;
|
const float DEFAULT_ALPHA_THRESHOLD = 0.5f;
|
||||||
|
|
||||||
|
|
||||||
//renderMeshes(RenderMode mode, bool translucent, float alphaThreshold, bool hasTangents, bool hasSpecular, book isSkinned, args);
|
//renderMeshes(batch, mode, translucent, alphaThreshold, hasTangents, hasSpecular, isSkinned, args, forceRenderMeshes);
|
||||||
int opaqueMeshPartsRendered = 0;
|
int opaqueMeshPartsRendered = 0;
|
||||||
opaqueMeshPartsRendered += renderMeshes(batch, mode, false, DEFAULT_ALPHA_THRESHOLD, false, false, false, false, args);
|
opaqueMeshPartsRendered += renderMeshes(batch, mode, false, DEFAULT_ALPHA_THRESHOLD, false, false, false, false, args, true);
|
||||||
opaqueMeshPartsRendered += renderMeshes(batch, mode, false, DEFAULT_ALPHA_THRESHOLD, false, false, false, true, args);
|
opaqueMeshPartsRendered += renderMeshes(batch, mode, false, DEFAULT_ALPHA_THRESHOLD, false, false, false, true, args, true);
|
||||||
opaqueMeshPartsRendered += renderMeshes(batch, mode, false, DEFAULT_ALPHA_THRESHOLD, false, false, true, false, args);
|
opaqueMeshPartsRendered += renderMeshes(batch, mode, false, DEFAULT_ALPHA_THRESHOLD, false, false, true, false, args, true);
|
||||||
opaqueMeshPartsRendered += renderMeshes(batch, mode, false, DEFAULT_ALPHA_THRESHOLD, false, false, true, true, args);
|
opaqueMeshPartsRendered += renderMeshes(batch, mode, false, DEFAULT_ALPHA_THRESHOLD, false, false, true, true, args, true);
|
||||||
opaqueMeshPartsRendered += renderMeshes(batch, mode, false, DEFAULT_ALPHA_THRESHOLD, false, true, false, false, args);
|
opaqueMeshPartsRendered += renderMeshes(batch, mode, false, DEFAULT_ALPHA_THRESHOLD, false, true, false, false, args, true);
|
||||||
opaqueMeshPartsRendered += renderMeshes(batch, mode, false, DEFAULT_ALPHA_THRESHOLD, false, true, false, true, args);
|
opaqueMeshPartsRendered += renderMeshes(batch, mode, false, DEFAULT_ALPHA_THRESHOLD, false, true, false, true, args, true);
|
||||||
opaqueMeshPartsRendered += renderMeshes(batch, mode, false, DEFAULT_ALPHA_THRESHOLD, false, true, true, false, args);
|
opaqueMeshPartsRendered += renderMeshes(batch, mode, false, DEFAULT_ALPHA_THRESHOLD, false, true, true, false, args, true);
|
||||||
opaqueMeshPartsRendered += renderMeshes(batch, mode, false, DEFAULT_ALPHA_THRESHOLD, false, true, true, true, args);
|
opaqueMeshPartsRendered += renderMeshes(batch, mode, false, DEFAULT_ALPHA_THRESHOLD, false, true, true, true, args, true);
|
||||||
|
|
||||||
opaqueMeshPartsRendered += renderMeshes(batch, mode, false, DEFAULT_ALPHA_THRESHOLD, true, false, false, false, args);
|
opaqueMeshPartsRendered += renderMeshes(batch, mode, false, DEFAULT_ALPHA_THRESHOLD, true, false, false, false, args, true);
|
||||||
opaqueMeshPartsRendered += renderMeshes(batch, mode, false, DEFAULT_ALPHA_THRESHOLD, true, false, true, false, args);
|
opaqueMeshPartsRendered += renderMeshes(batch, mode, false, DEFAULT_ALPHA_THRESHOLD, true, false, true, false, args, true);
|
||||||
opaqueMeshPartsRendered += renderMeshes(batch, mode, false, DEFAULT_ALPHA_THRESHOLD, true, true, false, false, args);
|
opaqueMeshPartsRendered += renderMeshes(batch, mode, false, DEFAULT_ALPHA_THRESHOLD, true, true, false, false, args, true);
|
||||||
opaqueMeshPartsRendered += renderMeshes(batch, mode, false, DEFAULT_ALPHA_THRESHOLD, true, true, true, false, args);
|
opaqueMeshPartsRendered += renderMeshes(batch, mode, false, DEFAULT_ALPHA_THRESHOLD, true, true, true, false, args, true);
|
||||||
|
|
||||||
// render translucent meshes afterwards
|
// render translucent meshes afterwards
|
||||||
//DependencyManager::get<TextureCache>()->setPrimaryDrawBuffers(false, true, true);
|
//DependencyManager::get<TextureCache>()->setPrimaryDrawBuffers(false, true, true);
|
||||||
|
@ -753,14 +753,14 @@ bool Model::renderCore(float alpha, RenderMode mode, RenderArgs* args) {
|
||||||
|
|
||||||
int translucentMeshPartsRendered = 0;
|
int translucentMeshPartsRendered = 0;
|
||||||
const float MOSTLY_OPAQUE_THRESHOLD = 0.75f;
|
const float MOSTLY_OPAQUE_THRESHOLD = 0.75f;
|
||||||
translucentMeshPartsRendered += renderMeshes(batch, mode, true, MOSTLY_OPAQUE_THRESHOLD, false, false, false, false, args);
|
translucentMeshPartsRendered += renderMeshes(batch, mode, true, MOSTLY_OPAQUE_THRESHOLD, false, false, false, false, args, true);
|
||||||
translucentMeshPartsRendered += renderMeshes(batch, mode, true, MOSTLY_OPAQUE_THRESHOLD, false, false, false, true, args);
|
translucentMeshPartsRendered += renderMeshes(batch, mode, true, MOSTLY_OPAQUE_THRESHOLD, false, false, false, true, args, true);
|
||||||
translucentMeshPartsRendered += renderMeshes(batch, mode, true, MOSTLY_OPAQUE_THRESHOLD, false, false, true, false, args);
|
translucentMeshPartsRendered += renderMeshes(batch, mode, true, MOSTLY_OPAQUE_THRESHOLD, false, false, true, false, args, true);
|
||||||
translucentMeshPartsRendered += renderMeshes(batch, mode, true, MOSTLY_OPAQUE_THRESHOLD, false, false, true, true, args);
|
translucentMeshPartsRendered += renderMeshes(batch, mode, true, MOSTLY_OPAQUE_THRESHOLD, false, false, true, true, args, true);
|
||||||
translucentMeshPartsRendered += renderMeshes(batch, mode, true, MOSTLY_OPAQUE_THRESHOLD, false, true, false, false, args);
|
translucentMeshPartsRendered += renderMeshes(batch, mode, true, MOSTLY_OPAQUE_THRESHOLD, false, true, false, false, args, true);
|
||||||
translucentMeshPartsRendered += renderMeshes(batch, mode, true, MOSTLY_OPAQUE_THRESHOLD, false, true, false, true, args);
|
translucentMeshPartsRendered += renderMeshes(batch, mode, true, MOSTLY_OPAQUE_THRESHOLD, false, true, false, true, args, true);
|
||||||
translucentMeshPartsRendered += renderMeshes(batch, mode, true, MOSTLY_OPAQUE_THRESHOLD, false, true, true, false, args);
|
translucentMeshPartsRendered += renderMeshes(batch, mode, true, MOSTLY_OPAQUE_THRESHOLD, false, true, true, false, args, true);
|
||||||
translucentMeshPartsRendered += renderMeshes(batch, mode, true, MOSTLY_OPAQUE_THRESHOLD, false, true, true, true, args);
|
translucentMeshPartsRendered += renderMeshes(batch, mode, true, MOSTLY_OPAQUE_THRESHOLD, false, true, true, true, args, true);
|
||||||
|
|
||||||
GLBATCH(glDisable)(GL_ALPHA_TEST);
|
GLBATCH(glDisable)(GL_ALPHA_TEST);
|
||||||
GLBATCH(glEnable)(GL_BLEND);
|
GLBATCH(glEnable)(GL_BLEND);
|
||||||
|
@ -777,14 +777,14 @@ bool Model::renderCore(float alpha, RenderMode mode, RenderArgs* args) {
|
||||||
|
|
||||||
if (mode == DEFAULT_RENDER_MODE || mode == DIFFUSE_RENDER_MODE) {
|
if (mode == DEFAULT_RENDER_MODE || mode == DIFFUSE_RENDER_MODE) {
|
||||||
const float MOSTLY_TRANSPARENT_THRESHOLD = 0.0f;
|
const float MOSTLY_TRANSPARENT_THRESHOLD = 0.0f;
|
||||||
translucentMeshPartsRendered += renderMeshes(batch, mode, true, MOSTLY_TRANSPARENT_THRESHOLD, false, false, false, false, args);
|
translucentMeshPartsRendered += renderMeshes(batch, mode, true, MOSTLY_TRANSPARENT_THRESHOLD, false, false, false, false, args, true);
|
||||||
translucentMeshPartsRendered += renderMeshes(batch, mode, true, MOSTLY_TRANSPARENT_THRESHOLD, false, false, false, true, args);
|
translucentMeshPartsRendered += renderMeshes(batch, mode, true, MOSTLY_TRANSPARENT_THRESHOLD, false, false, false, true, args, true);
|
||||||
translucentMeshPartsRendered += renderMeshes(batch, mode, true, MOSTLY_TRANSPARENT_THRESHOLD, false, false, true, false, args);
|
translucentMeshPartsRendered += renderMeshes(batch, mode, true, MOSTLY_TRANSPARENT_THRESHOLD, false, false, true, false, args, true);
|
||||||
translucentMeshPartsRendered += renderMeshes(batch, mode, true, MOSTLY_TRANSPARENT_THRESHOLD, false, false, true, true, args);
|
translucentMeshPartsRendered += renderMeshes(batch, mode, true, MOSTLY_TRANSPARENT_THRESHOLD, false, false, true, true, args, true);
|
||||||
translucentMeshPartsRendered += renderMeshes(batch, mode, true, MOSTLY_TRANSPARENT_THRESHOLD, false, true, false, false, args);
|
translucentMeshPartsRendered += renderMeshes(batch, mode, true, MOSTLY_TRANSPARENT_THRESHOLD, false, true, false, false, args, true);
|
||||||
translucentMeshPartsRendered += renderMeshes(batch, mode, true, MOSTLY_TRANSPARENT_THRESHOLD, false, true, false, true, args);
|
translucentMeshPartsRendered += renderMeshes(batch, mode, true, MOSTLY_TRANSPARENT_THRESHOLD, false, true, false, true, args, true);
|
||||||
translucentMeshPartsRendered += renderMeshes(batch, mode, true, MOSTLY_TRANSPARENT_THRESHOLD, false, true, true, false, args);
|
translucentMeshPartsRendered += renderMeshes(batch, mode, true, MOSTLY_TRANSPARENT_THRESHOLD, false, true, true, false, args, true);
|
||||||
translucentMeshPartsRendered += renderMeshes(batch, mode, true, MOSTLY_TRANSPARENT_THRESHOLD, false, true, true, true, args);
|
translucentMeshPartsRendered += renderMeshes(batch, mode, true, MOSTLY_TRANSPARENT_THRESHOLD, false, true, true, true, args, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
GLBATCH(glDepthMask)(true);
|
GLBATCH(glDepthMask)(true);
|
||||||
|
@ -843,9 +843,66 @@ bool Model::renderCore(float alpha, RenderMode mode, RenderArgs* args) {
|
||||||
args->_opaqueMeshPartsRendered = opaqueMeshPartsRendered;
|
args->_opaqueMeshPartsRendered = opaqueMeshPartsRendered;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#ifdef WANT_DEBUG_MESHBOXES
|
||||||
|
renderDebugMeshBoxes();
|
||||||
|
#endif
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Model::renderDebugMeshBoxes() {
|
||||||
|
int colorNdx = 0;
|
||||||
|
foreach(AABox box, _calculatedMeshBoxes) {
|
||||||
|
if (_debugMeshBoxesID == GeometryCache::UNKNOWN_ID) {
|
||||||
|
_debugMeshBoxesID = DependencyManager::get<GeometryCache>()->allocateID();
|
||||||
|
}
|
||||||
|
QVector<glm::vec3> points;
|
||||||
|
|
||||||
|
glm::vec3 brn = box.getCorner();
|
||||||
|
glm::vec3 bln = brn + glm::vec3(box.getDimensions().x, 0, 0);
|
||||||
|
glm::vec3 brf = brn + glm::vec3(0, 0, box.getDimensions().z);
|
||||||
|
glm::vec3 blf = brn + glm::vec3(box.getDimensions().x, 0, box.getDimensions().z);
|
||||||
|
|
||||||
|
glm::vec3 trn = brn + glm::vec3(0, box.getDimensions().y, 0);
|
||||||
|
glm::vec3 tln = bln + glm::vec3(0, box.getDimensions().y, 0);
|
||||||
|
glm::vec3 trf = brf + glm::vec3(0, box.getDimensions().y, 0);
|
||||||
|
glm::vec3 tlf = blf + glm::vec3(0, box.getDimensions().y, 0);
|
||||||
|
|
||||||
|
points << brn << bln;
|
||||||
|
points << brf << blf;
|
||||||
|
points << brn << brf;
|
||||||
|
points << bln << blf;
|
||||||
|
|
||||||
|
points << trn << tln;
|
||||||
|
points << trf << tlf;
|
||||||
|
points << trn << trf;
|
||||||
|
points << tln << tlf;
|
||||||
|
|
||||||
|
points << brn << trn;
|
||||||
|
points << brf << trf;
|
||||||
|
points << bln << tln;
|
||||||
|
points << blf << tlf;
|
||||||
|
|
||||||
|
glm::vec4 color[] = {
|
||||||
|
{ 1.0f, 0.0f, 0.0f, 1.0f }, // red
|
||||||
|
{ 0.0f, 1.0f, 0.0f, 1.0f }, // green
|
||||||
|
{ 0.0f, 0.0f, 1.0f, 1.0f }, // blue
|
||||||
|
{ 1.0f, 0.0f, 1.0f, 1.0f }, // purple
|
||||||
|
{ 1.0f, 1.0f, 0.0f, 1.0f }, // yellow
|
||||||
|
{ 0.0f, 1.0f, 1.0f, 1.0f }, // cyan
|
||||||
|
{ 1.0f, 1.0f, 1.0f, 1.0f }, // white
|
||||||
|
{ 0.0f, 0.5f, 0.0f, 1.0f },
|
||||||
|
{ 0.0f, 0.0f, 0.5f, 1.0f },
|
||||||
|
{ 0.5f, 0.0f, 0.5f, 1.0f },
|
||||||
|
{ 0.5f, 0.5f, 0.0f, 1.0f },
|
||||||
|
{ 0.0f, 0.5f, 0.5f, 1.0f } };
|
||||||
|
|
||||||
|
DependencyManager::get<GeometryCache>()->updateVertices(_debugMeshBoxesID, points, color[colorNdx]);
|
||||||
|
DependencyManager::get<GeometryCache>()->renderVertices(gpu::LINES, _debugMeshBoxesID);
|
||||||
|
colorNdx++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Extents Model::getBindExtents() const {
|
Extents Model::getBindExtents() const {
|
||||||
if (!isActive()) {
|
if (!isActive()) {
|
||||||
return Extents();
|
return Extents();
|
||||||
|
@ -1306,8 +1363,11 @@ void Model::updateJointState(int index) {
|
||||||
glm::mat4 parentTransform = glm::scale(_scale) * glm::translate(_offset) * geometry.offset;
|
glm::mat4 parentTransform = glm::scale(_scale) * glm::translate(_offset) * geometry.offset;
|
||||||
state.computeTransform(parentTransform);
|
state.computeTransform(parentTransform);
|
||||||
} else {
|
} else {
|
||||||
const JointState& parentState = _jointStates.at(parentIndex);
|
// guard against out-of-bounds access to _jointStates
|
||||||
state.computeTransform(parentState.getTransform(), parentState.getTransformChanged());
|
if (joint.parentIndex >= 0 && joint.parentIndex < _jointStates.size()) {
|
||||||
|
const JointState& parentState = _jointStates.at(parentIndex);
|
||||||
|
state.computeTransform(parentState.getTransform(), parentState.getTransformChanged());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2299,7 +2359,8 @@ int Model::renderMeshesForModelsInScene(gpu::Batch& batch, RenderMode mode, bool
|
||||||
}
|
}
|
||||||
|
|
||||||
int Model::renderMeshes(gpu::Batch& batch, RenderMode mode, bool translucent, float alphaThreshold,
|
int Model::renderMeshes(gpu::Batch& batch, RenderMode mode, bool translucent, float alphaThreshold,
|
||||||
bool hasLightmap, bool hasTangents, bool hasSpecular, bool isSkinned, RenderArgs* args) {
|
bool hasLightmap, bool hasTangents, bool hasSpecular, bool isSkinned, RenderArgs* args,
|
||||||
|
bool forceRenderSomeMeshes) {
|
||||||
|
|
||||||
PROFILE_RANGE(__FUNCTION__);
|
PROFILE_RANGE(__FUNCTION__);
|
||||||
int meshPartsRendered = 0;
|
int meshPartsRendered = 0;
|
||||||
|
@ -2319,8 +2380,10 @@ int Model::renderMeshes(gpu::Batch& batch, RenderMode mode, bool translucent, fl
|
||||||
|
|
||||||
Locations* locations;
|
Locations* locations;
|
||||||
SkinLocations* skinLocations;
|
SkinLocations* skinLocations;
|
||||||
pickPrograms(batch, mode, translucent, alphaThreshold, hasLightmap, hasTangents, hasSpecular, isSkinned, args, locations, skinLocations);
|
pickPrograms(batch, mode, translucent, alphaThreshold, hasLightmap, hasTangents, hasSpecular, isSkinned,
|
||||||
meshPartsRendered = renderMeshesFromList(list, batch, mode, translucent, alphaThreshold, args, locations, skinLocations);
|
args, locations, skinLocations);
|
||||||
|
meshPartsRendered = renderMeshesFromList(list, batch, mode, translucent, alphaThreshold,
|
||||||
|
args, locations, skinLocations, forceRenderSomeMeshes);
|
||||||
GLBATCH(glUseProgram)(0);
|
GLBATCH(glUseProgram)(0);
|
||||||
|
|
||||||
return meshPartsRendered;
|
return meshPartsRendered;
|
||||||
|
@ -2328,7 +2391,7 @@ int Model::renderMeshes(gpu::Batch& batch, RenderMode mode, bool translucent, fl
|
||||||
|
|
||||||
|
|
||||||
int Model::renderMeshesFromList(QVector<int>& list, gpu::Batch& batch, RenderMode mode, bool translucent, float alphaThreshold, RenderArgs* args,
|
int Model::renderMeshesFromList(QVector<int>& list, gpu::Batch& batch, RenderMode mode, bool translucent, float alphaThreshold, RenderArgs* args,
|
||||||
Locations* locations, SkinLocations* skinLocations) {
|
Locations* locations, SkinLocations* skinLocations, bool forceRenderSomeMeshes) {
|
||||||
PROFILE_RANGE(__FUNCTION__);
|
PROFILE_RANGE(__FUNCTION__);
|
||||||
|
|
||||||
auto textureCache = DependencyManager::get<TextureCache>();
|
auto textureCache = DependencyManager::get<TextureCache>();
|
||||||
|
@ -2364,11 +2427,21 @@ int Model::renderMeshesFromList(QVector<int>& list, gpu::Batch& batch, RenderMod
|
||||||
// if we got here, then check to see if this mesh is in view
|
// if we got here, then check to see if this mesh is in view
|
||||||
if (args) {
|
if (args) {
|
||||||
bool shouldRender = true;
|
bool shouldRender = true;
|
||||||
|
bool forceRender = false;
|
||||||
args->_meshesConsidered++;
|
args->_meshesConsidered++;
|
||||||
|
|
||||||
if (args->_viewFrustum) {
|
if (args->_viewFrustum) {
|
||||||
shouldRender = args->_viewFrustum->boxInFrustum(_calculatedMeshBoxes.at(i)) != ViewFrustum::OUTSIDE;
|
|
||||||
if (shouldRender) {
|
// NOTE: This is a hack to address the fact that for avatar meshes, the _calculatedMeshBoxes can be wrong
|
||||||
|
// for some meshes. Those meshes where the mesh's modelTransform is the identity matrix, and will have
|
||||||
|
// incorrectly calculated mesh boxes. In this case, we will ignore the box and assume it's visible.
|
||||||
|
if (forceRenderSomeMeshes && (geometry.meshes.at(i).modelTransform == glm::mat4())) {
|
||||||
|
forceRender = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
shouldRender = forceRender || args->_viewFrustum->boxInFrustum(_calculatedMeshBoxes.at(i)) != ViewFrustum::OUTSIDE;
|
||||||
|
|
||||||
|
if (shouldRender && !forceRender) {
|
||||||
float distance = args->_viewFrustum->distanceToCamera(_calculatedMeshBoxes.at(i).calcCenter());
|
float distance = args->_viewFrustum->distanceToCamera(_calculatedMeshBoxes.at(i).calcCenter());
|
||||||
shouldRender = !_viewState ? false : _viewState->shouldRenderMesh(_calculatedMeshBoxes.at(i).getLargestDimension(),
|
shouldRender = !_viewState ? false : _viewState->shouldRenderMesh(_calculatedMeshBoxes.at(i).getLargestDimension(),
|
||||||
distance);
|
distance);
|
||||||
|
|
|
@ -444,6 +444,9 @@ private:
|
||||||
QVector<int> _meshesOpaqueLightmapTangentsSpecular;
|
QVector<int> _meshesOpaqueLightmapTangentsSpecular;
|
||||||
QVector<int> _meshesOpaqueLightmapSpecular;
|
QVector<int> _meshesOpaqueLightmapSpecular;
|
||||||
|
|
||||||
|
// debug rendering support
|
||||||
|
void renderDebugMeshBoxes();
|
||||||
|
int _debugMeshBoxesID = GeometryCache::UNKNOWN_ID;
|
||||||
|
|
||||||
// Scene rendering support
|
// Scene rendering support
|
||||||
static QVector<Model*> _modelsInScene;
|
static QVector<Model*> _modelsInScene;
|
||||||
|
@ -456,12 +459,15 @@ private:
|
||||||
void renderSetup(RenderArgs* args);
|
void renderSetup(RenderArgs* args);
|
||||||
bool renderCore(float alpha, RenderMode mode, RenderArgs* args);
|
bool renderCore(float alpha, RenderMode mode, RenderArgs* args);
|
||||||
int renderMeshes(gpu::Batch& batch, RenderMode mode, bool translucent, float alphaThreshold,
|
int renderMeshes(gpu::Batch& batch, RenderMode mode, bool translucent, float alphaThreshold,
|
||||||
bool hasLightmap, bool hasTangents, bool hasSpecular, bool isSkinned, RenderArgs* args = NULL);
|
bool hasLightmap, bool hasTangents, bool hasSpecular, bool isSkinned, RenderArgs* args = NULL,
|
||||||
|
bool forceRenderSomeMeshes = false);
|
||||||
|
|
||||||
void setupBatchTransform(gpu::Batch& batch);
|
void setupBatchTransform(gpu::Batch& batch);
|
||||||
QVector<int>* pickMeshList(bool translucent, float alphaThreshold, bool hasLightmap, bool hasTangents, bool hasSpecular, bool isSkinned);
|
QVector<int>* pickMeshList(bool translucent, float alphaThreshold, bool hasLightmap, bool hasTangents, bool hasSpecular, bool isSkinned);
|
||||||
|
|
||||||
int renderMeshesFromList(QVector<int>& list, gpu::Batch& batch, RenderMode mode, bool translucent, float alphaThreshold,
|
int renderMeshesFromList(QVector<int>& list, gpu::Batch& batch, RenderMode mode, bool translucent, float alphaThreshold,
|
||||||
RenderArgs* args, Locations* locations, SkinLocations* skinLocations);
|
RenderArgs* args, Locations* locations, SkinLocations* skinLocations,
|
||||||
|
bool forceRenderSomeMeshes = false);
|
||||||
|
|
||||||
static void pickPrograms(gpu::Batch& batch, RenderMode mode, bool translucent, float alphaThreshold,
|
static void pickPrograms(gpu::Batch& batch, RenderMode mode, bool translucent, float alphaThreshold,
|
||||||
bool hasLightmap, bool hasTangents, bool hasSpecular, bool isSkinned, RenderArgs* args,
|
bool hasLightmap, bool hasTangents, bool hasSpecular, bool isSkinned, RenderArgs* args,
|
||||||
|
|
Loading…
Reference in a new issue