diff --git a/examples/defaultScripts.js b/examples/defaultScripts.js
index f52d6be87e..05ffb0bd3f 100644
--- a/examples/defaultScripts.js
+++ b/examples/defaultScripts.js
@@ -18,4 +18,3 @@ Script.load("lobby.js");
Script.load("notifications.js");
Script.load("look.js");
Script.load("users.js");
-Script.load("utilities/LODWarning.js");
diff --git a/examples/example/entities/makeHouses.js b/examples/example/entities/makeHouses.js
new file mode 100644
index 0000000000..37bc1d5a8e
--- /dev/null
+++ b/examples/example/entities/makeHouses.js
@@ -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);
+})();
diff --git a/examples/hmdDefaults.js b/examples/hmdDefaults.js
index 1e96d41713..0096b11777 100644
--- a/examples/hmdDefaults.js
+++ b/examples/hmdDefaults.js
@@ -13,5 +13,4 @@ Script.load("progress.js");
Script.load("lobby.js");
Script.load("notifications.js");
Script.load("controllers/oculus/goTo.js");
-Script.load("utilities/LODWarning.js");
//Script.load("scripts.js"); // Not created yet
diff --git a/examples/notifications.js b/examples/notifications.js
index 0c2a06c878..7e56c1b50d 100644
--- a/examples/notifications.js
+++ b/examples/notifications.js
@@ -43,7 +43,6 @@
// 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.
-
// To add a keypress driven notification:
//
// 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 PLAY_NOTIFICATION_SOUNDS_SETTING = "play_notification_sounds";
var PLAY_NOTIFICATION_SOUNDS_TYPE_SETTING_PRE = "play_notification_sounds_type_";
+var lodTextID = false;
var NotificationType = {
UNKNOWN: 0,
MUTE_TOGGLE: 1,
SNAPSHOT: 2,
WINDOW_RESIZE: 3,
+ LOD_WARNING: 4,
properties: [
{ text: "Mute Toggle" },
{ text: "Snapshot" },
- { text: "Window Resize" }
+ { text: "Window Resize" },
+ { text: "Level of Detail" }
],
getTypeFromMenuItem: function(menuItemName) {
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
function dismiss(firstNoteOut, firstButOut, firstOut) {
+ if (firstNoteOut == lodTextID) {
+ lodTextID = false;
+ }
+
Overlays.deleteOverlay(firstNoteOut);
Overlays.deleteOverlay(firstButOut);
notifications.splice(firstOut, 1);
@@ -261,7 +267,8 @@ function notify(notice, button, height) {
height: noticeHeight
});
} else {
- notifications.push((Overlays.addOverlay("text", notice)));
+ var notificationText = Overlays.addOverlay("text", notice);
+ notifications.push((notificationText));
buttons.push((Overlays.addOverlay("image", button)));
}
@@ -272,6 +279,7 @@ function notify(notice, button, height) {
last = notifications.length - 1;
createArrays(notifications[last], buttons[last], times[last], heights[last], myAlpha[last]);
fadeIn(notifications[last], buttons[last]);
+ return notificationText;
}
// This function creates and sizes the overlays
@@ -331,11 +339,15 @@ function createNotification(text, notificationType) {
randomSounds.playRandom();
}
- notify(noticeProperties, buttonProperties, height);
+ return notify(noticeProperties, buttonProperties, height);
}
function deleteNotification(index) {
- Overlays.deleteOverlay(notifications[index]);
+ var notificationTextID = notifications[index];
+ if (notificationTextID == lodTextID) {
+ lodTextID = false;
+ }
+ Overlays.deleteOverlay(notificationTextID);
Overlays.deleteOverlay(buttons[index]);
notifications.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);
Controller.keyPressEvent.connect(keyPressEvent);
Controller.mousePressEvent.connect(mousePressEvent);
diff --git a/examples/utilities/LODWarning.js b/examples/utilities/LODWarning.js
deleted file mode 100644
index 644d98ebf4..0000000000
--- a/examples/utilities/LODWarning.js
+++ /dev/null
@@ -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);
-});
\ No newline at end of file
diff --git a/interface/external/sixense/readme.txt b/interface/external/sixense/readme.txt
index 29d1bbc2f9..a4790caa5e 100644
--- a/interface/external/sixense/readme.txt
+++ b/interface/external/sixense/readme.txt
@@ -2,7 +2,7 @@
Instructions for adding the Sixense driver to Interface
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).
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.
diff --git a/interface/resources/html/edit-commands.html b/interface/resources/html/edit-commands.html
new file mode 100644
index 0000000000..65b985fb6a
--- /dev/null
+++ b/interface/resources/html/edit-commands.html
@@ -0,0 +1,2058 @@
+
+
+
+
+
+
+
Edit Entity Help
+
+
diff --git a/interface/resources/html/edit-entities-commands.html b/interface/resources/html/edit-entities-commands.html
deleted file mode 100644
index afa662f089..0000000000
--- a/interface/resources/html/edit-entities-commands.html
+++ /dev/null
@@ -1,2036 +0,0 @@
-
-
-
-
-
-
-
-
diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp
index 4ff41e1b6f..c1f3698326 100644
--- a/interface/src/Application.cpp
+++ b/interface/src/Application.cpp
@@ -382,6 +382,10 @@ Application::Application(int& argc, char** argv, QElapsedTimer &startup_time) :
auto discoverabilityManager = DependencyManager::get();
connect(locationUpdateTimer, &QTimer::timeout, discoverabilityManager.data(), &DiscoverabilityManager::updateLocation);
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::nodeKilled, this, &Application::nodeKilled);
@@ -1786,6 +1790,7 @@ bool Application::exportEntities(const QString& filename, float x, float y, floa
void Application::loadSettings() {
DependencyManager::get()->loadSettings();
+ DependencyManager::get()->loadSettings();
Menu::getInstance()->loadSettings();
_myAvatar->loadData();
@@ -1793,6 +1798,7 @@ void Application::loadSettings() {
void Application::saveSettings() {
DependencyManager::get()->saveSettings();
+ DependencyManager::get()->saveSettings();
Menu::getInstance()->saveSettings();
_myAvatar->saveData();
@@ -1969,7 +1975,7 @@ bool Application::isLookingAtMyAvatar(Avatar* avatar) {
void Application::updateLOD() {
PerformanceTimer perfTimer("LOD");
// 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()->autoAdjustLOD(_fps);
} else {
DependencyManager::get()->resetLODAdjust();
@@ -3295,11 +3301,6 @@ void Application::connectedToDomain(const QString& hostname) {
const QUuid& domainID = DependencyManager::get()->getDomainHandler().getUUID();
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;
}
}
diff --git a/interface/src/Application.h b/interface/src/Application.h
index 4038b21739..898db76f07 100644
--- a/interface/src/Application.h
+++ b/interface/src/Application.h
@@ -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 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
static const UINT UWM_IDENTIFY_INSTANCES =
diff --git a/interface/src/LODManager.cpp b/interface/src/LODManager.cpp
index 063ba13492..8b942dcfe1 100644
--- a/interface/src/LODManager.cpp
+++ b/interface/src/LODManager.cpp
@@ -17,13 +17,26 @@
#include "LODManager.h"
-Setting::Handle automaticAvatarLOD("automaticAvatarLOD", true);
-Setting::Handle avatarLODDecreaseFPS("avatarLODDecreaseFPS", DEFAULT_ADJUST_AVATAR_LOD_DOWN_FPS);
-Setting::Handle avatarLODIncreaseFPS("avatarLODIncreaseFPS", ADJUST_LOD_UP_FPS);
-Setting::Handle avatarLODDistanceMultiplier("avatarLODDistanceMultiplier",
- DEFAULT_AVATAR_LOD_DISTANCE_MULTIPLIER);
-Setting::Handle boundaryLevelAdjust("boundaryLevelAdjust", 0);
-Setting::Handle octreeSizeScale("octreeSizeScale", DEFAULT_OCTREE_SIZE_SCALE);
+Setting::Handle desktopLODDecreaseFPS("desktopLODDecreaseFPS", DEFAULT_DESKTOP_LOD_DOWN_FPS);
+Setting::Handle hmdLODDecreaseFPS("hmdLODDecreaseFPS", DEFAULT_HMD_LOD_DOWN_FPS);
+
+LODManager::LODManager() {
+ calculateAvatarLODDistanceMultiplier();
+}
+
+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) {
@@ -39,66 +52,65 @@ void LODManager::autoAdjustLOD(float currentFPS) {
_fastFPSAverage.updateAverage(currentFPS);
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 octreeChanged = false;
quint64 elapsed = now - _lastAdjust;
- if (elapsed > ADJUST_LOD_DOWN_DELAY && _fpsAverage.getAverage() < ADJUST_LOD_DOWN_FPS
- && _octreeSizeScale > ADJUST_LOD_MIN_SIZE_SCALE) {
-
- _octreeSizeScale *= ADJUST_LOD_DOWN_BY;
-
- if (_octreeSizeScale < ADJUST_LOD_MIN_SIZE_SCALE) {
- _octreeSizeScale = ADJUST_LOD_MIN_SIZE_SCALE;
- }
- changed = true;
- _lastAdjust = now;
- qDebug() << "adjusting LOD down... average fps for last approximately 5 seconds=" << _fpsAverage.getAverage()
- << "_octreeSizeScale=" << _octreeSizeScale;
-
- emit LODDecreased();
- }
-
- if (elapsed > ADJUST_LOD_UP_DELAY && _fpsAverage.getAverage() > ADJUST_LOD_UP_FPS
- && _octreeSizeScale < ADJUST_LOD_MAX_SIZE_SCALE) {
- _octreeSizeScale *= ADJUST_LOD_UP_BY;
- if (_octreeSizeScale > ADJUST_LOD_MAX_SIZE_SCALE) {
- _octreeSizeScale = ADJUST_LOD_MAX_SIZE_SCALE;
- }
- changed = true;
- _lastAdjust = now;
- qDebug() << "adjusting LOD up... average fps for last approximately 5 seconds=" << _fpsAverage.getAverage()
- << "_octreeSizeScale=" << _octreeSizeScale;
+ if (_automaticLODAdjust) {
+ // LOD Downward adjustment
+ if (elapsed > ADJUST_LOD_DOWN_DELAY && _fpsAverage.getAverage() < getLODDecreaseFPS()) {
- emit LODIncreased();
- }
+ // 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 (changed) {
+ _lastAdjust = now;
+ qDebug() << "adjusting LOD down... average fps for last approximately 5 seconds=" << _fpsAverage.getAverage()
+ << "_octreeSizeScale=" << _octreeSizeScale;
+
+ emit LODDecreased();
+ }
+ }
- if (changed) {
- _shouldRenderTableNeedsRebuilding = true;
- auto lodToolsDialog = DependencyManager::get()->getLodToolsDialog();
- if (lodToolsDialog) {
- lodToolsDialog->reloadSliders();
+ // LOD Upward adjustment
+ if (elapsed > ADJUST_LOD_UP_DELAY && _fpsAverage.getAverage() > getLODIncreaseFPS()) {
+
+ // Octee items... stepwise adjustment
+ if (_octreeSizeScale < ADJUST_LOD_MAX_SIZE_SCALE) {
+ if (_octreeSizeScale < ADJUST_LOD_MIN_SIZE_SCALE) {
+ _octreeSizeScale = ADJUST_LOD_MIN_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();
+ }
+ }
+
+ if (changed) {
+ calculateAvatarLODDistanceMultiplier();
+ _shouldRenderTableNeedsRebuilding = true;
+ auto lodToolsDialog = DependencyManager::get()->getLodToolsDialog();
+ if (lodToolsDialog) {
+ lodToolsDialog->reloadSliders();
+ }
}
}
}
@@ -106,7 +118,7 @@ void LODManager::autoAdjustLOD(float currentFPS) {
void LODManager::resetLODAdjust() {
_fpsAverage.reset();
_fastFPSAverage.reset();
- _lastAvatarDetailDrop = _lastAdjust = usecTimestampNow();
+ _lastAdjust = usecTimestampNow();
}
QString LODManager::getLODFeedbackText() {
@@ -116,29 +128,33 @@ QString LODManager::getLODFeedbackText() {
switch (boundaryLevelAdjust) {
case 0: {
- granularityFeedback = QString("at standard granularity.");
+ granularityFeedback = QString(".");
} break;
case 1: {
- granularityFeedback = QString("at half of standard granularity.");
+ granularityFeedback = QString(" at half of standard granularity.");
} break;
case 2: {
- granularityFeedback = QString("at a third of standard granularity.");
+ granularityFeedback = QString(" at a third of standard granularity.");
} break;
default: {
- granularityFeedback = QString("at 1/%1th of standard granularity.").arg(boundaryLevelAdjust + 1);
+ granularityFeedback = QString(" at 1/%1th of standard granularity.").arg(boundaryLevelAdjust + 1);
} break;
}
// distance feedback
float octreeSizeScale = getOctreeSizeScale();
float relativeToDefault = octreeSizeScale / DEFAULT_OCTREE_SIZE_SCALE;
+ int relativeToTwentyTwenty = 20 / relativeToDefault;
+
QString result;
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) {
- 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 {
- 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;
}
@@ -184,9 +200,14 @@ bool LODManager::shouldRenderMesh(float largestDimension, float distanceToCamera
void LODManager::setOctreeSizeScale(float sizeScale) {
_octreeSizeScale = sizeScale;
+ calculateAvatarLODDistanceMultiplier();
_shouldRenderTableNeedsRebuilding = true;
}
+void LODManager::calculateAvatarLODDistanceMultiplier() {
+ _avatarLODDistanceMultiplier = AVATAR_TO_ENTITY_RATIO / (_octreeSizeScale / DEFAULT_OCTREE_SIZE_SCALE);
+}
+
void LODManager::setBoundaryLevelAdjust(int boundaryLevelAdjust) {
_boundaryLevelAdjust = boundaryLevelAdjust;
_shouldRenderTableNeedsRebuilding = true;
@@ -194,21 +215,13 @@ void LODManager::setBoundaryLevelAdjust(int boundaryLevelAdjust) {
void LODManager::loadSettings() {
- setAutomaticAvatarLOD(automaticAvatarLOD.get());
- setAvatarLODDecreaseFPS(avatarLODDecreaseFPS.get());
- setAvatarLODIncreaseFPS(avatarLODIncreaseFPS.get());
- setAvatarLODDistanceMultiplier(avatarLODDistanceMultiplier.get());
- setBoundaryLevelAdjust(boundaryLevelAdjust.get());
- setOctreeSizeScale(octreeSizeScale.get());
+ setDesktopLODDecreaseFPS(desktopLODDecreaseFPS.get());
+ setHMDLODDecreaseFPS(hmdLODDecreaseFPS.get());
}
void LODManager::saveSettings() {
- automaticAvatarLOD.set(getAutomaticAvatarLOD());
- avatarLODDecreaseFPS.set(getAvatarLODDecreaseFPS());
- avatarLODIncreaseFPS.set(getAvatarLODIncreaseFPS());
- avatarLODDistanceMultiplier.set(getAvatarLODDistanceMultiplier());
- boundaryLevelAdjust.set(getBoundaryLevelAdjust());
- octreeSizeScale.set(getOctreeSizeScale());
+ desktopLODDecreaseFPS.set(getDesktopLODDecreaseFPS());
+ hmdLODDecreaseFPS.set(getHMDLODDecreaseFPS());
}
diff --git a/interface/src/LODManager.h b/interface/src/LODManager.h
index 61c24bf5af..c14f17ca61 100644
--- a/interface/src/LODManager.h
+++ b/interface/src/LODManager.h
@@ -17,9 +17,9 @@
#include
#include
-const float ADJUST_LOD_DOWN_FPS = 40.0;
-const float ADJUST_LOD_UP_FPS = 55.0;
-const float DEFAULT_ADJUST_AVATAR_LOD_DOWN_FPS = 30.0f;
+const float DEFAULT_DESKTOP_LOD_DOWN_FPS = 30.0;
+const float DEFAULT_HMD_LOD_DOWN_FPS = 60.0;
+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_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_MAX_SIZE_SCALE = DEFAULT_OCTREE_SIZE_SCALE;
-const float MINIMUM_AVATAR_LOD_DISTANCE_MULTIPLIER = 0.1f;
-const float MAXIMUM_AVATAR_LOD_DISTANCE_MULTIPLIER = 15.0f;
-const float DEFAULT_AVATAR_LOD_DISTANCE_MULTIPLIER = 1.0f;
+// The ratio of "visibility" of avatars to other content. A value larger than 1 will mean Avatars "cull" later than entities
+// do. But both are still culled using the same angular size logic.
+const float AVATAR_TO_ENTITY_RATIO = 2.0f;
const int ONE_SECOND_OF_FRAMES = 60;
const int FIVE_SECONDS_OF_FRAMES = 5 * ONE_SECOND_OF_FRAMES;
@@ -46,14 +46,18 @@ class LODManager : public QObject, public Dependency {
SINGLETON_DEPENDENCY
public:
- void setAutomaticAvatarLOD(bool automaticAvatarLOD) { _automaticAvatarLOD = automaticAvatarLOD; }
- bool getAutomaticAvatarLOD() const { return _automaticAvatarLOD; }
- void setAvatarLODDecreaseFPS(float avatarLODDecreaseFPS) { _avatarLODDecreaseFPS = avatarLODDecreaseFPS; }
- float getAvatarLODDecreaseFPS() const { return _avatarLODDecreaseFPS; }
- void setAvatarLODIncreaseFPS(float avatarLODIncreaseFPS) { _avatarLODIncreaseFPS = avatarLODIncreaseFPS; }
- float getAvatarLODIncreaseFPS() const { return _avatarLODIncreaseFPS; }
- void setAvatarLODDistanceMultiplier(float multiplier) { _avatarLODDistanceMultiplier = multiplier; }
- float getAvatarLODDistanceMultiplier() const { return _avatarLODDistanceMultiplier; }
+ Q_INVOKABLE void setAutomaticLODAdjust(bool value) { _automaticLODAdjust = value; }
+ Q_INVOKABLE bool getAutomaticLODAdjust() const { return _automaticLODAdjust; }
+
+ Q_INVOKABLE void setDesktopLODDecreaseFPS(float value) { _desktopLODDecreaseFPS = value; }
+ Q_INVOKABLE float getDesktopLODDecreaseFPS() const { return _desktopLODDecreaseFPS; }
+ Q_INVOKABLE float getDesktopLODIncreaseFPS() const { return _desktopLODDecreaseFPS + INCREASE_LOD_GAP; }
+
+ 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
Q_INVOKABLE QString getLODFeedbackText();
@@ -63,12 +67,15 @@ public:
Q_INVOKABLE void setBoundaryLevelAdjust(int boundaryLevelAdjust);
Q_INVOKABLE int getBoundaryLevelAdjust() const { return _boundaryLevelAdjust; }
- void autoAdjustLOD(float currentFPS);
Q_INVOKABLE void resetLODAdjust();
Q_INVOKABLE float getFPSAverage() const { return _fpsAverage.getAverage(); }
Q_INVOKABLE float getFastFPSAverage() const { return _fastFPSAverage.getAverage(); }
+ Q_INVOKABLE float getLODDecreaseFPS();
+ Q_INVOKABLE float getLODIncreaseFPS();
+
bool shouldRenderMesh(float largestDimension, float distanceToCamera);
+ void autoAdjustLOD(float currentFPS);
void loadSettings();
void saveSettings();
@@ -78,18 +85,18 @@ signals:
void LODDecreased();
private:
- LODManager() {}
-
- bool _automaticAvatarLOD = true;
- float _avatarLODDecreaseFPS = DEFAULT_ADJUST_AVATAR_LOD_DOWN_FPS;
- float _avatarLODIncreaseFPS = ADJUST_LOD_UP_FPS;
- float _avatarLODDistanceMultiplier = DEFAULT_AVATAR_LOD_DISTANCE_MULTIPLIER;
+ LODManager();
+ void calculateAvatarLODDistanceMultiplier();
+ bool _automaticLODAdjust = true;
+ float _desktopLODDecreaseFPS = DEFAULT_DESKTOP_LOD_DOWN_FPS;
+ float _hmdLODDecreaseFPS = DEFAULT_HMD_LOD_DOWN_FPS;
+
+ float _avatarLODDistanceMultiplier;
float _octreeSizeScale = DEFAULT_OCTREE_SIZE_SCALE;
int _boundaryLevelAdjust = 0;
quint64 _lastAdjust = 0;
- quint64 _lastAvatarDetailDrop = 0;
SimpleMovingAverage _fpsAverage = FIVE_SECONDS_OF_FRAMES;
SimpleMovingAverage _fastFPSAverage = ONE_SECOND_OF_FRAMES;
diff --git a/interface/src/Menu.cpp b/interface/src/Menu.cpp
index 2823e8eb23..8d3c01320f 100644
--- a/interface/src/Menu.cpp
+++ b/interface/src/Menu.cpp
@@ -276,7 +276,6 @@ Menu::Menu() {
addCheckableActionToQMenuAndActionHash(renderOptionsMenu, MenuOption::Entities, 0, true);
addCheckableActionToQMenuAndActionHash(renderOptionsMenu, MenuOption::AmbientOcclusion);
addCheckableActionToQMenuAndActionHash(renderOptionsMenu, MenuOption::DontFadeOnOctreeServerChanges);
- addCheckableActionToQMenuAndActionHash(renderOptionsMenu, MenuOption::DisableAutoAdjustLOD);
QMenu* ambientLightMenu = renderOptionsMenu->addMenu(MenuOption::RenderAmbientLight);
QActionGroup* ambientLightGroup = new QActionGroup(ambientLightMenu);
diff --git a/interface/src/Menu.h b/interface/src/Menu.h
index fc1347fa27..60fe5d4cd2 100644
--- a/interface/src/Menu.h
+++ b/interface/src/Menu.h
@@ -136,7 +136,6 @@ namespace MenuOption {
const QString DecreaseAvatarSize = "Decrease Avatar Size";
const QString DeleteBookmark = "Delete Bookmark...";
const QString DisableActivityLogger = "Disable Activity Logger";
- const QString DisableAutoAdjustLOD = "Disable Automatically Adjusting LOD";
const QString DisableLightEntities = "Disable Light Entities";
const QString DisableNackPackets = "Disable NACK Packets";
const QString DiskCacheEditor = "Disk Cache Editor";
diff --git a/interface/src/avatar/FaceModel.cpp b/interface/src/avatar/FaceModel.cpp
index c80772ef49..722f998f86 100644
--- a/interface/src/avatar/FaceModel.cpp
+++ b/interface/src/avatar/FaceModel.cpp
@@ -87,7 +87,8 @@ void FaceModel::maybeUpdateEyeRotation(Model* model, const JointState& parentSta
void FaceModel::updateJointState(int index) {
JointState& state = _jointStates[index];
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 FBXGeometry& geometry = _geometry->getFBXGeometry();
if (index == geometry.neckJointIndex) {
diff --git a/interface/src/ui/LodToolsDialog.cpp b/interface/src/ui/LodToolsDialog.cpp
index 277d634735..378a1391f4 100644
--- a/interface/src/ui/LodToolsDialog.cpp
+++ b/interface/src/ui/LodToolsDialog.cpp
@@ -35,9 +35,24 @@ LodToolsDialog::LodToolsDialog(QWidget* parent) :
// Create layouter
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);
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 PAGE_STEP_LOD_SIZE = 100;
const int SLIDER_WIDTH = 300;
@@ -50,55 +65,8 @@ LodToolsDialog::LodToolsDialog(QWidget* parent) :
_lodSize->setPageStep(PAGE_STEP_LOD_SIZE);
int sliderValue = lodManager->getOctreeSizeScale() / TREE_SCALE;
_lodSize->setValue(sliderValue);
- form->addRow("LOD Size Scale:", _lodSize);
+ form->addRow("Level of Detail:", _lodSize);
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
QPushButton* resetButton = new QPushButton("Reset", this);
@@ -107,49 +75,19 @@ LodToolsDialog::LodToolsDialog(QWidget* parent) :
this->QDialog::setLayout(form);
- updateAvatarLODControls();
+ updateAutomaticLODAdjust();
}
void LodToolsDialog::reloadSliders() {
auto lodManager = DependencyManager::get();
_lodSize->setValue(lodManager->getOctreeSizeScale() / TREE_SCALE);
- _boundaryLevelAdjust->setValue(lodManager->getBoundaryLevelAdjust());
_feedback->setText(lodManager->getLODFeedbackText());
}
-void LodToolsDialog::updateAvatarLODControls() {
- QFormLayout* form = static_cast(layout());
-
+void LodToolsDialog::updateAutomaticLODAdjust() {
auto lodManager = DependencyManager::get();
- lodManager->setAutomaticAvatarLOD(_automaticAvatarLOD->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();
- if (_automaticAvatarLOD->isChecked()) {
- lodManager->setAvatarLODDecreaseFPS(_avatarLODDecreaseFPS->value());
- lodManager->setAvatarLODIncreaseFPS(_avatarLODIncreaseFPS->value());
-
- } else {
- lodManager->setAvatarLODDistanceMultiplier(1.0 / _avatarLOD->value());
- }
+ lodManager->setAutomaticLODAdjust(!_manualLODAdjust->isChecked());
+ _lodSize->setEnabled(_manualLODAdjust->isChecked());
}
void LodToolsDialog::sizeScaleValueChanged(int value) {
@@ -160,20 +98,13 @@ void LodToolsDialog::sizeScaleValueChanged(int value) {
_feedback->setText(lodManager->getLODFeedbackText());
}
-void LodToolsDialog::boundaryLevelValueChanged(int value) {
- auto lodManager = DependencyManager::get();
- lodManager->setBoundaryLevelAdjust(value);
- _feedback->setText(lodManager->getLODFeedbackText());
-}
-
void LodToolsDialog::resetClicked(bool checked) {
+
int sliderValue = DEFAULT_OCTREE_SIZE_SCALE / TREE_SCALE;
- //sizeScaleValueChanged(sliderValue);
_lodSize->setValue(sliderValue);
- _boundaryLevelAdjust->setValue(0);
- _automaticAvatarLOD->setChecked(true);
- _avatarLODDecreaseFPS->setValue(DEFAULT_ADJUST_AVATAR_LOD_DOWN_FPS);
- _avatarLODIncreaseFPS->setValue(ADJUST_LOD_UP_FPS);
+ _manualLODAdjust->setChecked(false);
+
+ updateAutomaticLODAdjust(); // tell our LOD manager about the reset
}
void LodToolsDialog::reject() {
@@ -184,6 +115,15 @@ void LodToolsDialog::reject() {
void LodToolsDialog::closeEvent(QCloseEvent* event) {
this->QDialog::closeEvent(event);
emit closed();
+ auto lodManager = DependencyManager::get();
+
+ // 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);
+ }
}
diff --git a/interface/src/ui/LodToolsDialog.h b/interface/src/ui/LodToolsDialog.h
index 772027790c..e5a2dae836 100644
--- a/interface/src/ui/LodToolsDialog.h
+++ b/interface/src/ui/LodToolsDialog.h
@@ -31,11 +31,9 @@ signals:
public slots:
void reject();
void sizeScaleValueChanged(int value);
- void boundaryLevelValueChanged(int value);
void resetClicked(bool checked);
void reloadSliders();
- void updateAvatarLODControls();
- void updateAvatarLODValues();
+ void updateAutomaticLODAdjust();
protected:
@@ -44,11 +42,13 @@ protected:
private:
QSlider* _lodSize;
- QSlider* _boundaryLevelAdjust;
- QCheckBox* _automaticAvatarLOD;
- QDoubleSpinBox* _avatarLODDecreaseFPS;
- QDoubleSpinBox* _avatarLODIncreaseFPS;
- QDoubleSpinBox* _avatarLOD;
+
+ QCheckBox* _manualLODAdjust;
+
+ QDoubleSpinBox* _desktopLODDecreaseFPS;
+
+ QDoubleSpinBox* _hmdLODDecreaseFPS;
+
QLabel* _feedback;
};
diff --git a/interface/src/ui/PreferencesDialog.cpp b/interface/src/ui/PreferencesDialog.cpp
index fa63079290..a07de371a2 100644
--- a/interface/src/ui/PreferencesDialog.cpp
+++ b/interface/src/ui/PreferencesDialog.cpp
@@ -19,6 +19,7 @@
#include "Application.h"
#include "MainWindow.h"
+#include "LODManager.h"
#include "Menu.h"
#include "ModelsBrowser.h"
#include "PreferencesDialog.h"
@@ -174,6 +175,10 @@ void PreferencesDialog::loadPreferences() {
ui.sixenseReticleMoveSpeedSpin->setValue(sixense.getReticleMoveSpeed());
ui.invertSixenseButtonsCheckBox->setChecked(sixense.getInvertButtons());
+ // LOD items
+ auto lodManager = DependencyManager::get();
+ ui.desktopMinimumFPSSpin->setValue(lodManager->getDesktopLODDecreaseFPS());
+ ui.hmdMinimumFPSSpin->setValue(lodManager->getHMDLODDecreaseFPS());
}
void PreferencesDialog::savePreferences() {
@@ -275,4 +280,9 @@ void PreferencesDialog::savePreferences() {
audio->setOutputStarveDetectionPeriod(ui.outputStarveDetectionPeriodSpinner->value());
Application::getInstance()->resizeGL(glCanvas->width(), glCanvas->height());
+
+ // LOD items
+ auto lodManager = DependencyManager::get();
+ lodManager->setDesktopLODDecreaseFPS(ui.desktopMinimumFPSSpin->value());
+ lodManager->setHMDLODDecreaseFPS(ui.hmdMinimumFPSSpin->value());
}
diff --git a/interface/ui/preferencesDialog.ui b/interface/ui/preferencesDialog.ui
index 13894a2592..d295d094c2 100644
--- a/interface/ui/preferencesDialog.ui
+++ b/interface/ui/preferencesDialog.ui
@@ -701,6 +701,219 @@
+
+
+
+ -
+
+
+ Qt::Vertical
+
+
+ QSizePolicy::Fixed
+
+
+
+ 20
+ 20
+
+
+
+
+
+ -
+
+
+
+ Arial
+ 18
+ 75
+ true
+
+
+
+ color:#29967e
+
+
+ Level of Detail Tuning
+
+
+ Qt::AlignBottom|Qt::AlignLeading|Qt::AlignLeft
+
+
+
+
+ -
+
+
+ 0
+
+
+ 7
+
+
+ 0
+
+
+ 7
+
+
-
+
+
+
+ Arial
+
+
+
+
+
+
+ Minimum Desktop FPS
+
+
+ 0
+
+
+
+
+ -
+
+
+
+ Arial
+
+
+
+ Qt::Horizontal
+
+
+
+ 0
+ 0
+
+
+
+
+ -
+
+
+
+ 100
+ 0
+
+
+
+
+ 95
+ 36
+
+
+
+
+ Arial
+
+
+
+ 0
+
+
+ 120
+
+
+
+
+
+
+ -
+
+
+ 0
+
+
+ 7
+
+
+ 0
+
+
+ 7
+
+
-
+
+
+
+ Arial
+
+
+
+
+
+
+ Minimum HMD FPS
+
+
+ 0
+
+
+
+
+ -
+
+
+
+ Arial
+
+
+
+ Qt::Horizontal
+
+
+
+ 0
+ 0
+
+
+
+
+ -
+
+
+
+ 100
+ 0
+
+
+
+
+ 95
+ 36
+
+
+
+
+ Arial
+
+
+
+ 0
+
+
+ 120
+
+
+
+
+
+
+
-
@@ -717,6 +930,7 @@
+
-
@@ -738,6 +952,7 @@
+
-
@@ -820,6 +1035,9 @@
+
+
+
-
diff --git a/libraries/octree/src/OctreeConstants.h b/libraries/octree/src/OctreeConstants.h
index a975469053..1246aeffd4 100644
--- a/libraries/octree/src/OctreeConstants.h
+++ b/libraries/octree/src/OctreeConstants.h
@@ -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;
// 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;
diff --git a/libraries/physics/src/CharacterController.cpp b/libraries/physics/src/CharacterController.cpp
index 5173b368c1..fb200b2c57 100644
--- a/libraries/physics/src/CharacterController.cpp
+++ b/libraries/physics/src/CharacterController.cpp
@@ -16,10 +16,9 @@ subject to the following restrictions:
*/
-//#include
-
#include "BulletCollision/CollisionDispatch/btGhostObject.h"
+#include "BulletUtil.h"
#include "CharacterController.h"
@@ -54,7 +53,7 @@ m_me = me;
virtual btScalar addSingleResult(btCollisionWorld::LocalRayResult& rayResult, bool normalInWorldSpace)
{
-if(rayResult.m_collisionObject == m_me)
+if (rayResult.m_collisionObject == m_me)
return 1.0;
return ClosestRayResultCallback::addSingleResult(rayResult, normalInWorldSpace);
@@ -64,8 +63,7 @@ btCollisionObject* m_me;
};
*/
-class btKinematicClosestNotMeConvexResultCallback : public btCollisionWorld::ClosestConvexResultCallback
-{
+class btKinematicClosestNotMeConvexResultCallback : public btCollisionWorld::ClosestConvexResultCallback {
public:
btKinematicClosestNotMeConvexResultCallback(btCollisionObject* me, const btVector3& up, btScalar minSlopeDot)
: 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;
}
+ // 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) {
return btScalar(1.0);
@@ -106,21 +107,104 @@ protected:
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'
*
* 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;
}
/*
* 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);
return normal * magnitude;
}
@@ -128,36 +212,38 @@ btVector3 CharacterController::parallelComponent(const btVector3& direction, con
/*
* 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);
}
-CharacterController::CharacterController(
- btPairCachingGhostObject* ghostObject,
- btConvexShape* convexShape,
- btScalar stepHeight,
- int upAxis) {
- m_upAxis = upAxis;
- m_addedMargin = 0.02;
- m_walkDirection.setValue(0,0,0);
- m_useGhostObjectSweepTest = true;
- m_ghostObject = ghostObject;
- m_stepHeight = stepHeight;
- m_turnAngle = btScalar(0.0);
- m_convexShape = convexShape;
+CharacterController::CharacterController(AvatarData* avatarData) {
+ assert(avatarData);
+ m_avatarData = avatarData;
+
+ // cache the "PhysicsEnabled" state of m_avatarData
+ m_avatarData->lockForRead();
+ m_enabled = m_avatarData->isPhysicsEnabled();
+ m_avatarData->unlock();
+
+ createShapeAndGhost();
+
+ m_upAxis = 1; // HACK: hard coded to be 1 for now (yAxis)
+
+ 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_velocityTimeInterval = 0.0;
- m_verticalVelocity = 0.0;
- m_verticalOffset = 0.0;
- m_gravity = 9.8 * 3 ; // 3G acceleration.
- m_fallSpeed = 55.0; // Terminal velocity of a sky diver in m/s.
- m_jumpSpeed = 10.0; // ?
+ m_velocityTimeInterval = 0.0f;
+ m_verticalVelocity = 0.0f;
+ m_verticalOffset = 0.0f;
+ m_gravity = 9.8f;
+ m_maxFallSpeed = 55.0f; // Terminal velocity of a sky diver in m/s.
+ m_jumpSpeed = 10.0f; // ?
m_wasOnGround = false;
m_wasJumping = false;
m_interpolateUp = true;
- setMaxSlope(btRadians(45.0));
- m_currentStepOffset = 0;
+ setMaxSlope(btRadians(45.0f));
+ m_lastStepUp = 0.0f;
// internal state data members
full_drop = false;
@@ -192,6 +278,9 @@ bool CharacterController::recoverFromPenetration(btCollisionWorld* collisionWorl
collisionWorld->getDispatcher()->dispatchAllCollisionPairs(m_ghostObject->getOverlappingPairCache(), collisionWorld->getDispatchInfo(), collisionWorld->getDispatcher());
m_currentPosition = m_ghostObject->getWorldTransform().getOrigin();
+ btVector3 up = getUpAxisDirections()[m_upAxis];
+
+ btVector3 currentPosition = m_currentPosition;
btScalar maxPen = btScalar(0.0);
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);
}
- for (int j=0;jgetBody0() == m_ghostObject ? btScalar(-1.0) : btScalar(1.0);
- for (int p=0;pgetNumContacts();p++) {
+ btScalar directionSign = (manifold->getBody0() == m_ghostObject) ? btScalar(1.0) : btScalar(-1.0);
+ for (int p = 0;p < manifold->getNumContacts(); p++) {
const btManifoldPoint&pt = manifold->getContactPoint(p);
btScalar dist = pt.getDistance();
if (dist < 0.0) {
- if (dist < maxPen) {
- maxPen = dist;
- m_touchingNormal = pt.m_normalWorldOnB * directionSign;//??
+ bool useContact = true;
+ btVector3 normal = pt.m_normalWorldOnB;
+ 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();
newTrans.setOrigin(m_currentPosition);
m_ghostObject->setWorldTransform(newTrans);
- //printf("m_touchingNormal = %f,%f,%f\n", m_touchingNormal[0], m_touchingNormal[1], m_touchingNormal[2]);
return penetration;
}
void CharacterController::stepUp( btCollisionWorld* world) {
// phase 1: up
+
+ // compute start and end
btTransform start, end;
- m_targetPosition = m_currentPosition + getUpAxisDirections()[m_upAxis] * (m_stepHeight + (m_verticalOffset > 0.f?m_verticalOffset:0.f));
-
start.setIdentity();
- end.setIdentity();
-
- /* FIXME: Handle penetration properly */
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);
- 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_collisionFilterMask = getGhostObject()->getBroadphaseHandle()->m_collisionFilterMask;
-
- if (m_useGhostObjectSweepTest) {
- m_ghostObject->convexSweepTest(m_convexShape, start, end, callback, world->getDispatchInfo().m_allowedCcdPenetration);
- }
- else {
- world->convexSweepTest(m_convexShape, start, end, callback);
- }
+ m_ghostObject->convexSweepTest(m_convexShape, start, end, callback, world->getDispatchInfo().m_allowedCcdPenetration);
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.
if (callback.m_hitNormalWorld.dot(getUpAxisDirections()[m_upAxis]) > 0.0) {
- // we moved up only a fraction of the step height
- m_currentStepOffset = m_stepHeight * callback.m_closestHitFraction;
+ m_lastStepUp = m_stepHeight * callback.m_closestHitFraction;
if (m_interpolateUp == true) {
m_currentPosition.setInterpolate3 (m_currentPosition, m_targetPosition, callback.m_closestHitFraction);
} else {
m_currentPosition = m_targetPosition;
}
+ } else {
+ m_lastStepUp = m_stepHeight;
+ m_currentPosition = m_targetPosition;
}
- m_verticalVelocity = 0.0;
- m_verticalOffset = 0.0;
} else {
- m_currentStepOffset = m_stepHeight;
m_currentPosition = m_targetPosition;
+ m_lastStepUp = m_stepHeight;
}
}
void CharacterController::updateTargetPositionBasedOnCollision(const btVector3& hitNormal, btScalar tangentMag, btScalar normalMag) {
btVector3 movementDirection = m_targetPosition - m_currentPosition;
btScalar movementLength = movementDirection.length();
- if (movementLength>SIMD_EPSILON) {
+ if (movementLength > SIMD_EPSILON) {
movementDirection.normalize();
btVector3 reflectDir = computeReflectionDirection(movementDirection, hitNormal);
@@ -300,241 +415,152 @@ void CharacterController::updateTargetPositionBasedOnCollision(const btVector3&
m_targetPosition = m_currentPosition;
//if (tangentMag != 0.0) {
if (0) {
- btVector3 parComponent = parallelDir * btScalar(tangentMag*movementLength);
- //printf("parComponent=%f,%f,%f\n", parComponent[0], parComponent[1], parComponent[2]);
+ btVector3 parComponent = parallelDir * btScalar(tangentMag * movementLength);
m_targetPosition += parComponent;
}
if (normalMag != 0.0) {
- btVector3 perpComponent = perpindicularDir * btScalar(normalMag*movementLength);
- //printf("perpComponent=%f,%f,%f\n", perpComponent[0], perpComponent[1], perpComponent[2]);
+ btVector3 perpComponent = perpindicularDir * btScalar(normalMag * movementLength);
m_targetPosition += perpComponent;
}
- } else {
- //printf("movementLength don't normalize a zero vector\n");
}
}
-void CharacterController::stepForwardAndStrafe( btCollisionWorld* collisionWorld, const btVector3& walkMove) {
- //printf("m_normalizedDirection=%f,%f,%f\n",
- // m_normalizedDirection[0], m_normalizedDirection[1], m_normalizedDirection[2]);
- // phase 2: forward and strafe
- btTransform start, end;
- m_targetPosition = m_currentPosition + walkMove;
+void CharacterController::stepForward( btCollisionWorld* collisionWorld, const btVector3& movement) {
+ // phase 2: forward
+ m_targetPosition = m_currentPosition + movement;
+ btTransform start, end;
start.setIdentity();
end.setIdentity();
- btScalar fraction = 1.0;
- btScalar distance2 = (m_currentPosition-m_targetPosition).length2();
- //printf("distance2=%f\n", distance2);
-
+ /* TODO: experiment with this to see if we can use this to help direct motion when a floor is available
if (m_touchingContact) {
- if (m_normalizedDirection.dot(m_touchingNormal) > btScalar(0.0)) {
- //interferes with step movement
- //updateTargetPositionBasedOnCollision(m_touchingNormal);
+ if (m_normalizedDirection.dot(m_floorNormal) < btScalar(0.0)) {
+ updateTargetPositionBasedOnCollision(m_floorNormal, 1.0f, 1.0f);
}
- }
+ }*/
+ // 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;
- while (fraction > btScalar(0.01) && maxIter-- > 0) {
+ while (stepLength2 > MIN_STEP_DISTANCE && maxIter-- > 0) {
start.setOrigin(m_currentPosition);
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));
callback.m_collisionFilterGroup = getGhostObject()->getBroadphaseHandle()->m_collisionFilterGroup;
callback.m_collisionFilterMask = getGhostObject()->getBroadphaseHandle()->m_collisionFilterMask;
-
-
- 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;
+ m_ghostObject->convexSweepTest(m_convexShape, start, end, callback, collisionWorld->getDispatchInfo().m_allowedCcdPenetration);
if (callback.hasHit()) {
- // we moved only a fraction
- //btScalar hitDistance;
- //hitDistance = (callback.m_hitPointWorld - m_currentPosition).length();
+ // we hit soemthing!
+ // Compute new target position by removing portion cut-off by collision, which will produce a new target
+ // 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);
-
- 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;
- }
+ stepLength2 = step.length2();
} else {
- // we moved whole way
+ // we swept to the end without hitting anything
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) {
- btTransform start, end, end_double;
- bool runonce = false;
-
// phase 3: down
- /*btScalar additionalDownStep = (m_wasOnGround && !onGround()) ? m_stepHeight : 0.0;
- btVector3 step_drop = getUpAxisDirections()[m_upAxis] * (m_currentStepOffset + additionalDownStep);
- btScalar downVelocity = (additionalDownStep == 0.0 && m_verticalVelocity<0.0?-m_verticalVelocity:0.0) * dt;
- btVector3 gravity_drop = getUpAxisDirections()[m_upAxis] * downVelocity;
- m_targetPosition -= (step_drop + gravity_drop);*/
+ //
+ // The "stepDown" phase first makes a normal sweep down that cancels the lift from the "stepUp" phase.
+ // If it hits a ledge then it stops otherwise it makes another sweep down in search of a floor within
+ // reach of the character's feet.
- btVector3 orig_position = m_targetPosition;
-
- btScalar downVelocity = (m_verticalVelocity<0.f?-m_verticalVelocity:0.f) * dt;
-
- if (downVelocity > 0.0 && downVelocity > m_fallSpeed && (m_wasOnGround || !m_wasJumping)) {
- downVelocity = m_fallSpeed;
+ btScalar downSpeed = (m_verticalVelocity < 0.0f) ? -m_verticalVelocity : 0.0f;
+ if (downSpeed > 0.0f && downSpeed > m_maxFallSpeed && (m_wasOnGround || !m_wasJumping)) {
+ downSpeed = m_maxFallSpeed;
}
- btVector3 step_drop = getUpAxisDirections()[m_upAxis] * (m_currentStepOffset + downVelocity);
- m_targetPosition -= step_drop;
+ // first sweep for ledge
+ 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_collisionFilterMask = getGhostObject()->getBroadphaseHandle()->m_collisionFilterMask;
- btKinematicClosestNotMeConvexResultCallback callback2 (m_ghostObject, getUpAxisDirections()[m_upAxis], m_maxSlopeCosine);
- callback2.m_collisionFilterGroup = getGhostObject()->getBroadphaseHandle()->m_collisionFilterGroup;
- callback2.m_collisionFilterMask = getGhostObject()->getBroadphaseHandle()->m_collisionFilterMask;
+ btTransform start, end;
+ start.setIdentity();
+ end.setIdentity();
- while (1) {
- start.setIdentity();
- end.setIdentity();
+ start.setOrigin(m_currentPosition);
+ m_targetPosition = m_currentPosition + step;
+ 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);
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
- end_double.setOrigin(m_targetPosition - step_drop);
-
- if (m_useGhostObjectSweepTest) {
- m_ghostObject->convexSweepTest(m_convexShape, start, end, callback, collisionWorld->getDispatchInfo().m_allowedCcdPenetration);
-
- 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);
- }
+ if (callback2.hasHit()) {
+ m_currentPosition += callback2.m_closestHitFraction * step;
+ m_verticalVelocity = 0.0f;
+ m_verticalOffset = 0.0f;
+ m_wasJumping = false;
} else {
- collisionWorld->convexSweepTest(m_convexShape, start, end, callback, collisionWorld->getDispatchInfo().m_allowedCcdPenetration);
-
- if (!callback.hasHit()) {
- //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);
- }
+ // nothing to step down on, so remove the stepUp effect
+ m_currentPosition = oldPosition - m_lastStepUp * getUpAxisDirections()[m_upAxis];
+ m_lastStepUp = 0.0f;
}
-
- 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) {
m_useWalkDirection = true;
m_walkDirection = walkDirection;
m_normalizedDirection = getNormalizedVector(m_walkDirection);
}
-
-
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_walkDirection = velocity;
m_normalizedDirection = getNormalizedVector(m_walkDirection);
@@ -565,29 +591,25 @@ void CharacterController::warp(const btVector3& origin) {
void CharacterController::preStep( btCollisionWorld* collisionWorld) {
+ if (!m_enabled) {
+ return;
+ }
int numPenetrationLoops = 0;
m_touchingContact = false;
while (recoverFromPenetration(collisionWorld)) {
numPenetrationLoops++;
m_touchingContact = true;
if (numPenetrationLoops > 4) {
- //printf("character could not recover from penetration = %d\n", numPenetrationLoops);
break;
}
}
m_currentPosition = m_ghostObject->getWorldTransform().getOrigin();
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) {
- //printf("playerStep(): ");
- //printf(" dt = %f", dt);
-
- // quick check...
- if (!m_useWalkDirection && m_velocityTimeInterval <= 0.0) {
- //printf("\n");
+ if (!m_enabled || (!m_useWalkDirection && m_velocityTimeInterval <= 0.0)) {
return; // no motion
}
@@ -595,49 +617,41 @@ void CharacterController::playerStep( btCollisionWorld* collisionWorld, btScala
// Update fall velocity.
m_verticalVelocity -= m_gravity * dt;
- if (m_verticalVelocity > 0.0 && m_verticalVelocity > m_jumpSpeed) {
+ if (m_verticalVelocity > m_jumpSpeed) {
m_verticalVelocity = m_jumpSpeed;
- }
- if (m_verticalVelocity < 0.0 && btFabs(m_verticalVelocity) > btFabs(m_fallSpeed)) {
- m_verticalVelocity = -btFabs(m_fallSpeed);
+ } else if (m_verticalVelocity < -m_maxFallSpeed) {
+ m_verticalVelocity = -m_maxFallSpeed;
}
m_verticalOffset = m_verticalVelocity * dt;
-
btTransform xform;
xform = m_ghostObject->getWorldTransform();
- //printf("walkDirection(%f,%f,%f)\n", walkDirection[0], walkDirection[1], walkDirection[2]);
- //printf("walkSpeed=%f\n", walkSpeed);
+ // the algorithm is as follows:
+ // (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) {
- stepForwardAndStrafe(collisionWorld, m_walkDirection);
+ stepForward(collisionWorld, m_walkDirection);
} else {
- //printf(" time: %f", m_velocityTimeInterval);
- // still have some time left for moving!
- btScalar dtMoving =
- (dt < m_velocityTimeInterval) ? dt : m_velocityTimeInterval;
+ // compute substep and decrement total interval
+ btScalar dtMoving = (dt < m_velocityTimeInterval) ? dt : m_velocityTimeInterval;
m_velocityTimeInterval -= dt;
- // how far will we move while we are moving?
+ // stepForward substep
btVector3 move = m_walkDirection * dtMoving;
-
- //printf(" dtMoving: %f", dtMoving);
-
- // okay, step
- stepForwardAndStrafe(collisionWorld, move);
+ stepForward(collisionWorld, move);
}
stepDown(collisionWorld, dt);
- //printf("\n");
-
xform.setOrigin(m_currentPosition);
m_ghostObject->setWorldTransform(xform);
}
-void CharacterController::setFallSpeed(btScalar fallSpeed) {
- m_fallSpeed = fallSpeed;
+void CharacterController::setMaxFallSpeed(btScalar speed) {
+ m_maxFallSpeed = speed;
}
void CharacterController::setJumpSpeed(btScalar jumpSpeed) {
@@ -662,7 +676,7 @@ void CharacterController::jump() {
#if 0
currently no jumping.
- btTransform xform;
+ btTransform xform;
m_rigidBody->getMotionState()->getWorldTransform(xform);
btVector3 up = xform.getBasis()[1];
up.normalize();
@@ -689,7 +703,7 @@ btScalar CharacterController::getMaxSlope() 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() {
@@ -704,3 +718,109 @@ void CharacterController::debugDraw(btIDebugDraw* debugDrawer) {
void CharacterController::setUpInterpolate(bool 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();
+ }
+}
+
diff --git a/libraries/physics/src/CharacterController.h b/libraries/physics/src/CharacterController.h
index 301253b2bd..7969fffd9d 100644
--- a/libraries/physics/src/CharacterController.h
+++ b/libraries/physics/src/CharacterController.h
@@ -19,6 +19,8 @@ subject to the following restrictions:
#ifndef hifi_CharacterController_h
#define hifi_CharacterController_h
+#include
+
#include
#include
#include
@@ -39,14 +41,17 @@ ATTRIBUTE_ALIGNED16(class) CharacterController : public btCharacterControllerInt
{
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;
- 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_verticalOffset;
- btScalar m_fallSpeed;
+ btScalar m_verticalOffset; // fall distance from velocity this frame
+ btScalar m_maxFallSpeed;
btScalar m_jumpSpeed;
btScalar m_maxJumpHeight;
btScalar m_maxSlopeRadians; // Slope angle that is set (used for returning the exact value)
@@ -55,7 +60,7 @@ protected:
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
@@ -65,18 +70,18 @@ protected:
//some internal variables
btVector3 m_currentPosition;
- btScalar m_currentStepOffset;
btVector3 m_targetPosition;
+ btScalar m_lastStepUp;
///keep track of the contact manifolds
btManifoldArray m_manifoldArray;
bool m_touchingContact;
- btVector3 m_touchingNormal;
+ btVector3 m_floorNormal; // points from object to character
- bool m_wasOnGround;
- bool m_wasJumping;
- bool m_useGhostObjectSweepTest;
+ bool m_enabled;
+ bool m_wasOnGround;
+ bool m_wasJumping;
bool m_useWalkDirection;
btScalar m_velocityTimeInterval;
int m_upAxis;
@@ -93,17 +98,14 @@ protected:
bool recoverFromPenetration(btCollisionWorld* collisionWorld);
void stepUp(btCollisionWorld* collisionWorld);
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 createShapeAndGhost();
public:
BT_DECLARE_ALIGNED_ALLOCATOR();
- CharacterController(
- btPairCachingGhostObject* ghostObject,
- btConvexShape* convexShape,
- btScalar stepHeight,
- int upAxis = 1);
+ CharacterController(AvatarData* avatarData);
~CharacterController();
@@ -145,7 +147,7 @@ public:
void preStep(btCollisionWorld* collisionWorld);
void playerStep(btCollisionWorld* collisionWorld, btScalar dt);
- void setFallSpeed(btScalar fallSpeed);
+ void setMaxFallSpeed(btScalar speed);
void setJumpSpeed(btScalar jumpSpeed);
void setMaxJumpHeight(btScalar maxJumpHeight);
bool canJump() const;
@@ -161,12 +163,15 @@ public:
btScalar getMaxSlope() const;
btPairCachingGhostObject* getGhostObject();
- void setUseGhostSweepTest(bool useGhostObjectSweepTest) {
- m_useGhostObjectSweepTest = useGhostObjectSweepTest;
- }
bool onGround() const;
void setUpInterpolate(bool value);
+
+ bool needsShapeUpdate();
+ void updateShape();
+
+ void preSimulation(btScalar timeStep);
+ void postSimulation();
};
#endif // hifi_CharacterController_h
diff --git a/libraries/physics/src/PhysicsEngine.cpp b/libraries/physics/src/PhysicsEngine.cpp
index 90224caa0c..a46ba9f819 100644
--- a/libraries/physics/src/PhysicsEngine.cpp
+++ b/libraries/physics/src/PhysicsEngine.cpp
@@ -24,8 +24,7 @@ uint32_t PhysicsEngine::getNumSubsteps() {
}
PhysicsEngine::PhysicsEngine(const glm::vec3& offset)
- : _originOffset(offset),
- _avatarShapeLocalOffset(0.0f) {
+ : _originOffset(offset) {
}
PhysicsEngine::~PhysicsEngine() {
@@ -279,9 +278,6 @@ void PhysicsEngine::init(EntityEditPacketSender* packetSender) {
}
void PhysicsEngine::stepSimulation() {
- // expect the engine to have an avatar (and hence: a character controller)
- assert(_avatarData);
-
lock();
// NOTE: the grand order of operations is:
// (1) relay incoming changes
@@ -298,19 +294,11 @@ void PhysicsEngine::stepSimulation() {
_clock.reset();
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).
+ if (_characterController) {
+ _characterController->preSimulation(timeStep);
+ }
+
int numSubsteps = _dynamicsWorld->stepSimulation(timeStep, MAX_NUM_SUBSTEPS, PHYSICS_ENGINE_FIXED_SUBSTEP);
_numSubsteps += (uint32_t)numSubsteps;
stepNonPhysicalKinematics(usecTimestampNow());
@@ -326,21 +314,11 @@ void PhysicsEngine::stepSimulation() {
//
// TODO: untangle these lock sequences.
_entityTree->lockForWrite();
-
lock();
_dynamicsWorld->synchronizeMotionStates();
- _avatarData->lockForRead();
- bool avatarHasPhysicsEnabled = _avatarData->isPhysicsEnabled();
- _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();
+ if (_characterController) {
+ _characterController->postSimulation();
}
unlock();
@@ -620,76 +598,35 @@ bool PhysicsEngine::updateObjectHard(btRigidBody* body, ObjectMotionState* motio
return true;
}
-
-
void PhysicsEngine::setAvatarData(AvatarData *avatarData) {
- assert(avatarData); // don't pass NULL argument
-
- // compute capsule dimensions
- AABox box = avatarData->getLocalAABox();
- 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;
-
- if (!_avatarData) {
- // _avatarData is being initialized
- _avatarData = avatarData;
- } else {
- // _avatarData is being updated
- assert(_avatarData == avatarData);
-
- // get old dimensions from shape
- btCapsuleShape* capsule = static_cast(_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;
+ if (_characterController) {
+ bool needsShapeUpdate = _characterController->needsShapeUpdate();
+ if (needsShapeUpdate) {
+ lock();
+ // remove old info
+ _dynamicsWorld->removeCollisionObject(_characterController->getGhostObject());
+ _dynamicsWorld->removeAction(_characterController);
+ // update shape
+ _characterController->updateShape();
+ // insert new info
+ _dynamicsWorld->addCollisionObject(_characterController->getGhostObject(),
+ btBroadphaseProxy::CharacterFilter,
+ btBroadphaseProxy::StaticFilter | btBroadphaseProxy::DefaultFilter);
+ _dynamicsWorld->addAction(_characterController);
+ _characterController->reset(_dynamicsWorld);
+ unlock();
}
-
- // delete old controller and friends
- _dynamicsWorld->removeCollisionObject(_avatarGhostObject);
- _dynamicsWorld->removeAction(_characterController);
- delete _characterController;
- _characterController = NULL;
- delete _avatarGhostObject;
- _avatarGhostObject = NULL;
- delete capsule;
+ } else {
+ // initialize _characterController
+ assert(avatarData); // don't pass NULL argument
+ lock();
+ _characterController = new CharacterController(avatarData);
+ _dynamicsWorld->addCollisionObject(_characterController->getGhostObject(),
+ btBroadphaseProxy::CharacterFilter,
+ btBroadphaseProxy::StaticFilter | btBroadphaseProxy::DefaultFilter);
+ _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);
}
+
diff --git a/libraries/physics/src/PhysicsEngine.h b/libraries/physics/src/PhysicsEngine.h
index acf1617b16..cb637c60b9 100644
--- a/libraries/physics/src/PhysicsEngine.h
+++ b/libraries/physics/src/PhysicsEngine.h
@@ -123,9 +123,6 @@ private:
/// character collisions
CharacterController* _characterController = NULL;
- class btPairCachingGhostObject* _avatarGhostObject = NULL;
- AvatarData* _avatarData = NULL;
- glm::vec3 _avatarShapeLocalOffset;
};
#endif // hifi_PhysicsEngine_h
diff --git a/libraries/render-utils/src/Model.cpp b/libraries/render-utils/src/Model.cpp
index 0e0f081ec8..873f458ccf 100644
--- a/libraries/render-utils/src/Model.cpp
+++ b/libraries/render-utils/src/Model.cpp
@@ -725,21 +725,21 @@ bool Model::renderCore(float alpha, RenderMode mode, RenderArgs* args) {
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;
- opaqueMeshPartsRendered += renderMeshes(batch, mode, false, DEFAULT_ALPHA_THRESHOLD, false, false, false, false, args);
- opaqueMeshPartsRendered += renderMeshes(batch, mode, false, DEFAULT_ALPHA_THRESHOLD, false, false, false, true, args);
- opaqueMeshPartsRendered += renderMeshes(batch, mode, false, DEFAULT_ALPHA_THRESHOLD, false, false, true, false, args);
- opaqueMeshPartsRendered += renderMeshes(batch, mode, false, DEFAULT_ALPHA_THRESHOLD, false, false, true, true, args);
- opaqueMeshPartsRendered += renderMeshes(batch, mode, false, DEFAULT_ALPHA_THRESHOLD, false, true, false, false, args);
- opaqueMeshPartsRendered += renderMeshes(batch, mode, false, DEFAULT_ALPHA_THRESHOLD, false, true, false, true, args);
- opaqueMeshPartsRendered += renderMeshes(batch, mode, false, DEFAULT_ALPHA_THRESHOLD, false, true, true, false, args);
- opaqueMeshPartsRendered += renderMeshes(batch, mode, false, DEFAULT_ALPHA_THRESHOLD, false, true, true, true, 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, true);
+ 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, true);
+ 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, true);
+ 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, true);
- opaqueMeshPartsRendered += renderMeshes(batch, mode, false, DEFAULT_ALPHA_THRESHOLD, true, false, false, false, args);
- opaqueMeshPartsRendered += renderMeshes(batch, mode, false, DEFAULT_ALPHA_THRESHOLD, true, false, true, false, args);
- opaqueMeshPartsRendered += renderMeshes(batch, mode, false, DEFAULT_ALPHA_THRESHOLD, true, true, false, false, args);
- opaqueMeshPartsRendered += renderMeshes(batch, mode, false, DEFAULT_ALPHA_THRESHOLD, true, true, true, 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, true);
+ 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, true);
// render translucent meshes afterwards
//DependencyManager::get()->setPrimaryDrawBuffers(false, true, true);
@@ -753,14 +753,14 @@ bool Model::renderCore(float alpha, RenderMode mode, RenderArgs* args) {
int translucentMeshPartsRendered = 0;
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, true, args);
- translucentMeshPartsRendered += renderMeshes(batch, mode, true, MOSTLY_OPAQUE_THRESHOLD, false, false, true, false, args);
- translucentMeshPartsRendered += renderMeshes(batch, mode, true, MOSTLY_OPAQUE_THRESHOLD, false, false, true, true, args);
- translucentMeshPartsRendered += renderMeshes(batch, mode, true, MOSTLY_OPAQUE_THRESHOLD, false, true, false, false, args);
- translucentMeshPartsRendered += renderMeshes(batch, mode, true, MOSTLY_OPAQUE_THRESHOLD, false, true, false, true, args);
- translucentMeshPartsRendered += renderMeshes(batch, mode, true, MOSTLY_OPAQUE_THRESHOLD, false, true, true, false, args);
- translucentMeshPartsRendered += renderMeshes(batch, mode, true, MOSTLY_OPAQUE_THRESHOLD, false, true, true, true, 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, true);
+ 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, true);
+ 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, true);
+ 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, true);
GLBATCH(glDisable)(GL_ALPHA_TEST);
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) {
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, true, args);
- translucentMeshPartsRendered += renderMeshes(batch, mode, true, MOSTLY_TRANSPARENT_THRESHOLD, false, false, true, false, args);
- translucentMeshPartsRendered += renderMeshes(batch, mode, true, MOSTLY_TRANSPARENT_THRESHOLD, false, false, true, true, args);
- translucentMeshPartsRendered += renderMeshes(batch, mode, true, MOSTLY_TRANSPARENT_THRESHOLD, false, true, false, false, args);
- translucentMeshPartsRendered += renderMeshes(batch, mode, true, MOSTLY_TRANSPARENT_THRESHOLD, false, true, false, true, args);
- translucentMeshPartsRendered += renderMeshes(batch, mode, true, MOSTLY_TRANSPARENT_THRESHOLD, false, true, true, false, args);
- translucentMeshPartsRendered += renderMeshes(batch, mode, true, MOSTLY_TRANSPARENT_THRESHOLD, false, true, true, true, 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, true);
+ 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, true);
+ 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, true);
+ 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, true);
}
GLBATCH(glDepthMask)(true);
@@ -842,10 +842,67 @@ bool Model::renderCore(float alpha, RenderMode mode, RenderArgs* args) {
args->_translucentMeshPartsRendered = translucentMeshPartsRendered;
args->_opaqueMeshPartsRendered = opaqueMeshPartsRendered;
}
-
+
+ #ifdef WANT_DEBUG_MESHBOXES
+ renderDebugMeshBoxes();
+ #endif
+
return true;
}
+void Model::renderDebugMeshBoxes() {
+ int colorNdx = 0;
+ foreach(AABox box, _calculatedMeshBoxes) {
+ if (_debugMeshBoxesID == GeometryCache::UNKNOWN_ID) {
+ _debugMeshBoxesID = DependencyManager::get()->allocateID();
+ }
+ QVector 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()->updateVertices(_debugMeshBoxesID, points, color[colorNdx]);
+ DependencyManager::get()->renderVertices(gpu::LINES, _debugMeshBoxesID);
+ colorNdx++;
+ }
+}
+
Extents Model::getBindExtents() const {
if (!isActive()) {
return Extents();
@@ -1306,8 +1363,11 @@ void Model::updateJointState(int index) {
glm::mat4 parentTransform = glm::scale(_scale) * glm::translate(_offset) * geometry.offset;
state.computeTransform(parentTransform);
} else {
- const JointState& parentState = _jointStates.at(parentIndex);
- state.computeTransform(parentState.getTransform(), parentState.getTransformChanged());
+ // guard against out-of-bounds access to _jointStates
+ 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,
- 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__);
int meshPartsRendered = 0;
@@ -2319,8 +2380,10 @@ int Model::renderMeshes(gpu::Batch& batch, RenderMode mode, bool translucent, fl
Locations* locations;
SkinLocations* skinLocations;
- pickPrograms(batch, mode, translucent, alphaThreshold, hasLightmap, hasTangents, hasSpecular, isSkinned, args, locations, skinLocations);
- meshPartsRendered = renderMeshesFromList(list, batch, mode, translucent, alphaThreshold, args, locations, skinLocations);
+ pickPrograms(batch, mode, translucent, alphaThreshold, hasLightmap, hasTangents, hasSpecular, isSkinned,
+ args, locations, skinLocations);
+ meshPartsRendered = renderMeshesFromList(list, batch, mode, translucent, alphaThreshold,
+ args, locations, skinLocations, forceRenderSomeMeshes);
GLBATCH(glUseProgram)(0);
return meshPartsRendered;
@@ -2328,7 +2391,7 @@ int Model::renderMeshes(gpu::Batch& batch, RenderMode mode, bool translucent, fl
int Model::renderMeshesFromList(QVector& 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__);
auto textureCache = DependencyManager::get();
@@ -2364,11 +2427,21 @@ int Model::renderMeshesFromList(QVector& list, gpu::Batch& batch, RenderMod
// if we got here, then check to see if this mesh is in view
if (args) {
bool shouldRender = true;
+ bool forceRender = false;
args->_meshesConsidered++;
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());
shouldRender = !_viewState ? false : _viewState->shouldRenderMesh(_calculatedMeshBoxes.at(i).getLargestDimension(),
distance);
diff --git a/libraries/render-utils/src/Model.h b/libraries/render-utils/src/Model.h
index 5114ef1c9f..05db20b056 100644
--- a/libraries/render-utils/src/Model.h
+++ b/libraries/render-utils/src/Model.h
@@ -444,6 +444,9 @@ private:
QVector _meshesOpaqueLightmapTangentsSpecular;
QVector _meshesOpaqueLightmapSpecular;
+ // debug rendering support
+ void renderDebugMeshBoxes();
+ int _debugMeshBoxesID = GeometryCache::UNKNOWN_ID;
// Scene rendering support
static QVector _modelsInScene;
@@ -456,12 +459,15 @@ private:
void renderSetup(RenderArgs* args);
bool renderCore(float alpha, RenderMode mode, RenderArgs* args);
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);
QVector* pickMeshList(bool translucent, float alphaThreshold, bool hasLightmap, bool hasTangents, bool hasSpecular, bool isSkinned);
int renderMeshesFromList(QVector& 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,
bool hasLightmap, bool hasTangents, bool hasSpecular, bool isSkinned, RenderArgs* args,