Merge branch 'master' of https://github.com/highfidelity/hifi into light_types

This commit is contained in:
Atlante45 2015-03-25 16:39:11 +01:00
commit 725a8795fb
26 changed files with 3224 additions and 2810 deletions

View file

@ -18,4 +18,3 @@ Script.load("lobby.js");
Script.load("notifications.js"); Script.load("notifications.js");
Script.load("look.js"); Script.load("look.js");
Script.load("users.js"); Script.load("users.js");
Script.load("utilities/LODWarning.js");

View file

@ -0,0 +1,157 @@
//
// makeHouses.js
//
//
// Created by Stojce Slavkovski on March 14, 2015
// Copyright 2015 High Fidelity, Inc.
//
// This sample script that creates house entities based on parameters.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
(function () {
/** options **/
var numHouses = 100;
var xRange = 300;
var yRange = 300;
var sizeOfTheHouse = {
x: 10,
y: 15,
z: 10
};
var randomizeModels = false;
/**/
var modelUrlPrefix = "http://public.highfidelity.io/load_testing/3-Buildings-2-SanFranciscoHouse-";
var modelurlExt = ".fbx";
var modelVariations = 100;
var houses = [];
function addHouseAt(position, rotation) {
// get house model
var modelNumber = randomizeModels ?
1 + Math.floor(Math.random() * (modelVariations - 1)) :
(houses.length + 1) % modelVariations;
if (modelNumber == 0) {
modelNumber = modelVariations;
}
var modelUrl = modelUrlPrefix + (modelNumber + "") + modelurlExt;
print("Model ID:" + modelNumber);
print("Model URL:" + modelUrl);
var properties = {
type: "Model",
position: position,
rotation: rotation,
dimensions: sizeOfTheHouse,
modelURL: modelUrl
};
return Entities.addEntity(properties);
}
// calculate initial position
var posX = MyAvatar.position.x - (xRange / 2);
var measures = calculateParcels(numHouses, xRange, yRange);
var dd = 0;
// avatar facing rotation
var rotEven = Quat.fromPitchYawRollDegrees(0, 270.0 + MyAvatar.bodyYaw, 0.0);
// avatar opposite rotation
var rotOdd = Quat.fromPitchYawRollDegrees(0, 90.0 + MyAvatar.bodyYaw, 0.0);
var housePos = Vec3.sum(MyAvatar.position, Quat.getFront(Camera.getOrientation()));
for (var j = 0; j < measures.rows; j++) {
var posX1 = 0 - (xRange / 2);
dd += measures.parcelLength;
for (var i = 0; i < measures.cols; i++) {
// skip reminder of houses
if (houses.length > numHouses) {
break;
}
var posShift = {
x: posX1,
y: 0,
z: dd
};
print("House nr.:" + (houses.length + 1));
houses.push(
addHouseAt(Vec3.sum(housePos, posShift), (j % 2 == 0) ? rotEven : rotOdd)
);
posX1 += measures.parcelWidth;
}
}
// calculate rows and columns in area, and dimension of single parcel
function calculateParcels(items, areaWidth, areaLength) {
var idealSize = Math.min(Math.sqrt(areaWidth * areaLength / items), areaWidth, areaLength);
var baseWidth = Math.min(Math.floor(areaWidth / idealSize), items);
var baseLength = Math.min(Math.floor(areaLength / idealSize), items);
var sirRows = baseWidth;
var sirCols = Math.ceil(items / sirRows);
var sirW = areaWidth / sirRows;
var sirL = areaLength / sirCols;
var visCols = baseLength;
var visRows = Math.ceil(items / visCols);
var visW = areaWidth / visRows;
var visL = areaLength / visCols;
var rows = 0;
var cols = 0;
var parcelWidth = 0;
var parcelLength = 0;
if (Math.min(sirW, sirL) > Math.min(visW, visL)) {
rows = sirRows;
cols = sirCols;
parcelWidth = sirW;
parcelLength = sirL;
} else {
rows = visRows;
cols = visCols;
parcelWidth = visW;
parcelLength = visL;
}
print("rows:" + rows);
print("cols:" + cols);
print("parcelWidth:" + parcelWidth);
print("parcelLength:" + parcelLength);
return {
rows: rows,
cols: cols,
parcelWidth: parcelWidth,
parcelLength: parcelLength
};
}
function cleanup() {
while (houses.length > 0) {
if (!houses[0].isKnownID) {
houses[0] = Entities.identifyEntity(houses[0]);
}
Entities.deleteEntity(houses.shift());
}
}
Script.scriptEnding.connect(cleanup);
})();

View file

@ -13,5 +13,4 @@ Script.load("progress.js");
Script.load("lobby.js"); Script.load("lobby.js");
Script.load("notifications.js"); Script.load("notifications.js");
Script.load("controllers/oculus/goTo.js"); Script.load("controllers/oculus/goTo.js");
Script.load("utilities/LODWarning.js");
//Script.load("scripts.js"); // Not created yet //Script.load("scripts.js"); // Not created yet

View file

@ -43,7 +43,6 @@
// after that we will send it to createNotification(text). // after that we will send it to createNotification(text).
// If the message is 42 chars or less you should bypass wordWrap() and call createNotification() directly. // If the message is 42 chars or less you should bypass wordWrap() and call createNotification() directly.
// To add a keypress driven notification: // To add a keypress driven notification:
// //
// 1. Add a key to the keyPressEvent(key). // 1. Add a key to the keyPressEvent(key).
@ -85,16 +84,19 @@ var PLAY_NOTIFICATION_SOUNDS_MENU_ITEM = "Play Notification Sounds";
var NOTIFICATION_MENU_ITEM_POST = " Notifications"; var NOTIFICATION_MENU_ITEM_POST = " Notifications";
var PLAY_NOTIFICATION_SOUNDS_SETTING = "play_notification_sounds"; var PLAY_NOTIFICATION_SOUNDS_SETTING = "play_notification_sounds";
var PLAY_NOTIFICATION_SOUNDS_TYPE_SETTING_PRE = "play_notification_sounds_type_"; var PLAY_NOTIFICATION_SOUNDS_TYPE_SETTING_PRE = "play_notification_sounds_type_";
var lodTextID = false;
var NotificationType = { var NotificationType = {
UNKNOWN: 0, UNKNOWN: 0,
MUTE_TOGGLE: 1, MUTE_TOGGLE: 1,
SNAPSHOT: 2, SNAPSHOT: 2,
WINDOW_RESIZE: 3, WINDOW_RESIZE: 3,
LOD_WARNING: 4,
properties: [ properties: [
{ text: "Mute Toggle" }, { text: "Mute Toggle" },
{ text: "Snapshot" }, { text: "Snapshot" },
{ text: "Window Resize" } { text: "Window Resize" },
{ text: "Level of Detail" }
], ],
getTypeFromMenuItem: function(menuItemName) { getTypeFromMenuItem: function(menuItemName) {
if (menuItemName.substr(menuItemName.length - NOTIFICATION_MENU_ITEM_POST.length) !== NOTIFICATION_MENU_ITEM_POST) { if (menuItemName.substr(menuItemName.length - NOTIFICATION_MENU_ITEM_POST.length) !== NOTIFICATION_MENU_ITEM_POST) {
@ -143,6 +145,10 @@ function createArrays(notice, button, createTime, height, myAlpha) {
// This handles the final dismissal of a notification after fading // This handles the final dismissal of a notification after fading
function dismiss(firstNoteOut, firstButOut, firstOut) { function dismiss(firstNoteOut, firstButOut, firstOut) {
if (firstNoteOut == lodTextID) {
lodTextID = false;
}
Overlays.deleteOverlay(firstNoteOut); Overlays.deleteOverlay(firstNoteOut);
Overlays.deleteOverlay(firstButOut); Overlays.deleteOverlay(firstButOut);
notifications.splice(firstOut, 1); notifications.splice(firstOut, 1);
@ -261,7 +267,8 @@ function notify(notice, button, height) {
height: noticeHeight height: noticeHeight
}); });
} else { } else {
notifications.push((Overlays.addOverlay("text", notice))); var notificationText = Overlays.addOverlay("text", notice);
notifications.push((notificationText));
buttons.push((Overlays.addOverlay("image", button))); buttons.push((Overlays.addOverlay("image", button)));
} }
@ -272,6 +279,7 @@ function notify(notice, button, height) {
last = notifications.length - 1; last = notifications.length - 1;
createArrays(notifications[last], buttons[last], times[last], heights[last], myAlpha[last]); createArrays(notifications[last], buttons[last], times[last], heights[last], myAlpha[last]);
fadeIn(notifications[last], buttons[last]); fadeIn(notifications[last], buttons[last]);
return notificationText;
} }
// This function creates and sizes the overlays // This function creates and sizes the overlays
@ -331,11 +339,15 @@ function createNotification(text, notificationType) {
randomSounds.playRandom(); randomSounds.playRandom();
} }
notify(noticeProperties, buttonProperties, height); return notify(noticeProperties, buttonProperties, height);
} }
function deleteNotification(index) { function deleteNotification(index) {
Overlays.deleteOverlay(notifications[index]); var notificationTextID = notifications[index];
if (notificationTextID == lodTextID) {
lodTextID = false;
}
Overlays.deleteOverlay(notificationTextID);
Overlays.deleteOverlay(buttons[index]); Overlays.deleteOverlay(buttons[index]);
notifications.splice(index, 1); notifications.splice(index, 1);
buttons.splice(index, 1); buttons.splice(index, 1);
@ -575,6 +587,20 @@ function menuItemEvent(menuItem) {
} }
} }
LODManager.LODDecreased.connect(function() {
var warningText = "\n"
+ "Due to the complexity of the content, the \n"
+ "level of detail has been decreased."
+ "You can now see: \n"
+ LODManager.getLODFeedbackText();
if (lodTextID == false) {
lodTextID = createNotification(warningText, NotificationType.LOD_WARNING);
} else {
Overlays.editOverlay(lodTextID, { text: warningText });
}
});
AudioDevice.muteToggled.connect(onMuteStateChanged); AudioDevice.muteToggled.connect(onMuteStateChanged);
Controller.keyPressEvent.connect(keyPressEvent); Controller.keyPressEvent.connect(keyPressEvent);
Controller.mousePressEvent.connect(mousePressEvent); Controller.mousePressEvent.connect(mousePressEvent);

View file

@ -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);
});

View file

@ -2,7 +2,7 @@
Instructions for adding the Sixense driver to Interface Instructions for adding the Sixense driver to Interface
Andrzej Kapolka, November 18, 2013 Andrzej Kapolka, November 18, 2013
1. Copy the Sixense sdk folders (lib, include) into the interface/external/Sixense folder. This readme.txt should be there as well. 1. Copy the Sixense sdk folders (bin, include, lib, and samples) into the interface/external/Sixense folder. This readme.txt should be there as well.
You may optionally choose to copy the SDK folders to a location outside the repository (so you can re-use with different checkouts and different projects). You may optionally choose to copy the SDK folders to a location outside the repository (so you can re-use with different checkouts and different projects).
If so our CMake find module expects you to set the ENV variable 'HIFI_LIB_DIR' to a directory containing a subfolder 'sixense' that contains the folders mentioned above. If so our CMake find module expects you to set the ENV variable 'HIFI_LIB_DIR' to a directory containing a subfolder 'sixense' that contains the folders mentioned above.

File diff suppressed because it is too large Load diff

After

Width:  |  Height:  |  Size: 197 KiB

File diff suppressed because it is too large Load diff

Before

Width:  |  Height:  |  Size: 193 KiB

View file

@ -383,6 +383,10 @@ Application::Application(int& argc, char** argv, QElapsedTimer &startup_time) :
connect(locationUpdateTimer, &QTimer::timeout, discoverabilityManager.data(), &DiscoverabilityManager::updateLocation); connect(locationUpdateTimer, &QTimer::timeout, discoverabilityManager.data(), &DiscoverabilityManager::updateLocation);
locationUpdateTimer->start(DATA_SERVER_LOCATION_CHANGE_UPDATE_MSECS); locationUpdateTimer->start(DATA_SERVER_LOCATION_CHANGE_UPDATE_MSECS);
// if we get a domain change, immediately attempt update location in metaverse server
connect(&nodeList->getDomainHandler(), &DomainHandler::connectedToDomain,
discoverabilityManager.data(), &DiscoverabilityManager::updateLocation);
connect(nodeList.data(), &NodeList::nodeAdded, this, &Application::nodeAdded); connect(nodeList.data(), &NodeList::nodeAdded, this, &Application::nodeAdded);
connect(nodeList.data(), &NodeList::nodeKilled, this, &Application::nodeKilled); connect(nodeList.data(), &NodeList::nodeKilled, this, &Application::nodeKilled);
connect(nodeList.data(), SIGNAL(nodeKilled(SharedNodePointer)), SLOT(nodeKilled(SharedNodePointer))); connect(nodeList.data(), SIGNAL(nodeKilled(SharedNodePointer)), SLOT(nodeKilled(SharedNodePointer)));
@ -1786,6 +1790,7 @@ bool Application::exportEntities(const QString& filename, float x, float y, floa
void Application::loadSettings() { void Application::loadSettings() {
DependencyManager::get<AudioClient>()->loadSettings(); DependencyManager::get<AudioClient>()->loadSettings();
DependencyManager::get<LODManager>()->loadSettings();
Menu::getInstance()->loadSettings(); Menu::getInstance()->loadSettings();
_myAvatar->loadData(); _myAvatar->loadData();
@ -1793,6 +1798,7 @@ void Application::loadSettings() {
void Application::saveSettings() { void Application::saveSettings() {
DependencyManager::get<AudioClient>()->saveSettings(); DependencyManager::get<AudioClient>()->saveSettings();
DependencyManager::get<LODManager>()->saveSettings();
Menu::getInstance()->saveSettings(); Menu::getInstance()->saveSettings();
_myAvatar->saveData(); _myAvatar->saveData();
@ -1969,7 +1975,7 @@ bool Application::isLookingAtMyAvatar(Avatar* avatar) {
void Application::updateLOD() { void Application::updateLOD() {
PerformanceTimer perfTimer("LOD"); PerformanceTimer perfTimer("LOD");
// adjust it unless we were asked to disable this feature, or if we're currently in throttleRendering mode // adjust it unless we were asked to disable this feature, or if we're currently in throttleRendering mode
if (!Menu::getInstance()->isOptionChecked(MenuOption::DisableAutoAdjustLOD) && !isThrottleRendering()) { if (!isThrottleRendering()) {
DependencyManager::get<LODManager>()->autoAdjustLOD(_fps); DependencyManager::get<LODManager>()->autoAdjustLOD(_fps);
} else { } else {
DependencyManager::get<LODManager>()->resetLODAdjust(); DependencyManager::get<LODManager>()->resetLODAdjust();
@ -3295,11 +3301,6 @@ void Application::connectedToDomain(const QString& hostname) {
const QUuid& domainID = DependencyManager::get<NodeList>()->getDomainHandler().getUUID(); const QUuid& domainID = DependencyManager::get<NodeList>()->getDomainHandler().getUUID();
if (accountManager.isLoggedIn() && !domainID.isNull()) { if (accountManager.isLoggedIn() && !domainID.isNull()) {
// update our data-server with the domain-server we're logged in with
QString domainPutJsonString = "{\"location\":{\"domain_id\":\"" + uuidStringWithoutCurlyBraces(domainID) + "\"}}";
accountManager.authenticatedRequest("/api/v1/user/location", QNetworkAccessManager::PutOperation,
JSONCallbackParameters(), domainPutJsonString.toUtf8());
_notifiedPacketVersionMismatchThisDomain = false; _notifiedPacketVersionMismatchThisDomain = false;
} }
} }

View file

@ -113,7 +113,7 @@ static const float MIRROR_FIELD_OF_VIEW = 30.0f;
static const quint64 TOO_LONG_SINCE_LAST_SEND_DOWNSTREAM_AUDIO_STATS = 1 * USECS_PER_SECOND; static const quint64 TOO_LONG_SINCE_LAST_SEND_DOWNSTREAM_AUDIO_STATS = 1 * USECS_PER_SECOND;
static const QString INFO_HELP_PATH = "html/interface-welcome.html"; static const QString INFO_HELP_PATH = "html/interface-welcome.html";
static const QString INFO_EDIT_ENTITIES_PATH = "html/edit-entities-commands.html"; static const QString INFO_EDIT_ENTITIES_PATH = "html/edit-commands.html";
#ifdef Q_OS_WIN #ifdef Q_OS_WIN
static const UINT UWM_IDENTIFY_INSTANCES = static const UINT UWM_IDENTIFY_INSTANCES =

View file

@ -17,13 +17,26 @@
#include "LODManager.h" #include "LODManager.h"
Setting::Handle<bool> automaticAvatarLOD("automaticAvatarLOD", true); Setting::Handle<float> desktopLODDecreaseFPS("desktopLODDecreaseFPS", DEFAULT_DESKTOP_LOD_DOWN_FPS);
Setting::Handle<float> avatarLODDecreaseFPS("avatarLODDecreaseFPS", DEFAULT_ADJUST_AVATAR_LOD_DOWN_FPS); Setting::Handle<float> hmdLODDecreaseFPS("hmdLODDecreaseFPS", DEFAULT_HMD_LOD_DOWN_FPS);
Setting::Handle<float> avatarLODIncreaseFPS("avatarLODIncreaseFPS", ADJUST_LOD_UP_FPS);
Setting::Handle<float> avatarLODDistanceMultiplier("avatarLODDistanceMultiplier", LODManager::LODManager() {
DEFAULT_AVATAR_LOD_DISTANCE_MULTIPLIER); calculateAvatarLODDistanceMultiplier();
Setting::Handle<int> boundaryLevelAdjust("boundaryLevelAdjust", 0); }
Setting::Handle<float> octreeSizeScale("octreeSizeScale", DEFAULT_OCTREE_SIZE_SCALE);
float LODManager::getLODDecreaseFPS() {
if (Application::getInstance()->isHMDMode()) {
return getHMDLODDecreaseFPS();
}
return getDesktopLODDecreaseFPS();
}
float LODManager::getLODIncreaseFPS() {
if (Application::getInstance()->isHMDMode()) {
return getHMDLODIncreaseFPS();
}
return getDesktopLODIncreaseFPS();
}
void LODManager::autoAdjustLOD(float currentFPS) { void LODManager::autoAdjustLOD(float currentFPS) {
@ -40,65 +53,64 @@ void LODManager::autoAdjustLOD(float currentFPS) {
quint64 now = usecTimestampNow(); quint64 now = usecTimestampNow();
const quint64 ADJUST_AVATAR_LOD_DOWN_DELAY = 1000 * 1000;
if (_automaticAvatarLOD) {
if (_fastFPSAverage.getAverage() < _avatarLODDecreaseFPS) {
if (now - _lastAvatarDetailDrop > ADJUST_AVATAR_LOD_DOWN_DELAY) {
// attempt to lower the detail in proportion to the fps difference
float targetFps = (_avatarLODDecreaseFPS + _avatarLODIncreaseFPS) * 0.5f;
float averageFps = _fastFPSAverage.getAverage();
const float MAXIMUM_MULTIPLIER_SCALE = 2.0f;
_avatarLODDistanceMultiplier = qMin(MAXIMUM_AVATAR_LOD_DISTANCE_MULTIPLIER, _avatarLODDistanceMultiplier *
(averageFps < EPSILON ? MAXIMUM_MULTIPLIER_SCALE :
qMin(MAXIMUM_MULTIPLIER_SCALE, targetFps / averageFps)));
_lastAvatarDetailDrop = now;
}
} else if (_fastFPSAverage.getAverage() > _avatarLODIncreaseFPS) {
// let the detail level creep slowly upwards
const float DISTANCE_DECREASE_RATE = 0.05f;
_avatarLODDistanceMultiplier = qMax(MINIMUM_AVATAR_LOD_DISTANCE_MULTIPLIER,
_avatarLODDistanceMultiplier - DISTANCE_DECREASE_RATE);
}
}
bool changed = false; bool changed = false;
bool octreeChanged = false;
quint64 elapsed = now - _lastAdjust; quint64 elapsed = now - _lastAdjust;
if (elapsed > ADJUST_LOD_DOWN_DELAY && _fpsAverage.getAverage() < ADJUST_LOD_DOWN_FPS if (_automaticLODAdjust) {
&& _octreeSizeScale > ADJUST_LOD_MIN_SIZE_SCALE) { // LOD Downward adjustment
if (elapsed > ADJUST_LOD_DOWN_DELAY && _fpsAverage.getAverage() < getLODDecreaseFPS()) {
_octreeSizeScale *= ADJUST_LOD_DOWN_BY; // Octree items... stepwise adjustment
if (_octreeSizeScale > ADJUST_LOD_MIN_SIZE_SCALE) {
_octreeSizeScale *= ADJUST_LOD_DOWN_BY;
if (_octreeSizeScale < ADJUST_LOD_MIN_SIZE_SCALE) {
_octreeSizeScale = ADJUST_LOD_MIN_SIZE_SCALE;
}
octreeChanged = changed = true;
}
if (_octreeSizeScale < ADJUST_LOD_MIN_SIZE_SCALE) { if (changed) {
_octreeSizeScale = ADJUST_LOD_MIN_SIZE_SCALE; _lastAdjust = now;
qDebug() << "adjusting LOD down... average fps for last approximately 5 seconds=" << _fpsAverage.getAverage()
<< "_octreeSizeScale=" << _octreeSizeScale;
emit LODDecreased();
}
} }
changed = true;
_lastAdjust = now;
qDebug() << "adjusting LOD down... average fps for last approximately 5 seconds=" << _fpsAverage.getAverage()
<< "_octreeSizeScale=" << _octreeSizeScale;
emit LODDecreased(); // LOD Upward adjustment
} if (elapsed > ADJUST_LOD_UP_DELAY && _fpsAverage.getAverage() > getLODIncreaseFPS()) {
if (elapsed > ADJUST_LOD_UP_DELAY && _fpsAverage.getAverage() > ADJUST_LOD_UP_FPS // Octee items... stepwise adjustment
&& _octreeSizeScale < ADJUST_LOD_MAX_SIZE_SCALE) { if (_octreeSizeScale < ADJUST_LOD_MAX_SIZE_SCALE) {
_octreeSizeScale *= ADJUST_LOD_UP_BY; if (_octreeSizeScale < ADJUST_LOD_MIN_SIZE_SCALE) {
if (_octreeSizeScale > ADJUST_LOD_MAX_SIZE_SCALE) { _octreeSizeScale = ADJUST_LOD_MIN_SIZE_SCALE;
_octreeSizeScale = ADJUST_LOD_MAX_SIZE_SCALE; } else {
_octreeSizeScale *= ADJUST_LOD_UP_BY;
}
if (_octreeSizeScale > ADJUST_LOD_MAX_SIZE_SCALE) {
_octreeSizeScale = ADJUST_LOD_MAX_SIZE_SCALE;
}
octreeChanged = changed = true;
}
if (changed) {
_lastAdjust = now;
qDebug() << "adjusting LOD up... average fps for last approximately 5 seconds=" << _fpsAverage.getAverage()
<< "_octreeSizeScale=" << _octreeSizeScale;
emit LODIncreased();
}
} }
changed = true;
_lastAdjust = now;
qDebug() << "adjusting LOD up... average fps for last approximately 5 seconds=" << _fpsAverage.getAverage()
<< "_octreeSizeScale=" << _octreeSizeScale;
emit LODIncreased(); if (changed) {
} calculateAvatarLODDistanceMultiplier();
_shouldRenderTableNeedsRebuilding = true;
if (changed) { auto lodToolsDialog = DependencyManager::get<DialogsManager>()->getLodToolsDialog();
_shouldRenderTableNeedsRebuilding = true; if (lodToolsDialog) {
auto lodToolsDialog = DependencyManager::get<DialogsManager>()->getLodToolsDialog(); lodToolsDialog->reloadSliders();
if (lodToolsDialog) { }
lodToolsDialog->reloadSliders();
} }
} }
} }
@ -106,7 +118,7 @@ void LODManager::autoAdjustLOD(float currentFPS) {
void LODManager::resetLODAdjust() { void LODManager::resetLODAdjust() {
_fpsAverage.reset(); _fpsAverage.reset();
_fastFPSAverage.reset(); _fastFPSAverage.reset();
_lastAvatarDetailDrop = _lastAdjust = usecTimestampNow(); _lastAdjust = usecTimestampNow();
} }
QString LODManager::getLODFeedbackText() { QString LODManager::getLODFeedbackText() {
@ -116,29 +128,33 @@ QString LODManager::getLODFeedbackText() {
switch (boundaryLevelAdjust) { switch (boundaryLevelAdjust) {
case 0: { case 0: {
granularityFeedback = QString("at standard granularity."); granularityFeedback = QString(".");
} break; } break;
case 1: { case 1: {
granularityFeedback = QString("at half of standard granularity."); granularityFeedback = QString(" at half of standard granularity.");
} break; } break;
case 2: { case 2: {
granularityFeedback = QString("at a third of standard granularity."); granularityFeedback = QString(" at a third of standard granularity.");
} break; } break;
default: { default: {
granularityFeedback = QString("at 1/%1th of standard granularity.").arg(boundaryLevelAdjust + 1); granularityFeedback = QString(" at 1/%1th of standard granularity.").arg(boundaryLevelAdjust + 1);
} break; } break;
} }
// distance feedback // distance feedback
float octreeSizeScale = getOctreeSizeScale(); float octreeSizeScale = getOctreeSizeScale();
float relativeToDefault = octreeSizeScale / DEFAULT_OCTREE_SIZE_SCALE; float relativeToDefault = octreeSizeScale / DEFAULT_OCTREE_SIZE_SCALE;
int relativeToTwentyTwenty = 20 / relativeToDefault;
QString result; QString result;
if (relativeToDefault > 1.01) { if (relativeToDefault > 1.01) {
result = QString("%1 further %2").arg(relativeToDefault,8,'f',2).arg(granularityFeedback); result = QString("20:%1 or %2 times further than average vision%3").arg(relativeToTwentyTwenty).arg(relativeToDefault,0,'f',2).arg(granularityFeedback);
} else if (relativeToDefault > 0.99) { } else if (relativeToDefault > 0.99) {
result = QString("the default distance %1").arg(granularityFeedback); result = QString("20:20 or the default distance for average vision%1").arg(granularityFeedback);
} else if (relativeToDefault > 0.01) {
result = QString("20:%1 or %2 of default distance for average vision%3").arg(relativeToTwentyTwenty).arg(relativeToDefault,0,'f',3).arg(granularityFeedback);
} else { } else {
result = QString("%1 of default %2").arg(relativeToDefault,8,'f',3).arg(granularityFeedback); result = QString("%2 of default distance for average vision%3").arg(relativeToDefault,0,'f',3).arg(granularityFeedback);
} }
return result; return result;
} }
@ -184,9 +200,14 @@ bool LODManager::shouldRenderMesh(float largestDimension, float distanceToCamera
void LODManager::setOctreeSizeScale(float sizeScale) { void LODManager::setOctreeSizeScale(float sizeScale) {
_octreeSizeScale = sizeScale; _octreeSizeScale = sizeScale;
calculateAvatarLODDistanceMultiplier();
_shouldRenderTableNeedsRebuilding = true; _shouldRenderTableNeedsRebuilding = true;
} }
void LODManager::calculateAvatarLODDistanceMultiplier() {
_avatarLODDistanceMultiplier = AVATAR_TO_ENTITY_RATIO / (_octreeSizeScale / DEFAULT_OCTREE_SIZE_SCALE);
}
void LODManager::setBoundaryLevelAdjust(int boundaryLevelAdjust) { void LODManager::setBoundaryLevelAdjust(int boundaryLevelAdjust) {
_boundaryLevelAdjust = boundaryLevelAdjust; _boundaryLevelAdjust = boundaryLevelAdjust;
_shouldRenderTableNeedsRebuilding = true; _shouldRenderTableNeedsRebuilding = true;
@ -194,21 +215,13 @@ void LODManager::setBoundaryLevelAdjust(int boundaryLevelAdjust) {
void LODManager::loadSettings() { void LODManager::loadSettings() {
setAutomaticAvatarLOD(automaticAvatarLOD.get()); setDesktopLODDecreaseFPS(desktopLODDecreaseFPS.get());
setAvatarLODDecreaseFPS(avatarLODDecreaseFPS.get()); setHMDLODDecreaseFPS(hmdLODDecreaseFPS.get());
setAvatarLODIncreaseFPS(avatarLODIncreaseFPS.get());
setAvatarLODDistanceMultiplier(avatarLODDistanceMultiplier.get());
setBoundaryLevelAdjust(boundaryLevelAdjust.get());
setOctreeSizeScale(octreeSizeScale.get());
} }
void LODManager::saveSettings() { void LODManager::saveSettings() {
automaticAvatarLOD.set(getAutomaticAvatarLOD()); desktopLODDecreaseFPS.set(getDesktopLODDecreaseFPS());
avatarLODDecreaseFPS.set(getAvatarLODDecreaseFPS()); hmdLODDecreaseFPS.set(getHMDLODDecreaseFPS());
avatarLODIncreaseFPS.set(getAvatarLODIncreaseFPS());
avatarLODDistanceMultiplier.set(getAvatarLODDistanceMultiplier());
boundaryLevelAdjust.set(getBoundaryLevelAdjust());
octreeSizeScale.set(getOctreeSizeScale());
} }

View file

@ -17,9 +17,9 @@
#include <SharedUtil.h> #include <SharedUtil.h>
#include <SimpleMovingAverage.h> #include <SimpleMovingAverage.h>
const float ADJUST_LOD_DOWN_FPS = 40.0; const float DEFAULT_DESKTOP_LOD_DOWN_FPS = 30.0;
const float ADJUST_LOD_UP_FPS = 55.0; const float DEFAULT_HMD_LOD_DOWN_FPS = 60.0;
const float DEFAULT_ADJUST_AVATAR_LOD_DOWN_FPS = 30.0f; const float INCREASE_LOD_GAP = 5.0f;
const quint64 ADJUST_LOD_DOWN_DELAY = 1000 * 1000 * 0.5; // Consider adjusting LOD down after half a second const quint64 ADJUST_LOD_DOWN_DELAY = 1000 * 1000 * 0.5; // Consider adjusting LOD down after half a second
const quint64 ADJUST_LOD_UP_DELAY = ADJUST_LOD_DOWN_DELAY * 2; const quint64 ADJUST_LOD_UP_DELAY = ADJUST_LOD_DOWN_DELAY * 2;
@ -33,9 +33,9 @@ const float ADJUST_LOD_UP_BY = 1.1f;
const float ADJUST_LOD_MIN_SIZE_SCALE = 1.0f; const float ADJUST_LOD_MIN_SIZE_SCALE = 1.0f;
const float ADJUST_LOD_MAX_SIZE_SCALE = DEFAULT_OCTREE_SIZE_SCALE; const float ADJUST_LOD_MAX_SIZE_SCALE = DEFAULT_OCTREE_SIZE_SCALE;
const float MINIMUM_AVATAR_LOD_DISTANCE_MULTIPLIER = 0.1f; // The ratio of "visibility" of avatars to other content. A value larger than 1 will mean Avatars "cull" later than entities
const float MAXIMUM_AVATAR_LOD_DISTANCE_MULTIPLIER = 15.0f; // do. But both are still culled using the same angular size logic.
const float DEFAULT_AVATAR_LOD_DISTANCE_MULTIPLIER = 1.0f; const float AVATAR_TO_ENTITY_RATIO = 2.0f;
const int ONE_SECOND_OF_FRAMES = 60; const int ONE_SECOND_OF_FRAMES = 60;
const int FIVE_SECONDS_OF_FRAMES = 5 * ONE_SECOND_OF_FRAMES; const int FIVE_SECONDS_OF_FRAMES = 5 * ONE_SECOND_OF_FRAMES;
@ -46,14 +46,18 @@ class LODManager : public QObject, public Dependency {
SINGLETON_DEPENDENCY SINGLETON_DEPENDENCY
public: public:
void setAutomaticAvatarLOD(bool automaticAvatarLOD) { _automaticAvatarLOD = automaticAvatarLOD; } Q_INVOKABLE void setAutomaticLODAdjust(bool value) { _automaticLODAdjust = value; }
bool getAutomaticAvatarLOD() const { return _automaticAvatarLOD; } Q_INVOKABLE bool getAutomaticLODAdjust() const { return _automaticLODAdjust; }
void setAvatarLODDecreaseFPS(float avatarLODDecreaseFPS) { _avatarLODDecreaseFPS = avatarLODDecreaseFPS; }
float getAvatarLODDecreaseFPS() const { return _avatarLODDecreaseFPS; } Q_INVOKABLE void setDesktopLODDecreaseFPS(float value) { _desktopLODDecreaseFPS = value; }
void setAvatarLODIncreaseFPS(float avatarLODIncreaseFPS) { _avatarLODIncreaseFPS = avatarLODIncreaseFPS; } Q_INVOKABLE float getDesktopLODDecreaseFPS() const { return _desktopLODDecreaseFPS; }
float getAvatarLODIncreaseFPS() const { return _avatarLODIncreaseFPS; } Q_INVOKABLE float getDesktopLODIncreaseFPS() const { return _desktopLODDecreaseFPS + INCREASE_LOD_GAP; }
void setAvatarLODDistanceMultiplier(float multiplier) { _avatarLODDistanceMultiplier = multiplier; }
float getAvatarLODDistanceMultiplier() const { return _avatarLODDistanceMultiplier; } Q_INVOKABLE void setHMDLODDecreaseFPS(float value) { _hmdLODDecreaseFPS = value; }
Q_INVOKABLE float getHMDLODDecreaseFPS() const { return _hmdLODDecreaseFPS; }
Q_INVOKABLE float getHMDLODIncreaseFPS() const { return _hmdLODDecreaseFPS + INCREASE_LOD_GAP; }
Q_INVOKABLE float getAvatarLODDistanceMultiplier() const { return _avatarLODDistanceMultiplier; }
// User Tweakable LOD Items // User Tweakable LOD Items
Q_INVOKABLE QString getLODFeedbackText(); Q_INVOKABLE QString getLODFeedbackText();
@ -63,12 +67,15 @@ public:
Q_INVOKABLE void setBoundaryLevelAdjust(int boundaryLevelAdjust); Q_INVOKABLE void setBoundaryLevelAdjust(int boundaryLevelAdjust);
Q_INVOKABLE int getBoundaryLevelAdjust() const { return _boundaryLevelAdjust; } Q_INVOKABLE int getBoundaryLevelAdjust() const { return _boundaryLevelAdjust; }
void autoAdjustLOD(float currentFPS);
Q_INVOKABLE void resetLODAdjust(); Q_INVOKABLE void resetLODAdjust();
Q_INVOKABLE float getFPSAverage() const { return _fpsAverage.getAverage(); } Q_INVOKABLE float getFPSAverage() const { return _fpsAverage.getAverage(); }
Q_INVOKABLE float getFastFPSAverage() const { return _fastFPSAverage.getAverage(); } Q_INVOKABLE float getFastFPSAverage() const { return _fastFPSAverage.getAverage(); }
Q_INVOKABLE float getLODDecreaseFPS();
Q_INVOKABLE float getLODIncreaseFPS();
bool shouldRenderMesh(float largestDimension, float distanceToCamera); bool shouldRenderMesh(float largestDimension, float distanceToCamera);
void autoAdjustLOD(float currentFPS);
void loadSettings(); void loadSettings();
void saveSettings(); void saveSettings();
@ -78,18 +85,18 @@ signals:
void LODDecreased(); void LODDecreased();
private: private:
LODManager() {} LODManager();
void calculateAvatarLODDistanceMultiplier();
bool _automaticAvatarLOD = true; bool _automaticLODAdjust = true;
float _avatarLODDecreaseFPS = DEFAULT_ADJUST_AVATAR_LOD_DOWN_FPS; float _desktopLODDecreaseFPS = DEFAULT_DESKTOP_LOD_DOWN_FPS;
float _avatarLODIncreaseFPS = ADJUST_LOD_UP_FPS; float _hmdLODDecreaseFPS = DEFAULT_HMD_LOD_DOWN_FPS;
float _avatarLODDistanceMultiplier = DEFAULT_AVATAR_LOD_DISTANCE_MULTIPLIER;
float _avatarLODDistanceMultiplier;
float _octreeSizeScale = DEFAULT_OCTREE_SIZE_SCALE; float _octreeSizeScale = DEFAULT_OCTREE_SIZE_SCALE;
int _boundaryLevelAdjust = 0; int _boundaryLevelAdjust = 0;
quint64 _lastAdjust = 0; quint64 _lastAdjust = 0;
quint64 _lastAvatarDetailDrop = 0;
SimpleMovingAverage _fpsAverage = FIVE_SECONDS_OF_FRAMES; SimpleMovingAverage _fpsAverage = FIVE_SECONDS_OF_FRAMES;
SimpleMovingAverage _fastFPSAverage = ONE_SECOND_OF_FRAMES; SimpleMovingAverage _fastFPSAverage = ONE_SECOND_OF_FRAMES;

View file

@ -276,7 +276,6 @@ Menu::Menu() {
addCheckableActionToQMenuAndActionHash(renderOptionsMenu, MenuOption::Entities, 0, true); addCheckableActionToQMenuAndActionHash(renderOptionsMenu, MenuOption::Entities, 0, true);
addCheckableActionToQMenuAndActionHash(renderOptionsMenu, MenuOption::AmbientOcclusion); addCheckableActionToQMenuAndActionHash(renderOptionsMenu, MenuOption::AmbientOcclusion);
addCheckableActionToQMenuAndActionHash(renderOptionsMenu, MenuOption::DontFadeOnOctreeServerChanges); addCheckableActionToQMenuAndActionHash(renderOptionsMenu, MenuOption::DontFadeOnOctreeServerChanges);
addCheckableActionToQMenuAndActionHash(renderOptionsMenu, MenuOption::DisableAutoAdjustLOD);
QMenu* ambientLightMenu = renderOptionsMenu->addMenu(MenuOption::RenderAmbientLight); QMenu* ambientLightMenu = renderOptionsMenu->addMenu(MenuOption::RenderAmbientLight);
QActionGroup* ambientLightGroup = new QActionGroup(ambientLightMenu); QActionGroup* ambientLightGroup = new QActionGroup(ambientLightMenu);

View file

@ -136,7 +136,6 @@ namespace MenuOption {
const QString DecreaseAvatarSize = "Decrease Avatar Size"; const QString DecreaseAvatarSize = "Decrease Avatar Size";
const QString DeleteBookmark = "Delete Bookmark..."; const QString DeleteBookmark = "Delete Bookmark...";
const QString DisableActivityLogger = "Disable Activity Logger"; const QString DisableActivityLogger = "Disable Activity Logger";
const QString DisableAutoAdjustLOD = "Disable Automatically Adjusting LOD";
const QString DisableLightEntities = "Disable Light Entities"; const QString DisableLightEntities = "Disable Light Entities";
const QString DisableNackPackets = "Disable NACK Packets"; const QString DisableNackPackets = "Disable NACK Packets";
const QString DiskCacheEditor = "Disk Cache Editor"; const QString DiskCacheEditor = "Disk Cache Editor";

View file

@ -87,7 +87,8 @@ void FaceModel::maybeUpdateEyeRotation(Model* model, const JointState& parentSta
void FaceModel::updateJointState(int index) { void FaceModel::updateJointState(int index) {
JointState& state = _jointStates[index]; JointState& state = _jointStates[index];
const FBXJoint& joint = state.getFBXJoint(); const FBXJoint& joint = state.getFBXJoint();
if (joint.parentIndex != -1) { // guard against out-of-bounds access to _jointStates
if (joint.parentIndex != -1 && joint.parentIndex >= 0 && joint.parentIndex < _jointStates.size()) {
const JointState& parentState = _jointStates.at(joint.parentIndex); const JointState& parentState = _jointStates.at(joint.parentIndex);
const FBXGeometry& geometry = _geometry->getFBXGeometry(); const FBXGeometry& geometry = _geometry->getFBXGeometry();
if (index == geometry.neckJointIndex) { if (index == geometry.neckJointIndex) {

View file

@ -35,9 +35,24 @@ LodToolsDialog::LodToolsDialog(QWidget* parent) :
// Create layouter // Create layouter
QFormLayout* form = new QFormLayout(this); QFormLayout* form = new QFormLayout(this);
// Create a label with feedback...
_feedback = new QLabel(this);
QPalette palette = _feedback->palette();
const unsigned redish = 0xfff00000;
palette.setColor(QPalette::WindowText, QColor::fromRgb(redish));
_feedback->setPalette(palette);
_feedback->setText(lodManager->getLODFeedbackText());
const int FEEDBACK_WIDTH = 350;
_feedback->setFixedWidth(FEEDBACK_WIDTH);
form->addRow("You can see... ", _feedback);
form->addRow("Manually Adjust Level of Detail:", _manualLODAdjust = new QCheckBox(this));
_manualLODAdjust->setChecked(!lodManager->getAutomaticLODAdjust());
connect(_manualLODAdjust, SIGNAL(toggled(bool)), SLOT(updateAutomaticLODAdjust()));
_lodSize = new QSlider(Qt::Horizontal, this); _lodSize = new QSlider(Qt::Horizontal, this);
const int MAX_LOD_SIZE = MAX_LOD_SIZE_MULTIPLIER; const int MAX_LOD_SIZE = MAX_LOD_SIZE_MULTIPLIER;
const int MIN_LOD_SIZE = 0; const int MIN_LOD_SIZE = ADJUST_LOD_MIN_SIZE_SCALE;
const int STEP_LOD_SIZE = 1; const int STEP_LOD_SIZE = 1;
const int PAGE_STEP_LOD_SIZE = 100; const int PAGE_STEP_LOD_SIZE = 100;
const int SLIDER_WIDTH = 300; const int SLIDER_WIDTH = 300;
@ -50,56 +65,9 @@ LodToolsDialog::LodToolsDialog(QWidget* parent) :
_lodSize->setPageStep(PAGE_STEP_LOD_SIZE); _lodSize->setPageStep(PAGE_STEP_LOD_SIZE);
int sliderValue = lodManager->getOctreeSizeScale() / TREE_SCALE; int sliderValue = lodManager->getOctreeSizeScale() / TREE_SCALE;
_lodSize->setValue(sliderValue); _lodSize->setValue(sliderValue);
form->addRow("LOD Size Scale:", _lodSize); form->addRow("Level of Detail:", _lodSize);
connect(_lodSize,SIGNAL(valueChanged(int)),this,SLOT(sizeScaleValueChanged(int))); connect(_lodSize,SIGNAL(valueChanged(int)),this,SLOT(sizeScaleValueChanged(int)));
_boundaryLevelAdjust = new QSlider(Qt::Horizontal, this);
const int MAX_ADJUST = 10;
const int MIN_ADJUST = 0;
const int STEP_ADJUST = 1;
_boundaryLevelAdjust->setMaximum(MAX_ADJUST);
_boundaryLevelAdjust->setMinimum(MIN_ADJUST);
_boundaryLevelAdjust->setSingleStep(STEP_ADJUST);
_boundaryLevelAdjust->setTickInterval(STEP_ADJUST);
_boundaryLevelAdjust->setTickPosition(QSlider::TicksBelow);
_boundaryLevelAdjust->setFixedWidth(SLIDER_WIDTH);
sliderValue = lodManager->getBoundaryLevelAdjust();
_boundaryLevelAdjust->setValue(sliderValue);
form->addRow("Boundary Level Adjust:", _boundaryLevelAdjust);
connect(_boundaryLevelAdjust,SIGNAL(valueChanged(int)),this,SLOT(boundaryLevelValueChanged(int)));
// Create a label with feedback...
_feedback = new QLabel(this);
QPalette palette = _feedback->palette();
const unsigned redish = 0xfff00000;
palette.setColor(QPalette::WindowText, QColor::fromRgb(redish));
_feedback->setPalette(palette);
_feedback->setText(lodManager->getLODFeedbackText());
const int FEEDBACK_WIDTH = 350;
_feedback->setFixedWidth(FEEDBACK_WIDTH);
form->addRow("You can see... ", _feedback);
form->addRow("Automatic Avatar LOD Adjustment:", _automaticAvatarLOD = new QCheckBox(this));
_automaticAvatarLOD->setChecked(lodManager->getAutomaticAvatarLOD());
connect(_automaticAvatarLOD, SIGNAL(toggled(bool)), SLOT(updateAvatarLODControls()));
form->addRow("Decrease Avatar LOD Below FPS:", _avatarLODDecreaseFPS = new QDoubleSpinBox(this));
_avatarLODDecreaseFPS->setValue(lodManager->getAvatarLODDecreaseFPS());
_avatarLODDecreaseFPS->setDecimals(0);
connect(_avatarLODDecreaseFPS, SIGNAL(valueChanged(double)), SLOT(updateAvatarLODValues()));
form->addRow("Increase Avatar LOD Above FPS:", _avatarLODIncreaseFPS = new QDoubleSpinBox(this));
_avatarLODIncreaseFPS->setValue(lodManager->getAvatarLODIncreaseFPS());
_avatarLODIncreaseFPS->setDecimals(0);
connect(_avatarLODIncreaseFPS, SIGNAL(valueChanged(double)), SLOT(updateAvatarLODValues()));
form->addRow("Avatar LOD:", _avatarLOD = new QDoubleSpinBox(this));
_avatarLOD->setDecimals(3);
_avatarLOD->setRange(1.0 / MAXIMUM_AVATAR_LOD_DISTANCE_MULTIPLIER, 1.0 / MINIMUM_AVATAR_LOD_DISTANCE_MULTIPLIER);
_avatarLOD->setSingleStep(0.001);
_avatarLOD->setValue(1.0 / lodManager->getAvatarLODDistanceMultiplier());
connect(_avatarLOD, SIGNAL(valueChanged(double)), SLOT(updateAvatarLODValues()));
// Add a button to reset // Add a button to reset
QPushButton* resetButton = new QPushButton("Reset", this); QPushButton* resetButton = new QPushButton("Reset", this);
form->addRow("", resetButton); form->addRow("", resetButton);
@ -107,49 +75,19 @@ LodToolsDialog::LodToolsDialog(QWidget* parent) :
this->QDialog::setLayout(form); this->QDialog::setLayout(form);
updateAvatarLODControls(); updateAutomaticLODAdjust();
} }
void LodToolsDialog::reloadSliders() { void LodToolsDialog::reloadSliders() {
auto lodManager = DependencyManager::get<LODManager>(); auto lodManager = DependencyManager::get<LODManager>();
_lodSize->setValue(lodManager->getOctreeSizeScale() / TREE_SCALE); _lodSize->setValue(lodManager->getOctreeSizeScale() / TREE_SCALE);
_boundaryLevelAdjust->setValue(lodManager->getBoundaryLevelAdjust());
_feedback->setText(lodManager->getLODFeedbackText()); _feedback->setText(lodManager->getLODFeedbackText());
} }
void LodToolsDialog::updateAvatarLODControls() { void LodToolsDialog::updateAutomaticLODAdjust() {
QFormLayout* form = static_cast<QFormLayout*>(layout());
auto lodManager = DependencyManager::get<LODManager>(); auto lodManager = DependencyManager::get<LODManager>();
lodManager->setAutomaticAvatarLOD(_automaticAvatarLOD->isChecked()); lodManager->setAutomaticLODAdjust(!_manualLODAdjust->isChecked());
_lodSize->setEnabled(_manualLODAdjust->isChecked());
_avatarLODDecreaseFPS->setVisible(_automaticAvatarLOD->isChecked());
form->labelForField(_avatarLODDecreaseFPS)->setVisible(_automaticAvatarLOD->isChecked());
_avatarLODIncreaseFPS->setVisible(_automaticAvatarLOD->isChecked());
form->labelForField(_avatarLODIncreaseFPS)->setVisible(_automaticAvatarLOD->isChecked());
_avatarLOD->setVisible(!_automaticAvatarLOD->isChecked());
form->labelForField(_avatarLOD)->setVisible(!_automaticAvatarLOD->isChecked());
if (!_automaticAvatarLOD->isChecked()) {
_avatarLOD->setValue(1.0 / lodManager->getAvatarLODDistanceMultiplier());
}
if (isVisible()) {
adjustSize();
}
}
void LodToolsDialog::updateAvatarLODValues() {
auto lodManager = DependencyManager::get<LODManager>();
if (_automaticAvatarLOD->isChecked()) {
lodManager->setAvatarLODDecreaseFPS(_avatarLODDecreaseFPS->value());
lodManager->setAvatarLODIncreaseFPS(_avatarLODIncreaseFPS->value());
} else {
lodManager->setAvatarLODDistanceMultiplier(1.0 / _avatarLOD->value());
}
} }
void LodToolsDialog::sizeScaleValueChanged(int value) { void LodToolsDialog::sizeScaleValueChanged(int value) {
@ -160,20 +98,13 @@ void LodToolsDialog::sizeScaleValueChanged(int value) {
_feedback->setText(lodManager->getLODFeedbackText()); _feedback->setText(lodManager->getLODFeedbackText());
} }
void LodToolsDialog::boundaryLevelValueChanged(int value) {
auto lodManager = DependencyManager::get<LODManager>();
lodManager->setBoundaryLevelAdjust(value);
_feedback->setText(lodManager->getLODFeedbackText());
}
void LodToolsDialog::resetClicked(bool checked) { void LodToolsDialog::resetClicked(bool checked) {
int sliderValue = DEFAULT_OCTREE_SIZE_SCALE / TREE_SCALE; int sliderValue = DEFAULT_OCTREE_SIZE_SCALE / TREE_SCALE;
//sizeScaleValueChanged(sliderValue);
_lodSize->setValue(sliderValue); _lodSize->setValue(sliderValue);
_boundaryLevelAdjust->setValue(0); _manualLODAdjust->setChecked(false);
_automaticAvatarLOD->setChecked(true);
_avatarLODDecreaseFPS->setValue(DEFAULT_ADJUST_AVATAR_LOD_DOWN_FPS); updateAutomaticLODAdjust(); // tell our LOD manager about the reset
_avatarLODIncreaseFPS->setValue(ADJUST_LOD_UP_FPS);
} }
void LodToolsDialog::reject() { void LodToolsDialog::reject() {
@ -184,6 +115,15 @@ void LodToolsDialog::reject() {
void LodToolsDialog::closeEvent(QCloseEvent* event) { void LodToolsDialog::closeEvent(QCloseEvent* event) {
this->QDialog::closeEvent(event); this->QDialog::closeEvent(event);
emit closed(); emit closed();
auto lodManager = DependencyManager::get<LODManager>();
// always revert back to automatic LOD adjustment when closed
lodManager->setAutomaticLODAdjust(true);
// if the user adjusted the LOD above "normal" then always revert back to default
if (lodManager->getOctreeSizeScale() > DEFAULT_OCTREE_SIZE_SCALE) {
lodManager->setOctreeSizeScale(DEFAULT_OCTREE_SIZE_SCALE);
}
} }

View file

@ -31,11 +31,9 @@ signals:
public slots: public slots:
void reject(); void reject();
void sizeScaleValueChanged(int value); void sizeScaleValueChanged(int value);
void boundaryLevelValueChanged(int value);
void resetClicked(bool checked); void resetClicked(bool checked);
void reloadSliders(); void reloadSliders();
void updateAvatarLODControls(); void updateAutomaticLODAdjust();
void updateAvatarLODValues();
protected: protected:
@ -44,11 +42,13 @@ protected:
private: private:
QSlider* _lodSize; QSlider* _lodSize;
QSlider* _boundaryLevelAdjust;
QCheckBox* _automaticAvatarLOD; QCheckBox* _manualLODAdjust;
QDoubleSpinBox* _avatarLODDecreaseFPS;
QDoubleSpinBox* _avatarLODIncreaseFPS; QDoubleSpinBox* _desktopLODDecreaseFPS;
QDoubleSpinBox* _avatarLOD;
QDoubleSpinBox* _hmdLODDecreaseFPS;
QLabel* _feedback; QLabel* _feedback;
}; };

View file

@ -19,6 +19,7 @@
#include "Application.h" #include "Application.h"
#include "MainWindow.h" #include "MainWindow.h"
#include "LODManager.h"
#include "Menu.h" #include "Menu.h"
#include "ModelsBrowser.h" #include "ModelsBrowser.h"
#include "PreferencesDialog.h" #include "PreferencesDialog.h"
@ -174,6 +175,10 @@ void PreferencesDialog::loadPreferences() {
ui.sixenseReticleMoveSpeedSpin->setValue(sixense.getReticleMoveSpeed()); ui.sixenseReticleMoveSpeedSpin->setValue(sixense.getReticleMoveSpeed());
ui.invertSixenseButtonsCheckBox->setChecked(sixense.getInvertButtons()); ui.invertSixenseButtonsCheckBox->setChecked(sixense.getInvertButtons());
// LOD items
auto lodManager = DependencyManager::get<LODManager>();
ui.desktopMinimumFPSSpin->setValue(lodManager->getDesktopLODDecreaseFPS());
ui.hmdMinimumFPSSpin->setValue(lodManager->getHMDLODDecreaseFPS());
} }
void PreferencesDialog::savePreferences() { void PreferencesDialog::savePreferences() {
@ -275,4 +280,9 @@ void PreferencesDialog::savePreferences() {
audio->setOutputStarveDetectionPeriod(ui.outputStarveDetectionPeriodSpinner->value()); audio->setOutputStarveDetectionPeriod(ui.outputStarveDetectionPeriodSpinner->value());
Application::getInstance()->resizeGL(glCanvas->width(), glCanvas->height()); Application::getInstance()->resizeGL(glCanvas->width(), glCanvas->height());
// LOD items
auto lodManager = DependencyManager::get<LODManager>();
lodManager->setDesktopLODDecreaseFPS(ui.desktopMinimumFPSSpin->value());
lodManager->setHMDLODDecreaseFPS(ui.hmdMinimumFPSSpin->value());
} }

View file

@ -701,6 +701,219 @@
</property> </property>
</widget> </widget>
</item> </item>
<item>
<spacer name="verticalSpacer_10">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeType">
<enum>QSizePolicy::Fixed</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QLabel" name="levelOfDetailTitleLabel">
<property name="font">
<font>
<family>Arial</family>
<pointsize>18</pointsize>
<weight>75</weight>
<bold>true</bold>
</font>
</property>
<property name="styleSheet">
<string notr="true">color:#29967e</string>
</property>
<property name="text">
<string>Level of Detail Tuning</string>
</property>
<property name="alignment">
<set>Qt::AlignBottom|Qt::AlignLeading|Qt::AlignLeft</set>
</property>
</widget>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_111x">
<property name="spacing">
<number>0</number>
</property>
<property name="topMargin">
<number>7</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>7</number>
</property>
<item>
<widget class="QLabel" name="label_9x">
<property name="font">
<font>
<family>Arial</family>
</font>
</property>
<property name="styleSheet">
<string notr="true"/>
</property>
<property name="text">
<string>Minimum Desktop FPS</string>
</property>
<property name="indent">
<number>0</number>
</property>
<!--
<property name="buddy">
<cstring>fieldOfViewSpin</cstring>
</property>
-->
</widget>
</item>
<item>
<spacer name="horizontalSpacer_111x">
<property name="font">
<font>
<family>Arial</family>
</font>
</property>
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>0</width>
<height>0</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QSpinBox" name="desktopMinimumFPSSpin">
<property name="minimumSize">
<size>
<width>100</width>
<height>0</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>95</width>
<height>36</height>
</size>
</property>
<property name="font">
<font>
<family>Arial</family>
</font>
</property>
<property name="minimum">
<number>0</number>
</property>
<property name="maximum">
<number>120</number>
</property>
</widget>
</item>
</layout>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_111y">
<property name="spacing">
<number>0</number>
</property>
<property name="topMargin">
<number>7</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>7</number>
</property>
<item>
<widget class="QLabel" name="label_9y">
<property name="font">
<font>
<family>Arial</family>
</font>
</property>
<property name="styleSheet">
<string notr="true"/>
</property>
<property name="text">
<string>Minimum HMD FPS</string>
</property>
<property name="indent">
<number>0</number>
</property>
<!--
<property name="buddy">
<cstring>fieldOfViewSpin</cstring>
</property>
-->
</widget>
</item>
<item>
<spacer name="horizontalSpacer_111y">
<property name="font">
<font>
<family>Arial</family>
</font>
</property>
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>0</width>
<height>0</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QSpinBox" name="hmdMinimumFPSSpin">
<property name="minimumSize">
<size>
<width>100</width>
<height>0</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>95</width>
<height>36</height>
</size>
</property>
<property name="font">
<font>
<family>Arial</family>
</font>
</property>
<property name="minimum">
<number>0</number>
</property>
<property name="maximum">
<number>120</number>
</property>
</widget>
</item>
</layout>
</item>
<item> <item>
<spacer name="verticalSpacer_8"> <spacer name="verticalSpacer_8">
<property name="orientation"> <property name="orientation">
@ -717,6 +930,7 @@
</property> </property>
</spacer> </spacer>
</item> </item>
<item> <item>
<widget class="QLabel" name="avatarTitleLabel"> <widget class="QLabel" name="avatarTitleLabel">
<property name="font"> <property name="font">
@ -738,6 +952,7 @@
</property> </property>
</widget> </widget>
</item> </item>
<item> <item>
<layout class="QHBoxLayout" name="horizontalLayout_111"> <layout class="QHBoxLayout" name="horizontalLayout_111">
<property name="spacing"> <property name="spacing">
@ -820,6 +1035,9 @@
</item> </item>
</layout> </layout>
</item> </item>
<item> <item>
<layout class="QHBoxLayout" name="horizontalLayout_3"> <layout class="QHBoxLayout" name="horizontalLayout_3">
<property name="spacing"> <property name="spacing">

View file

@ -23,7 +23,7 @@ const int TREE_SCALE = 16384; // ~10 miles.. This is the number of meters of t
const float DEFAULT_OCTREE_SIZE_SCALE = TREE_SCALE * 400.0f; const float DEFAULT_OCTREE_SIZE_SCALE = TREE_SCALE * 400.0f;
// This is used in the LOD Tools to translate between the size scale slider and the values used to set the OctreeSizeScale // This is used in the LOD Tools to translate between the size scale slider and the values used to set the OctreeSizeScale
const float MAX_LOD_SIZE_MULTIPLIER = 2000.0f; const float MAX_LOD_SIZE_MULTIPLIER = 800.0f;
const int NUMBER_OF_CHILDREN = 8; const int NUMBER_OF_CHILDREN = 8;

View file

@ -16,10 +16,9 @@ subject to the following restrictions:
*/ */
//#include <stdio.h>
#include "BulletCollision/CollisionDispatch/btGhostObject.h" #include "BulletCollision/CollisionDispatch/btGhostObject.h"
#include "BulletUtil.h"
#include "CharacterController.h" #include "CharacterController.h"
@ -54,7 +53,7 @@ m_me = me;
virtual btScalar addSingleResult(btCollisionWorld::LocalRayResult& rayResult, bool normalInWorldSpace) virtual btScalar addSingleResult(btCollisionWorld::LocalRayResult& rayResult, bool normalInWorldSpace)
{ {
if(rayResult.m_collisionObject == m_me) if (rayResult.m_collisionObject == m_me)
return 1.0; return 1.0;
return ClosestRayResultCallback::addSingleResult(rayResult, normalInWorldSpace); return ClosestRayResultCallback::addSingleResult(rayResult, normalInWorldSpace);
@ -64,8 +63,7 @@ btCollisionObject* m_me;
}; };
*/ */
class btKinematicClosestNotMeConvexResultCallback : public btCollisionWorld::ClosestConvexResultCallback class btKinematicClosestNotMeConvexResultCallback : public btCollisionWorld::ClosestConvexResultCallback {
{
public: public:
btKinematicClosestNotMeConvexResultCallback(btCollisionObject* me, const btVector3& up, btScalar minSlopeDot) btKinematicClosestNotMeConvexResultCallback(btCollisionObject* me, const btVector3& up, btScalar minSlopeDot)
: btCollisionWorld::ClosestConvexResultCallback(btVector3(0.0, 0.0, 0.0), btVector3(0.0, 0.0, 0.0)) : btCollisionWorld::ClosestConvexResultCallback(btVector3(0.0, 0.0, 0.0), btVector3(0.0, 0.0, 0.0))
@ -92,6 +90,9 @@ class btKinematicClosestNotMeConvexResultCallback : public btCollisionWorld::Clo
hitNormalWorld = convexResult.m_hitCollisionObject->getWorldTransform().getBasis()*convexResult.m_hitNormalLocal; hitNormalWorld = convexResult.m_hitCollisionObject->getWorldTransform().getBasis()*convexResult.m_hitNormalLocal;
} }
// Note: hitNormalWorld points into character, away from object
// and m_up points opposite to movement
btScalar dotUp = m_up.dot(hitNormalWorld); btScalar dotUp = m_up.dot(hitNormalWorld);
if (dotUp < m_minSlopeDot) { if (dotUp < m_minSlopeDot) {
return btScalar(1.0); return btScalar(1.0);
@ -106,21 +107,104 @@ protected:
btScalar m_minSlopeDot; btScalar m_minSlopeDot;
}; };
class StepDownConvexResultCallback : public btCollisionWorld::ClosestConvexResultCallback {
// special convex sweep callback for character during the stepDown() phase
public:
StepDownConvexResultCallback(btCollisionObject* me,
const btVector3& up,
const btVector3& start,
const btVector3& step,
const btVector3& pushDirection,
btScalar minSlopeDot,
btScalar radius,
btScalar halfHeight)
: btCollisionWorld::ClosestConvexResultCallback(btVector3(0.0, 0.0, 0.0), btVector3(0.0, 0.0, 0.0))
, m_me(me)
, m_up(up)
, m_start(start)
, m_step(step)
, m_pushDirection(pushDirection)
, m_minSlopeDot(minSlopeDot)
, m_radius(radius)
, m_halfHeight(halfHeight)
{
}
virtual btScalar addSingleResult(btCollisionWorld::LocalConvexResult& convexResult, bool normalInWorldSpace) {
if (convexResult.m_hitCollisionObject == m_me) {
return btScalar(1.0);
}
if (!convexResult.m_hitCollisionObject->hasContactResponse()) {
return btScalar(1.0);
}
btVector3 hitNormalWorld;
if (normalInWorldSpace) {
hitNormalWorld = convexResult.m_hitNormalLocal;
} else {
///need to transform normal into worldspace
hitNormalWorld = convexResult.m_hitCollisionObject->getWorldTransform().getBasis() * convexResult.m_hitNormalLocal;
}
// Note: hitNormalWorld points into character, away from object
// and m_up points opposite to movement
btScalar dotUp = m_up.dot(hitNormalWorld);
if (dotUp < m_minSlopeDot) {
if (hitNormalWorld.dot(m_pushDirection) > 0.0f) {
// ignore hits that push in same direction as character is moving
// which helps character NOT snag when stepping off ledges
return btScalar(1.0f);
}
// compute the angle between "down" and the line from character center to "hit" point
btVector3 fractionalStep = convexResult.m_hitFraction * m_step;
btVector3 localHit = convexResult.m_hitPointLocal - m_start + fractionalStep;
btScalar angle = localHit.angle(-m_up);
// compute a maxAngle based on size of m_step
btVector3 side(m_radius, - (m_halfHeight - m_step.length() + fractionalStep.dot(m_up)), 0.0f);
btScalar maxAngle = side.angle(-m_up);
// Ignore hits that are larger than maxAngle. Effectively what is happening here is:
// we're ignoring hits at contacts that have non-vertical normals... if they hit higher
// than the character's "feet". Ignoring the contact allows the character to slide down
// for these hits. In other words, vertical walls against the character's torso will
// not prevent them from "stepping down" to find the floor.
if (angle > maxAngle) {
return btScalar(1.0f);
}
}
btScalar fraction = ClosestConvexResultCallback::addSingleResult(convexResult, normalInWorldSpace);
return fraction;
}
protected:
btCollisionObject* m_me;
const btVector3 m_up;
btVector3 m_start;
btVector3 m_step;
btVector3 m_pushDirection;
btScalar m_minSlopeDot;
btScalar m_radius;
btScalar m_halfHeight;
};
/* /*
* Returns the reflection direction of a ray going 'direction' hitting a surface with normal 'normal' * Returns the reflection direction of a ray going 'direction' hitting a surface with normal 'normal'
* *
* from: http://www-cs-students.stanford.edu/~adityagp/final/node3.html * from: http://www-cs-students.stanford.edu/~adityagp/final/node3.html
*/ */
btVector3 CharacterController::computeReflectionDirection(const btVector3& direction, const btVector3& normal) btVector3 CharacterController::computeReflectionDirection(const btVector3& direction, const btVector3& normal) {
{
return direction - (btScalar(2.0) * direction.dot(normal)) * normal; return direction - (btScalar(2.0) * direction.dot(normal)) * normal;
} }
/* /*
* Returns the portion of 'direction' that is parallel to 'normal' * Returns the portion of 'direction' that is parallel to 'normal'
*/ */
btVector3 CharacterController::parallelComponent(const btVector3& direction, const btVector3& normal) btVector3 CharacterController::parallelComponent(const btVector3& direction, const btVector3& normal) {
{
btScalar magnitude = direction.dot(normal); btScalar magnitude = direction.dot(normal);
return normal * magnitude; return normal * magnitude;
} }
@ -128,36 +212,38 @@ btVector3 CharacterController::parallelComponent(const btVector3& direction, con
/* /*
* Returns the portion of 'direction' that is perpindicular to 'normal' * Returns the portion of 'direction' that is perpindicular to 'normal'
*/ */
btVector3 CharacterController::perpindicularComponent(const btVector3& direction, const btVector3& normal) btVector3 CharacterController::perpindicularComponent(const btVector3& direction, const btVector3& normal) {
{
return direction - parallelComponent(direction, normal); return direction - parallelComponent(direction, normal);
} }
CharacterController::CharacterController( CharacterController::CharacterController(AvatarData* avatarData) {
btPairCachingGhostObject* ghostObject, assert(avatarData);
btConvexShape* convexShape, m_avatarData = avatarData;
btScalar stepHeight,
int upAxis) { // cache the "PhysicsEnabled" state of m_avatarData
m_upAxis = upAxis; m_avatarData->lockForRead();
m_addedMargin = 0.02; m_enabled = m_avatarData->isPhysicsEnabled();
m_walkDirection.setValue(0,0,0); m_avatarData->unlock();
m_useGhostObjectSweepTest = true;
m_ghostObject = ghostObject; createShapeAndGhost();
m_stepHeight = stepHeight;
m_turnAngle = btScalar(0.0); m_upAxis = 1; // HACK: hard coded to be 1 for now (yAxis)
m_convexShape = convexShape;
m_addedMargin = 0.02f;
m_walkDirection.setValue(0.0f,0.0f,0.0f);
m_turnAngle = btScalar(0.0f);
m_useWalkDirection = true; // use walk direction by default, legacy behavior m_useWalkDirection = true; // use walk direction by default, legacy behavior
m_velocityTimeInterval = 0.0; m_velocityTimeInterval = 0.0f;
m_verticalVelocity = 0.0; m_verticalVelocity = 0.0f;
m_verticalOffset = 0.0; m_verticalOffset = 0.0f;
m_gravity = 9.8 * 3 ; // 3G acceleration. m_gravity = 9.8f;
m_fallSpeed = 55.0; // Terminal velocity of a sky diver in m/s. m_maxFallSpeed = 55.0f; // Terminal velocity of a sky diver in m/s.
m_jumpSpeed = 10.0; // ? m_jumpSpeed = 10.0f; // ?
m_wasOnGround = false; m_wasOnGround = false;
m_wasJumping = false; m_wasJumping = false;
m_interpolateUp = true; m_interpolateUp = true;
setMaxSlope(btRadians(45.0)); setMaxSlope(btRadians(45.0f));
m_currentStepOffset = 0; m_lastStepUp = 0.0f;
// internal state data members // internal state data members
full_drop = false; full_drop = false;
@ -192,6 +278,9 @@ bool CharacterController::recoverFromPenetration(btCollisionWorld* collisionWorl
collisionWorld->getDispatcher()->dispatchAllCollisionPairs(m_ghostObject->getOverlappingPairCache(), collisionWorld->getDispatchInfo(), collisionWorld->getDispatcher()); collisionWorld->getDispatcher()->dispatchAllCollisionPairs(m_ghostObject->getOverlappingPairCache(), collisionWorld->getDispatchInfo(), collisionWorld->getDispatcher());
m_currentPosition = m_ghostObject->getWorldTransform().getOrigin(); m_currentPosition = m_ghostObject->getWorldTransform().getOrigin();
btVector3 up = getUpAxisDirections()[m_upAxis];
btVector3 currentPosition = m_currentPosition;
btScalar maxPen = btScalar(0.0); btScalar maxPen = btScalar(0.0);
for (int i = 0; i < m_ghostObject->getOverlappingPairCache()->getNumOverlappingPairs(); i++) { for (int i = 0; i < m_ghostObject->getOverlappingPairCache()->getNumOverlappingPairs(); i++) {
@ -210,83 +299,109 @@ bool CharacterController::recoverFromPenetration(btCollisionWorld* collisionWorl
collisionPair->m_algorithm->getAllContactManifolds(m_manifoldArray); collisionPair->m_algorithm->getAllContactManifolds(m_manifoldArray);
} }
for (int j=0;j<m_manifoldArray.size();j++) { for (int j = 0;j < m_manifoldArray.size(); j++) {
btPersistentManifold* manifold = m_manifoldArray[j]; btPersistentManifold* manifold = m_manifoldArray[j];
btScalar directionSign = manifold->getBody0() == m_ghostObject ? btScalar(-1.0) : btScalar(1.0); btScalar directionSign = (manifold->getBody0() == m_ghostObject) ? btScalar(1.0) : btScalar(-1.0);
for (int p=0;p<manifold->getNumContacts();p++) { for (int p = 0;p < manifold->getNumContacts(); p++) {
const btManifoldPoint&pt = manifold->getContactPoint(p); const btManifoldPoint&pt = manifold->getContactPoint(p);
btScalar dist = pt.getDistance(); btScalar dist = pt.getDistance();
if (dist < 0.0) { if (dist < 0.0) {
if (dist < maxPen) { bool useContact = true;
maxPen = dist; btVector3 normal = pt.m_normalWorldOnB;
m_touchingNormal = pt.m_normalWorldOnB * directionSign;//?? normal *= directionSign; // always points from object to character
btScalar normalDotUp = normal.dot(up);
if (normalDotUp < m_maxSlopeCosine) {
// this contact has a non-vertical normal... might need to ignored
btVector3 collisionPoint;
if (directionSign > 0.0) {
collisionPoint = pt.getPositionWorldOnB();
} else {
collisionPoint = pt.getPositionWorldOnA();
}
// we do math in frame where character base is origin
btVector3 characterBase = currentPosition - (m_radius + m_halfHeight) * up;
collisionPoint -= characterBase;
btScalar collisionHeight = collisionPoint.dot(up);
if (collisionHeight < m_lastStepUp) {
// This contact is below the lastStepUp, so we ignore it for penetration resolution,
// otherwise it may prevent the character from getting close enough to find any available
// horizontal foothold that would allow it to climbe the ledge. In other words, we're
// making the character's "feet" soft for collisions against steps, but not floors.
useContact = false;
}
}
if (useContact) {
if (dist < maxPen) {
maxPen = dist;
m_floorNormal = normal;
}
const btScalar INCREMENTAL_RESOLUTION_FACTOR = 0.2f;
m_currentPosition += normal * (fabsf(dist) * INCREMENTAL_RESOLUTION_FACTOR);
penetration = true;
} }
m_currentPosition += pt.m_normalWorldOnB * directionSign * dist * btScalar(0.2);
penetration = true;
} else {
//printf("touching %f\n", dist);
} }
} }
//manifold->clearManifold();
} }
} }
btTransform newTrans = m_ghostObject->getWorldTransform(); btTransform newTrans = m_ghostObject->getWorldTransform();
newTrans.setOrigin(m_currentPosition); newTrans.setOrigin(m_currentPosition);
m_ghostObject->setWorldTransform(newTrans); m_ghostObject->setWorldTransform(newTrans);
//printf("m_touchingNormal = %f,%f,%f\n", m_touchingNormal[0], m_touchingNormal[1], m_touchingNormal[2]);
return penetration; return penetration;
} }
void CharacterController::stepUp( btCollisionWorld* world) { void CharacterController::stepUp( btCollisionWorld* world) {
// phase 1: up // phase 1: up
// compute start and end
btTransform start, end; btTransform start, end;
m_targetPosition = m_currentPosition + getUpAxisDirections()[m_upAxis] * (m_stepHeight + (m_verticalOffset > 0.f?m_verticalOffset:0.f));
start.setIdentity(); start.setIdentity();
end.setIdentity();
/* FIXME: Handle penetration properly */
start.setOrigin(m_currentPosition + getUpAxisDirections()[m_upAxis] * (m_convexShape->getMargin() + m_addedMargin)); start.setOrigin(m_currentPosition + getUpAxisDirections()[m_upAxis] * (m_convexShape->getMargin() + m_addedMargin));
//m_targetPosition = m_currentPosition + getUpAxisDirections()[m_upAxis] * (m_stepHeight + (m_verticalOffset > 0.0f ? m_verticalOffset : 0.0f));
m_targetPosition = m_currentPosition + getUpAxisDirections()[m_upAxis] * m_stepHeight;
end.setIdentity();
end.setOrigin(m_targetPosition); end.setOrigin(m_targetPosition);
btKinematicClosestNotMeConvexResultCallback callback(m_ghostObject, -getUpAxisDirections()[m_upAxis], btScalar(0.7071)); // sweep up
btVector3 sweepDirNegative = -getUpAxisDirections()[m_upAxis];
btKinematicClosestNotMeConvexResultCallback callback(m_ghostObject, sweepDirNegative, btScalar(0.7071));
callback.m_collisionFilterGroup = getGhostObject()->getBroadphaseHandle()->m_collisionFilterGroup; callback.m_collisionFilterGroup = getGhostObject()->getBroadphaseHandle()->m_collisionFilterGroup;
callback.m_collisionFilterMask = getGhostObject()->getBroadphaseHandle()->m_collisionFilterMask; callback.m_collisionFilterMask = getGhostObject()->getBroadphaseHandle()->m_collisionFilterMask;
m_ghostObject->convexSweepTest(m_convexShape, start, end, callback, world->getDispatchInfo().m_allowedCcdPenetration);
if (m_useGhostObjectSweepTest) {
m_ghostObject->convexSweepTest(m_convexShape, start, end, callback, world->getDispatchInfo().m_allowedCcdPenetration);
}
else {
world->convexSweepTest(m_convexShape, start, end, callback);
}
if (callback.hasHit()) { if (callback.hasHit()) {
// we hit something, so zero our vertical velocity
m_verticalVelocity = 0.0;
m_verticalOffset = 0.0;
// Only modify the position if the hit was a slope and not a wall or ceiling. // Only modify the position if the hit was a slope and not a wall or ceiling.
if (callback.m_hitNormalWorld.dot(getUpAxisDirections()[m_upAxis]) > 0.0) { if (callback.m_hitNormalWorld.dot(getUpAxisDirections()[m_upAxis]) > 0.0) {
// we moved up only a fraction of the step height m_lastStepUp = m_stepHeight * callback.m_closestHitFraction;
m_currentStepOffset = m_stepHeight * callback.m_closestHitFraction;
if (m_interpolateUp == true) { if (m_interpolateUp == true) {
m_currentPosition.setInterpolate3 (m_currentPosition, m_targetPosition, callback.m_closestHitFraction); m_currentPosition.setInterpolate3 (m_currentPosition, m_targetPosition, callback.m_closestHitFraction);
} else { } else {
m_currentPosition = m_targetPosition; m_currentPosition = m_targetPosition;
} }
} else {
m_lastStepUp = m_stepHeight;
m_currentPosition = m_targetPosition;
} }
m_verticalVelocity = 0.0;
m_verticalOffset = 0.0;
} else { } else {
m_currentStepOffset = m_stepHeight;
m_currentPosition = m_targetPosition; m_currentPosition = m_targetPosition;
m_lastStepUp = m_stepHeight;
} }
} }
void CharacterController::updateTargetPositionBasedOnCollision(const btVector3& hitNormal, btScalar tangentMag, btScalar normalMag) { void CharacterController::updateTargetPositionBasedOnCollision(const btVector3& hitNormal, btScalar tangentMag, btScalar normalMag) {
btVector3 movementDirection = m_targetPosition - m_currentPosition; btVector3 movementDirection = m_targetPosition - m_currentPosition;
btScalar movementLength = movementDirection.length(); btScalar movementLength = movementDirection.length();
if (movementLength>SIMD_EPSILON) { if (movementLength > SIMD_EPSILON) {
movementDirection.normalize(); movementDirection.normalize();
btVector3 reflectDir = computeReflectionDirection(movementDirection, hitNormal); btVector3 reflectDir = computeReflectionDirection(movementDirection, hitNormal);
@ -300,241 +415,152 @@ void CharacterController::updateTargetPositionBasedOnCollision(const btVector3&
m_targetPosition = m_currentPosition; m_targetPosition = m_currentPosition;
//if (tangentMag != 0.0) { //if (tangentMag != 0.0) {
if (0) { if (0) {
btVector3 parComponent = parallelDir * btScalar(tangentMag*movementLength); btVector3 parComponent = parallelDir * btScalar(tangentMag * movementLength);
//printf("parComponent=%f,%f,%f\n", parComponent[0], parComponent[1], parComponent[2]);
m_targetPosition += parComponent; m_targetPosition += parComponent;
} }
if (normalMag != 0.0) { if (normalMag != 0.0) {
btVector3 perpComponent = perpindicularDir * btScalar(normalMag*movementLength); btVector3 perpComponent = perpindicularDir * btScalar(normalMag * movementLength);
//printf("perpComponent=%f,%f,%f\n", perpComponent[0], perpComponent[1], perpComponent[2]);
m_targetPosition += perpComponent; m_targetPosition += perpComponent;
} }
} else {
//printf("movementLength don't normalize a zero vector\n");
} }
} }
void CharacterController::stepForwardAndStrafe( btCollisionWorld* collisionWorld, const btVector3& walkMove) { void CharacterController::stepForward( btCollisionWorld* collisionWorld, const btVector3& movement) {
//printf("m_normalizedDirection=%f,%f,%f\n", // phase 2: forward
// m_normalizedDirection[0], m_normalizedDirection[1], m_normalizedDirection[2]); m_targetPosition = m_currentPosition + movement;
// phase 2: forward and strafe
btTransform start, end;
m_targetPosition = m_currentPosition + walkMove;
btTransform start, end;
start.setIdentity(); start.setIdentity();
end.setIdentity(); end.setIdentity();
btScalar fraction = 1.0; /* TODO: experiment with this to see if we can use this to help direct motion when a floor is available
btScalar distance2 = (m_currentPosition-m_targetPosition).length2();
//printf("distance2=%f\n", distance2);
if (m_touchingContact) { if (m_touchingContact) {
if (m_normalizedDirection.dot(m_touchingNormal) > btScalar(0.0)) { if (m_normalizedDirection.dot(m_floorNormal) < btScalar(0.0)) {
//interferes with step movement updateTargetPositionBasedOnCollision(m_floorNormal, 1.0f, 1.0f);
//updateTargetPositionBasedOnCollision(m_touchingNormal);
} }
} }*/
// modify shape's margin for the sweeps
btScalar margin = m_convexShape->getMargin();
m_convexShape->setMargin(margin + m_addedMargin);
const btScalar MIN_STEP_DISTANCE = 0.0001f;
btVector3 step = m_targetPosition - m_currentPosition;
btScalar stepLength2 = step.length2();
int maxIter = 10; int maxIter = 10;
while (fraction > btScalar(0.01) && maxIter-- > 0) { while (stepLength2 > MIN_STEP_DISTANCE && maxIter-- > 0) {
start.setOrigin(m_currentPosition); start.setOrigin(m_currentPosition);
end.setOrigin(m_targetPosition); end.setOrigin(m_targetPosition);
btVector3 sweepDirNegative(m_currentPosition - m_targetPosition);
// sweep forward
btVector3 sweepDirNegative(m_currentPosition - m_targetPosition);
btKinematicClosestNotMeConvexResultCallback callback(m_ghostObject, sweepDirNegative, btScalar(0.0)); btKinematicClosestNotMeConvexResultCallback callback(m_ghostObject, sweepDirNegative, btScalar(0.0));
callback.m_collisionFilterGroup = getGhostObject()->getBroadphaseHandle()->m_collisionFilterGroup; callback.m_collisionFilterGroup = getGhostObject()->getBroadphaseHandle()->m_collisionFilterGroup;
callback.m_collisionFilterMask = getGhostObject()->getBroadphaseHandle()->m_collisionFilterMask; callback.m_collisionFilterMask = getGhostObject()->getBroadphaseHandle()->m_collisionFilterMask;
m_ghostObject->convexSweepTest(m_convexShape, start, end, callback, collisionWorld->getDispatchInfo().m_allowedCcdPenetration);
btScalar margin = m_convexShape->getMargin();
m_convexShape->setMargin(margin + m_addedMargin);
if (m_useGhostObjectSweepTest) {
m_ghostObject->convexSweepTest(m_convexShape, start, end, callback, collisionWorld->getDispatchInfo().m_allowedCcdPenetration);
} else {
collisionWorld->convexSweepTest(m_convexShape, start, end, callback, collisionWorld->getDispatchInfo().m_allowedCcdPenetration);
}
m_convexShape->setMargin(margin);
fraction -= callback.m_closestHitFraction;
if (callback.hasHit()) { if (callback.hasHit()) {
// we moved only a fraction // we hit soemthing!
//btScalar hitDistance; // Compute new target position by removing portion cut-off by collision, which will produce a new target
//hitDistance = (callback.m_hitPointWorld - m_currentPosition).length(); // that is the closest approach of the the obstacle plane to the original target.
step = m_targetPosition - m_currentPosition;
btScalar stepDotNormal = step.dot(callback.m_hitNormalWorld); // we expect this dot to be negative
step += (stepDotNormal * (1.0f - callback.m_closestHitFraction)) * callback.m_hitNormalWorld;
m_targetPosition = m_currentPosition + step;
//m_currentPosition.setInterpolate3 (m_currentPosition, m_targetPosition, callback.m_closestHitFraction); stepLength2 = step.length2();
updateTargetPositionBasedOnCollision(callback.m_hitNormalWorld);
btVector3 currentDir = m_targetPosition - m_currentPosition;
distance2 = currentDir.length2();
if (distance2 > SIMD_EPSILON) {
currentDir.normalize();
/* See Quake2: "If velocity is against original velocity, stop ead to avoid tiny oscilations in sloping corners." */
if (currentDir.dot(m_normalizedDirection) <= btScalar(0.0)) {
break;
}
} else {
//printf("currentDir: don't normalize a zero vector\n");
break;
}
} else { } else {
// we moved whole way // we swept to the end without hitting anything
m_currentPosition = m_targetPosition; m_currentPosition = m_targetPosition;
break;
} }
//if (callback.m_closestHitFraction == 0.f) {
// break;
//}
} }
// restore shape's margin
m_convexShape->setMargin(margin);
} }
void CharacterController::stepDown( btCollisionWorld* collisionWorld, btScalar dt) { void CharacterController::stepDown( btCollisionWorld* collisionWorld, btScalar dt) {
btTransform start, end, end_double;
bool runonce = false;
// phase 3: down // phase 3: down
/*btScalar additionalDownStep = (m_wasOnGround && !onGround()) ? m_stepHeight : 0.0; //
btVector3 step_drop = getUpAxisDirections()[m_upAxis] * (m_currentStepOffset + additionalDownStep); // The "stepDown" phase first makes a normal sweep down that cancels the lift from the "stepUp" phase.
btScalar downVelocity = (additionalDownStep == 0.0 && m_verticalVelocity<0.0?-m_verticalVelocity:0.0) * dt; // If it hits a ledge then it stops otherwise it makes another sweep down in search of a floor within
btVector3 gravity_drop = getUpAxisDirections()[m_upAxis] * downVelocity; // reach of the character's feet.
m_targetPosition -= (step_drop + gravity_drop);*/
btVector3 orig_position = m_targetPosition; btScalar downSpeed = (m_verticalVelocity < 0.0f) ? -m_verticalVelocity : 0.0f;
if (downSpeed > 0.0f && downSpeed > m_maxFallSpeed && (m_wasOnGround || !m_wasJumping)) {
btScalar downVelocity = (m_verticalVelocity<0.f?-m_verticalVelocity:0.f) * dt; downSpeed = m_maxFallSpeed;
if (downVelocity > 0.0 && downVelocity > m_fallSpeed && (m_wasOnGround || !m_wasJumping)) {
downVelocity = m_fallSpeed;
} }
btVector3 step_drop = getUpAxisDirections()[m_upAxis] * (m_currentStepOffset + downVelocity); // first sweep for ledge
m_targetPosition -= step_drop; btVector3 step = getUpAxisDirections()[m_upAxis] * (-(m_lastStepUp + downSpeed * dt));
btKinematicClosestNotMeConvexResultCallback callback(m_ghostObject, getUpAxisDirections()[m_upAxis], m_maxSlopeCosine); StepDownConvexResultCallback callback(m_ghostObject,
getUpAxisDirections()[m_upAxis],
m_currentPosition, step,
m_walkDirection,
m_maxSlopeCosine,
m_radius, m_halfHeight);
callback.m_collisionFilterGroup = getGhostObject()->getBroadphaseHandle()->m_collisionFilterGroup; callback.m_collisionFilterGroup = getGhostObject()->getBroadphaseHandle()->m_collisionFilterGroup;
callback.m_collisionFilterMask = getGhostObject()->getBroadphaseHandle()->m_collisionFilterMask; callback.m_collisionFilterMask = getGhostObject()->getBroadphaseHandle()->m_collisionFilterMask;
btKinematicClosestNotMeConvexResultCallback callback2 (m_ghostObject, getUpAxisDirections()[m_upAxis], m_maxSlopeCosine); btTransform start, end;
callback2.m_collisionFilterGroup = getGhostObject()->getBroadphaseHandle()->m_collisionFilterGroup; start.setIdentity();
callback2.m_collisionFilterMask = getGhostObject()->getBroadphaseHandle()->m_collisionFilterMask; end.setIdentity();
while (1) { start.setOrigin(m_currentPosition);
start.setIdentity(); m_targetPosition = m_currentPosition + step;
end.setIdentity(); end.setOrigin(m_targetPosition);
m_ghostObject->convexSweepTest(m_convexShape, start, end, callback, collisionWorld->getDispatchInfo().m_allowedCcdPenetration);
end_double.setIdentity(); if (callback.hasHit()) {
m_currentPosition += callback.m_closestHitFraction * step;
m_verticalVelocity = 0.0f;
m_verticalOffset = 0.0f;
m_wasJumping = false;
} else {
// sweep again for floor within downStep threshold
StepDownConvexResultCallback callback2 (m_ghostObject,
getUpAxisDirections()[m_upAxis],
m_currentPosition, step,
m_walkDirection,
m_maxSlopeCosine,
m_radius, m_halfHeight);
callback2.m_collisionFilterGroup = getGhostObject()->getBroadphaseHandle()->m_collisionFilterGroup;
callback2.m_collisionFilterMask = getGhostObject()->getBroadphaseHandle()->m_collisionFilterMask;
m_currentPosition = m_targetPosition;
btVector3 oldPosition = m_currentPosition;
step = (- m_stepHeight) * getUpAxisDirections()[m_upAxis];
m_targetPosition = m_currentPosition + step;
start.setOrigin(m_currentPosition); start.setOrigin(m_currentPosition);
end.setOrigin(m_targetPosition); end.setOrigin(m_targetPosition);
m_ghostObject->convexSweepTest(m_convexShape, start, end, callback2, collisionWorld->getDispatchInfo().m_allowedCcdPenetration);
//set double test for 2x the step drop, to check for a large drop vs small drop if (callback2.hasHit()) {
end_double.setOrigin(m_targetPosition - step_drop); m_currentPosition += callback2.m_closestHitFraction * step;
m_verticalVelocity = 0.0f;
if (m_useGhostObjectSweepTest) { m_verticalOffset = 0.0f;
m_ghostObject->convexSweepTest(m_convexShape, start, end, callback, collisionWorld->getDispatchInfo().m_allowedCcdPenetration); m_wasJumping = false;
if (!callback.hasHit()) {
//test a double fall height, to see if the character should interpolate it's fall (full) or not (partial)
m_ghostObject->convexSweepTest(m_convexShape, start, end_double, callback2, collisionWorld->getDispatchInfo().m_allowedCcdPenetration);
}
} else { } else {
collisionWorld->convexSweepTest(m_convexShape, start, end, callback, collisionWorld->getDispatchInfo().m_allowedCcdPenetration); // nothing to step down on, so remove the stepUp effect
m_currentPosition = oldPosition - m_lastStepUp * getUpAxisDirections()[m_upAxis];
if (!callback.hasHit()) { m_lastStepUp = 0.0f;
//test a double fall height, to see if the character should interpolate it's fall (large) or not (small)
collisionWorld->convexSweepTest(m_convexShape, start, end_double, callback2, collisionWorld->getDispatchInfo().m_allowedCcdPenetration);
}
} }
btScalar downVelocity2 = (m_verticalVelocity<0.f?-m_verticalVelocity:0.f) * dt;
bool has_hit = false;
if(bounce_fix == true) {
has_hit = callback.hasHit() || callback2.hasHit();
} else {
has_hit = callback2.hasHit();
}
if(downVelocity2 > 0.0 && downVelocity2 < m_stepHeight && has_hit == true && runonce == false
&& (m_wasOnGround || !m_wasJumping)) {
//redo the velocity calculation when falling a small amount, for fast stairs motion
//for larger falls, use the smoother/slower interpolated movement by not touching the target position
m_targetPosition = orig_position;
downVelocity = m_stepHeight;
btVector3 step_drop = getUpAxisDirections()[m_upAxis] * (m_currentStepOffset + downVelocity);
m_targetPosition -= step_drop;
runonce = true;
continue; //re-run previous tests
}
break;
}
if (callback.hasHit() || runonce == true) {
// we dropped a fraction of the height -> hit floor
btScalar fraction = (m_currentPosition.getY() - callback.m_hitPointWorld.getY()) / 2;
//printf("hitpoint: %g - pos %g\n", callback.m_hitPointWorld.getY(), m_currentPosition.getY());
if (bounce_fix == true) {
if (full_drop == true) {
m_currentPosition.setInterpolate3 (m_currentPosition, m_targetPosition, callback.m_closestHitFraction);
} else {
//due to errors in the closestHitFraction variable when used with large polygons, calculate the hit fraction manually
m_currentPosition.setInterpolate3 (m_currentPosition, m_targetPosition, fraction);
}
}
else
m_currentPosition.setInterpolate3 (m_currentPosition, m_targetPosition, callback.m_closestHitFraction);
full_drop = false;
m_verticalVelocity = 0.0;
m_verticalOffset = 0.0;
m_wasJumping = false;
} else {
// we dropped the full height
full_drop = true;
if (bounce_fix == true) {
downVelocity = (m_verticalVelocity<0.f?-m_verticalVelocity:0.f) * dt;
if (downVelocity > m_fallSpeed && (m_wasOnGround || !m_wasJumping)) {
m_targetPosition += step_drop; //undo previous target change
downVelocity = m_fallSpeed;
step_drop = getUpAxisDirections()[m_upAxis] * (m_currentStepOffset + downVelocity);
m_targetPosition -= step_drop;
}
}
//printf("full drop - %g, %g\n", m_currentPosition.getY(), m_targetPosition.getY());
m_currentPosition = m_targetPosition;
} }
} }
void CharacterController::setWalkDirection(const btVector3& walkDirection) { void CharacterController::setWalkDirection(const btVector3& walkDirection) {
m_useWalkDirection = true; m_useWalkDirection = true;
m_walkDirection = walkDirection; m_walkDirection = walkDirection;
m_normalizedDirection = getNormalizedVector(m_walkDirection); m_normalizedDirection = getNormalizedVector(m_walkDirection);
} }
void CharacterController::setVelocityForTimeInterval(const btVector3& velocity, btScalar timeInterval) { void CharacterController::setVelocityForTimeInterval(const btVector3& velocity, btScalar timeInterval) {
//printf("setVelocity!\n");
//printf(" interval: %f\n", timeInterval);
//printf(" velocity: (%f, %f, %f)\n", velocity.x(), velocity.y(), velocity.z());
m_useWalkDirection = false; m_useWalkDirection = false;
m_walkDirection = velocity; m_walkDirection = velocity;
m_normalizedDirection = getNormalizedVector(m_walkDirection); m_normalizedDirection = getNormalizedVector(m_walkDirection);
@ -565,29 +591,25 @@ void CharacterController::warp(const btVector3& origin) {
void CharacterController::preStep( btCollisionWorld* collisionWorld) { void CharacterController::preStep( btCollisionWorld* collisionWorld) {
if (!m_enabled) {
return;
}
int numPenetrationLoops = 0; int numPenetrationLoops = 0;
m_touchingContact = false; m_touchingContact = false;
while (recoverFromPenetration(collisionWorld)) { while (recoverFromPenetration(collisionWorld)) {
numPenetrationLoops++; numPenetrationLoops++;
m_touchingContact = true; m_touchingContact = true;
if (numPenetrationLoops > 4) { if (numPenetrationLoops > 4) {
//printf("character could not recover from penetration = %d\n", numPenetrationLoops);
break; break;
} }
} }
m_currentPosition = m_ghostObject->getWorldTransform().getOrigin(); m_currentPosition = m_ghostObject->getWorldTransform().getOrigin();
m_targetPosition = m_currentPosition; m_targetPosition = m_currentPosition;
//printf("m_targetPosition=%f,%f,%f\n", m_targetPosition[0], m_targetPosition[1], m_targetPosition[2]);
} }
void CharacterController::playerStep( btCollisionWorld* collisionWorld, btScalar dt) { void CharacterController::playerStep( btCollisionWorld* collisionWorld, btScalar dt) {
//printf("playerStep(): "); if (!m_enabled || (!m_useWalkDirection && m_velocityTimeInterval <= 0.0)) {
//printf(" dt = %f", dt);
// quick check...
if (!m_useWalkDirection && m_velocityTimeInterval <= 0.0) {
//printf("\n");
return; // no motion return; // no motion
} }
@ -595,49 +617,41 @@ void CharacterController::playerStep( btCollisionWorld* collisionWorld, btScala
// Update fall velocity. // Update fall velocity.
m_verticalVelocity -= m_gravity * dt; m_verticalVelocity -= m_gravity * dt;
if (m_verticalVelocity > 0.0 && m_verticalVelocity > m_jumpSpeed) { if (m_verticalVelocity > m_jumpSpeed) {
m_verticalVelocity = m_jumpSpeed; m_verticalVelocity = m_jumpSpeed;
} } else if (m_verticalVelocity < -m_maxFallSpeed) {
if (m_verticalVelocity < 0.0 && btFabs(m_verticalVelocity) > btFabs(m_fallSpeed)) { m_verticalVelocity = -m_maxFallSpeed;
m_verticalVelocity = -btFabs(m_fallSpeed);
} }
m_verticalOffset = m_verticalVelocity * dt; m_verticalOffset = m_verticalVelocity * dt;
btTransform xform; btTransform xform;
xform = m_ghostObject->getWorldTransform(); xform = m_ghostObject->getWorldTransform();
//printf("walkDirection(%f,%f,%f)\n", walkDirection[0], walkDirection[1], walkDirection[2]); // the algorithm is as follows:
//printf("walkSpeed=%f\n", walkSpeed); // (1) step the character up a little bit so that its forward step doesn't hit the floor
// (2) step the character forward
// (3) step the character down looking for new ledges, the original floor, or a floor one step below where we started
stepUp (collisionWorld); stepUp(collisionWorld);
if (m_useWalkDirection) { if (m_useWalkDirection) {
stepForwardAndStrafe(collisionWorld, m_walkDirection); stepForward(collisionWorld, m_walkDirection);
} else { } else {
//printf(" time: %f", m_velocityTimeInterval); // compute substep and decrement total interval
// still have some time left for moving! btScalar dtMoving = (dt < m_velocityTimeInterval) ? dt : m_velocityTimeInterval;
btScalar dtMoving =
(dt < m_velocityTimeInterval) ? dt : m_velocityTimeInterval;
m_velocityTimeInterval -= dt; m_velocityTimeInterval -= dt;
// how far will we move while we are moving? // stepForward substep
btVector3 move = m_walkDirection * dtMoving; btVector3 move = m_walkDirection * dtMoving;
stepForward(collisionWorld, move);
//printf(" dtMoving: %f", dtMoving);
// okay, step
stepForwardAndStrafe(collisionWorld, move);
} }
stepDown(collisionWorld, dt); stepDown(collisionWorld, dt);
//printf("\n");
xform.setOrigin(m_currentPosition); xform.setOrigin(m_currentPosition);
m_ghostObject->setWorldTransform(xform); m_ghostObject->setWorldTransform(xform);
} }
void CharacterController::setFallSpeed(btScalar fallSpeed) { void CharacterController::setMaxFallSpeed(btScalar speed) {
m_fallSpeed = fallSpeed; m_maxFallSpeed = speed;
} }
void CharacterController::setJumpSpeed(btScalar jumpSpeed) { void CharacterController::setJumpSpeed(btScalar jumpSpeed) {
@ -662,7 +676,7 @@ void CharacterController::jump() {
#if 0 #if 0
currently no jumping. currently no jumping.
btTransform xform; btTransform xform;
m_rigidBody->getMotionState()->getWorldTransform(xform); m_rigidBody->getMotionState()->getWorldTransform(xform);
btVector3 up = xform.getBasis()[1]; btVector3 up = xform.getBasis()[1];
up.normalize(); up.normalize();
@ -689,7 +703,7 @@ btScalar CharacterController::getMaxSlope() const {
} }
bool CharacterController::onGround() const { bool CharacterController::onGround() const {
return m_verticalVelocity == 0.0 && m_verticalOffset == 0.0; return m_enabled && m_verticalVelocity == 0.0 && m_verticalOffset == 0.0;
} }
btVector3* CharacterController::getUpAxisDirections() { btVector3* CharacterController::getUpAxisDirections() {
@ -704,3 +718,109 @@ void CharacterController::debugDraw(btIDebugDraw* debugDrawer) {
void CharacterController::setUpInterpolate(bool value) { void CharacterController::setUpInterpolate(bool value) {
m_interpolateUp = value; m_interpolateUp = value;
} }
// protected
void CharacterController::createShapeAndGhost() {
// get new dimensions from avatar
m_avatarData->lockForRead();
AABox box = m_avatarData->getLocalAABox();
// create new ghost
m_ghostObject = new btPairCachingGhostObject();
m_ghostObject->setWorldTransform(btTransform(glmToBullet(m_avatarData->getOrientation()),
glmToBullet(m_avatarData->getPosition())));
m_avatarData->unlock();
const glm::vec3& diagonal = box.getScale();
m_radius = 0.5f * sqrtf(0.5f * (diagonal.x * diagonal.x + diagonal.z * diagonal.z));
m_halfHeight = 0.5f * diagonal.y - m_radius;
float MIN_HALF_HEIGHT = 0.1f;
if (m_halfHeight < MIN_HALF_HEIGHT) {
m_halfHeight = MIN_HALF_HEIGHT;
}
glm::vec3 offset = box.getCorner() + 0.5f * diagonal;
m_shapeLocalOffset = offset;
// stepHeight affects the heights of ledges that the character can ascend
// however the actual ledge height is some function of m_stepHeight
// due to character shape and this CharacterController algorithm
// (the function is approximately 2*m_stepHeight)
m_stepHeight = 0.1f * (m_radius + m_halfHeight) + 0.1f;
// create new shape
m_convexShape = new btCapsuleShape(m_radius, 2.0f * m_halfHeight);
m_ghostObject->setCollisionShape(m_convexShape);
m_ghostObject->setCollisionFlags(btCollisionObject::CF_CHARACTER_OBJECT);
}
bool CharacterController::needsShapeUpdate() {
// get new dimensions from avatar
m_avatarData->lockForRead();
AABox box = m_avatarData->getLocalAABox();
m_avatarData->unlock();
const glm::vec3& diagonal = box.getScale();
float radius = 0.5f * sqrtf(0.5f * (diagonal.x * diagonal.x + diagonal.z * diagonal.z));
float halfHeight = 0.5f * diagonal.y - radius;
float MIN_HALF_HEIGHT = 0.1f;
if (halfHeight < MIN_HALF_HEIGHT) {
halfHeight = MIN_HALF_HEIGHT;
}
glm::vec3 offset = box.getCorner() + 0.5f * diagonal;
// compare dimensions (and offset)
float radiusDelta = glm::abs(radius - m_radius);
float heightDelta = glm::abs(halfHeight - m_halfHeight);
if (radiusDelta < FLT_EPSILON && heightDelta < FLT_EPSILON) {
// shape hasn't changed --> nothing to do
float offsetDelta = glm::distance(offset, m_shapeLocalOffset);
if (offsetDelta > FLT_EPSILON) {
// if only the offset changes then we can update it --> no need to rebuild shape
m_shapeLocalOffset = offset;
}
return false;
}
return true;
}
void CharacterController::updateShape() {
// DANGER: make sure this CharacterController and its GhostShape have been removed from
// the PhysicsEngine before calling this.
// delete shape and GhostObject
delete m_ghostObject;
m_ghostObject = NULL;
delete m_convexShape;
m_convexShape = NULL;
createShapeAndGhost();
}
void CharacterController::preSimulation(btScalar timeStep) {
m_avatarData->lockForRead();
// cache the "PhysicsEnabled" state of m_avatarData here
// and use the cached value for the rest of the simulation step
m_enabled = m_avatarData->isPhysicsEnabled();
glm::quat rotation = m_avatarData->getOrientation();
glm::vec3 position = m_avatarData->getPosition() + rotation * m_shapeLocalOffset;
m_ghostObject->setWorldTransform(btTransform(glmToBullet(rotation), glmToBullet(position)));
btVector3 walkVelocity = glmToBullet(m_avatarData->getVelocity());
setVelocityForTimeInterval(walkVelocity, timeStep);
m_avatarData->unlock();
}
void CharacterController::postSimulation() {
if (m_enabled) {
m_avatarData->lockForWrite();
const btTransform& avatarTransform = m_ghostObject->getWorldTransform();
glm::quat rotation = bulletToGLM(avatarTransform.getRotation());
glm::vec3 offset = rotation * m_shapeLocalOffset;
m_avatarData->setOrientation(rotation);
m_avatarData->setPosition(bulletToGLM(avatarTransform.getOrigin()) - offset);
m_avatarData->unlock();
}
}

View file

@ -19,6 +19,8 @@ subject to the following restrictions:
#ifndef hifi_CharacterController_h #ifndef hifi_CharacterController_h
#define hifi_CharacterController_h #define hifi_CharacterController_h
#include <AvatarData.h>
#include <btBulletDynamicsCommon.h> #include <btBulletDynamicsCommon.h>
#include <BulletDynamics/Character/btCharacterControllerInterface.h> #include <BulletDynamics/Character/btCharacterControllerInterface.h>
#include <BulletCollision/BroadphaseCollision/btCollisionAlgorithm.h> #include <BulletCollision/BroadphaseCollision/btCollisionAlgorithm.h>
@ -39,14 +41,17 @@ ATTRIBUTE_ALIGNED16(class) CharacterController : public btCharacterControllerInt
{ {
protected: protected:
AvatarData* m_avatarData = NULL;
btPairCachingGhostObject* m_ghostObject;
glm::vec3 m_shapeLocalOffset;
btConvexShape* m_convexShape;//is also in m_ghostObject, but it needs to be convex, so we store it here to avoid upcast
btScalar m_radius;
btScalar m_halfHeight; btScalar m_halfHeight;
btPairCachingGhostObject* m_ghostObject;
btConvexShape* m_convexShape;//is also in m_ghostObject, but it needs to be convex, so we store it here to avoid upcast
btScalar m_verticalVelocity; btScalar m_verticalVelocity;
btScalar m_verticalOffset; btScalar m_verticalOffset; // fall distance from velocity this frame
btScalar m_fallSpeed; btScalar m_maxFallSpeed;
btScalar m_jumpSpeed; btScalar m_jumpSpeed;
btScalar m_maxJumpHeight; btScalar m_maxJumpHeight;
btScalar m_maxSlopeRadians; // Slope angle that is set (used for returning the exact value) btScalar m_maxSlopeRadians; // Slope angle that is set (used for returning the exact value)
@ -55,7 +60,7 @@ protected:
btScalar m_turnAngle; btScalar m_turnAngle;
btScalar m_stepHeight; btScalar m_stepHeight; // height of stepUp prior to stepForward
btScalar m_addedMargin;//@todo: remove this and fix the code btScalar m_addedMargin;//@todo: remove this and fix the code
@ -65,18 +70,18 @@ protected:
//some internal variables //some internal variables
btVector3 m_currentPosition; btVector3 m_currentPosition;
btScalar m_currentStepOffset;
btVector3 m_targetPosition; btVector3 m_targetPosition;
btScalar m_lastStepUp;
///keep track of the contact manifolds ///keep track of the contact manifolds
btManifoldArray m_manifoldArray; btManifoldArray m_manifoldArray;
bool m_touchingContact; bool m_touchingContact;
btVector3 m_touchingNormal; btVector3 m_floorNormal; // points from object to character
bool m_wasOnGround; bool m_enabled;
bool m_wasJumping; bool m_wasOnGround;
bool m_useGhostObjectSweepTest; bool m_wasJumping;
bool m_useWalkDirection; bool m_useWalkDirection;
btScalar m_velocityTimeInterval; btScalar m_velocityTimeInterval;
int m_upAxis; int m_upAxis;
@ -93,17 +98,14 @@ protected:
bool recoverFromPenetration(btCollisionWorld* collisionWorld); bool recoverFromPenetration(btCollisionWorld* collisionWorld);
void stepUp(btCollisionWorld* collisionWorld); void stepUp(btCollisionWorld* collisionWorld);
void updateTargetPositionBasedOnCollision(const btVector3& hit_normal, btScalar tangentMag = btScalar(0.0), btScalar normalMag = btScalar(1.0)); void updateTargetPositionBasedOnCollision(const btVector3& hit_normal, btScalar tangentMag = btScalar(0.0), btScalar normalMag = btScalar(1.0));
void stepForwardAndStrafe(btCollisionWorld* collisionWorld, const btVector3& walkMove); void stepForward(btCollisionWorld* collisionWorld, const btVector3& walkMove);
void stepDown(btCollisionWorld* collisionWorld, btScalar dt); void stepDown(btCollisionWorld* collisionWorld, btScalar dt);
void createShapeAndGhost();
public: public:
BT_DECLARE_ALIGNED_ALLOCATOR(); BT_DECLARE_ALIGNED_ALLOCATOR();
CharacterController( CharacterController(AvatarData* avatarData);
btPairCachingGhostObject* ghostObject,
btConvexShape* convexShape,
btScalar stepHeight,
int upAxis = 1);
~CharacterController(); ~CharacterController();
@ -145,7 +147,7 @@ public:
void preStep(btCollisionWorld* collisionWorld); void preStep(btCollisionWorld* collisionWorld);
void playerStep(btCollisionWorld* collisionWorld, btScalar dt); void playerStep(btCollisionWorld* collisionWorld, btScalar dt);
void setFallSpeed(btScalar fallSpeed); void setMaxFallSpeed(btScalar speed);
void setJumpSpeed(btScalar jumpSpeed); void setJumpSpeed(btScalar jumpSpeed);
void setMaxJumpHeight(btScalar maxJumpHeight); void setMaxJumpHeight(btScalar maxJumpHeight);
bool canJump() const; bool canJump() const;
@ -161,12 +163,15 @@ public:
btScalar getMaxSlope() const; btScalar getMaxSlope() const;
btPairCachingGhostObject* getGhostObject(); btPairCachingGhostObject* getGhostObject();
void setUseGhostSweepTest(bool useGhostObjectSweepTest) {
m_useGhostObjectSweepTest = useGhostObjectSweepTest;
}
bool onGround() const; bool onGround() const;
void setUpInterpolate(bool value); void setUpInterpolate(bool value);
bool needsShapeUpdate();
void updateShape();
void preSimulation(btScalar timeStep);
void postSimulation();
}; };
#endif // hifi_CharacterController_h #endif // hifi_CharacterController_h

View file

@ -24,8 +24,7 @@ uint32_t PhysicsEngine::getNumSubsteps() {
} }
PhysicsEngine::PhysicsEngine(const glm::vec3& offset) PhysicsEngine::PhysicsEngine(const glm::vec3& offset)
: _originOffset(offset), : _originOffset(offset) {
_avatarShapeLocalOffset(0.0f) {
} }
PhysicsEngine::~PhysicsEngine() { PhysicsEngine::~PhysicsEngine() {
@ -279,9 +278,6 @@ void PhysicsEngine::init(EntityEditPacketSender* packetSender) {
} }
void PhysicsEngine::stepSimulation() { void PhysicsEngine::stepSimulation() {
// expect the engine to have an avatar (and hence: a character controller)
assert(_avatarData);
lock(); lock();
// NOTE: the grand order of operations is: // NOTE: the grand order of operations is:
// (1) relay incoming changes // (1) relay incoming changes
@ -298,19 +294,11 @@ void PhysicsEngine::stepSimulation() {
_clock.reset(); _clock.reset();
float timeStep = btMin(dt, MAX_TIMESTEP); float timeStep = btMin(dt, MAX_TIMESTEP);
_avatarData->lockForRead();
if (_avatarData->isPhysicsEnabled()) {
// update character controller
glm::quat rotation = _avatarData->getOrientation();
glm::vec3 position = _avatarData->getPosition() + rotation * _avatarShapeLocalOffset;
_avatarGhostObject->setWorldTransform(btTransform(glmToBullet(rotation), glmToBullet(position)));
btVector3 walkVelocity = glmToBullet(_avatarData->getVelocity());
_characterController->setVelocityForTimeInterval(walkVelocity, timeStep);
}
_avatarData->unlock();
// This is step (2). // This is step (2).
if (_characterController) {
_characterController->preSimulation(timeStep);
}
int numSubsteps = _dynamicsWorld->stepSimulation(timeStep, MAX_NUM_SUBSTEPS, PHYSICS_ENGINE_FIXED_SUBSTEP); int numSubsteps = _dynamicsWorld->stepSimulation(timeStep, MAX_NUM_SUBSTEPS, PHYSICS_ENGINE_FIXED_SUBSTEP);
_numSubsteps += (uint32_t)numSubsteps; _numSubsteps += (uint32_t)numSubsteps;
stepNonPhysicalKinematics(usecTimestampNow()); stepNonPhysicalKinematics(usecTimestampNow());
@ -326,21 +314,11 @@ void PhysicsEngine::stepSimulation() {
// //
// TODO: untangle these lock sequences. // TODO: untangle these lock sequences.
_entityTree->lockForWrite(); _entityTree->lockForWrite();
lock(); lock();
_dynamicsWorld->synchronizeMotionStates(); _dynamicsWorld->synchronizeMotionStates();
_avatarData->lockForRead(); if (_characterController) {
bool avatarHasPhysicsEnabled = _avatarData->isPhysicsEnabled(); _characterController->postSimulation();
_avatarData->unlock();
if (avatarHasPhysicsEnabled) {
const btTransform& avatarTransform = _avatarGhostObject->getWorldTransform();
glm::quat rotation = bulletToGLM(avatarTransform.getRotation());
glm::vec3 offset = rotation * _avatarShapeLocalOffset;
_avatarData->lockForWrite();
_avatarData->setOrientation(rotation);
_avatarData->setPosition(bulletToGLM(avatarTransform.getOrigin()) - offset);
_avatarData->unlock();
} }
unlock(); unlock();
@ -620,76 +598,35 @@ bool PhysicsEngine::updateObjectHard(btRigidBody* body, ObjectMotionState* motio
return true; return true;
} }
void PhysicsEngine::setAvatarData(AvatarData *avatarData) { void PhysicsEngine::setAvatarData(AvatarData *avatarData) {
assert(avatarData); // don't pass NULL argument if (_characterController) {
bool needsShapeUpdate = _characterController->needsShapeUpdate();
// compute capsule dimensions if (needsShapeUpdate) {
AABox box = avatarData->getLocalAABox(); lock();
const glm::vec3& diagonal = box.getScale(); // remove old info
float radius = 0.5f * sqrtf(0.5f * (diagonal.x * diagonal.x + diagonal.z * diagonal.z)); _dynamicsWorld->removeCollisionObject(_characterController->getGhostObject());
float halfHeight = 0.5f * diagonal.y - radius; _dynamicsWorld->removeAction(_characterController);
float MIN_HALF_HEIGHT = 0.1f; // update shape
if (halfHeight < MIN_HALF_HEIGHT) { _characterController->updateShape();
halfHeight = MIN_HALF_HEIGHT; // insert new info
} _dynamicsWorld->addCollisionObject(_characterController->getGhostObject(),
glm::vec3 offset = box.getCorner() + 0.5f * diagonal; btBroadphaseProxy::CharacterFilter,
btBroadphaseProxy::StaticFilter | btBroadphaseProxy::DefaultFilter);
if (!_avatarData) { _dynamicsWorld->addAction(_characterController);
// _avatarData is being initialized _characterController->reset(_dynamicsWorld);
_avatarData = avatarData; unlock();
} else {
// _avatarData is being updated
assert(_avatarData == avatarData);
// get old dimensions from shape
btCapsuleShape* capsule = static_cast<btCapsuleShape*>(_avatarGhostObject->getCollisionShape());
btScalar oldRadius = capsule->getRadius();
btScalar oldHalfHeight = capsule->getHalfHeight();
// compare dimensions (and offset)
float radiusDelta = glm::abs(radius - oldRadius);
float heightDelta = glm::abs(halfHeight - oldHalfHeight);
if (radiusDelta < FLT_EPSILON && heightDelta < FLT_EPSILON) {
// shape hasn't changed --> nothing to do
float offsetDelta = glm::distance(offset, _avatarShapeLocalOffset);
if (offsetDelta > FLT_EPSILON) {
// if only the offset changes then we can update it --> no need to rebuild shape
_avatarShapeLocalOffset = offset;
}
return;
} }
} else {
// delete old controller and friends // initialize _characterController
_dynamicsWorld->removeCollisionObject(_avatarGhostObject); assert(avatarData); // don't pass NULL argument
_dynamicsWorld->removeAction(_characterController); lock();
delete _characterController; _characterController = new CharacterController(avatarData);
_characterController = NULL; _dynamicsWorld->addCollisionObject(_characterController->getGhostObject(),
delete _avatarGhostObject; btBroadphaseProxy::CharacterFilter,
_avatarGhostObject = NULL; btBroadphaseProxy::StaticFilter | btBroadphaseProxy::DefaultFilter);
delete capsule; _dynamicsWorld->addAction(_characterController);
_characterController->reset(_dynamicsWorld);
unlock();
} }
// set offset
_avatarShapeLocalOffset = offset;
// build ghost, shape, and controller
_avatarGhostObject = new btPairCachingGhostObject();
_avatarGhostObject->setWorldTransform(btTransform(glmToBullet(_avatarData->getOrientation()),
glmToBullet(_avatarData->getPosition())));
// ?TODO: use ShapeManager to get avatar's shape?
btCapsuleShape* capsule = new btCapsuleShape(radius, 2.0f * halfHeight);
_avatarGhostObject->setCollisionShape(capsule);
_avatarGhostObject->setCollisionFlags(btCollisionObject::CF_CHARACTER_OBJECT);
const float MIN_STEP_HEIGHT = 0.35f;
btScalar stepHeight = glm::max(MIN_STEP_HEIGHT, radius + 0.5f * halfHeight);
_characterController = new CharacterController(_avatarGhostObject, capsule, stepHeight);
_dynamicsWorld->addCollisionObject(_avatarGhostObject, btBroadphaseProxy::CharacterFilter,
btBroadphaseProxy::StaticFilter | btBroadphaseProxy::DefaultFilter);
_dynamicsWorld->addAction(_characterController);
_characterController->reset(_dynamicsWorld);
} }

View file

@ -123,9 +123,6 @@ private:
/// character collisions /// character collisions
CharacterController* _characterController = NULL; CharacterController* _characterController = NULL;
class btPairCachingGhostObject* _avatarGhostObject = NULL;
AvatarData* _avatarData = NULL;
glm::vec3 _avatarShapeLocalOffset;
}; };
#endif // hifi_PhysicsEngine_h #endif // hifi_PhysicsEngine_h

View file

@ -725,21 +725,21 @@ bool Model::renderCore(float alpha, RenderMode mode, RenderArgs* args) {
const float DEFAULT_ALPHA_THRESHOLD = 0.5f; const float DEFAULT_ALPHA_THRESHOLD = 0.5f;
//renderMeshes(RenderMode mode, bool translucent, float alphaThreshold, bool hasTangents, bool hasSpecular, book isSkinned, args); //renderMeshes(batch, mode, translucent, alphaThreshold, hasTangents, hasSpecular, isSkinned, args, forceRenderMeshes);
int opaqueMeshPartsRendered = 0; int opaqueMeshPartsRendered = 0;
opaqueMeshPartsRendered += renderMeshes(batch, mode, false, DEFAULT_ALPHA_THRESHOLD, false, false, false, false, args); opaqueMeshPartsRendered += renderMeshes(batch, mode, false, DEFAULT_ALPHA_THRESHOLD, false, false, false, false, args, true);
opaqueMeshPartsRendered += renderMeshes(batch, mode, false, DEFAULT_ALPHA_THRESHOLD, false, false, false, true, args); opaqueMeshPartsRendered += renderMeshes(batch, mode, false, DEFAULT_ALPHA_THRESHOLD, false, false, false, true, args, true);
opaqueMeshPartsRendered += renderMeshes(batch, mode, false, DEFAULT_ALPHA_THRESHOLD, false, false, true, false, args); opaqueMeshPartsRendered += renderMeshes(batch, mode, false, DEFAULT_ALPHA_THRESHOLD, false, false, true, false, args, true);
opaqueMeshPartsRendered += renderMeshes(batch, mode, false, DEFAULT_ALPHA_THRESHOLD, false, false, true, true, args); opaqueMeshPartsRendered += renderMeshes(batch, mode, false, DEFAULT_ALPHA_THRESHOLD, false, false, true, true, args, true);
opaqueMeshPartsRendered += renderMeshes(batch, mode, false, DEFAULT_ALPHA_THRESHOLD, false, true, false, false, args); opaqueMeshPartsRendered += renderMeshes(batch, mode, false, DEFAULT_ALPHA_THRESHOLD, false, true, false, false, args, true);
opaqueMeshPartsRendered += renderMeshes(batch, mode, false, DEFAULT_ALPHA_THRESHOLD, false, true, false, true, args); opaqueMeshPartsRendered += renderMeshes(batch, mode, false, DEFAULT_ALPHA_THRESHOLD, false, true, false, true, args, true);
opaqueMeshPartsRendered += renderMeshes(batch, mode, false, DEFAULT_ALPHA_THRESHOLD, false, true, true, false, args); opaqueMeshPartsRendered += renderMeshes(batch, mode, false, DEFAULT_ALPHA_THRESHOLD, false, true, true, false, args, true);
opaqueMeshPartsRendered += renderMeshes(batch, mode, false, DEFAULT_ALPHA_THRESHOLD, false, true, true, true, args); opaqueMeshPartsRendered += renderMeshes(batch, mode, false, DEFAULT_ALPHA_THRESHOLD, false, true, true, true, args, true);
opaqueMeshPartsRendered += renderMeshes(batch, mode, false, DEFAULT_ALPHA_THRESHOLD, true, false, false, false, args); opaqueMeshPartsRendered += renderMeshes(batch, mode, false, DEFAULT_ALPHA_THRESHOLD, true, false, false, false, args, true);
opaqueMeshPartsRendered += renderMeshes(batch, mode, false, DEFAULT_ALPHA_THRESHOLD, true, false, true, false, args); opaqueMeshPartsRendered += renderMeshes(batch, mode, false, DEFAULT_ALPHA_THRESHOLD, true, false, true, false, args, true);
opaqueMeshPartsRendered += renderMeshes(batch, mode, false, DEFAULT_ALPHA_THRESHOLD, true, true, false, false, args); opaqueMeshPartsRendered += renderMeshes(batch, mode, false, DEFAULT_ALPHA_THRESHOLD, true, true, false, false, args, true);
opaqueMeshPartsRendered += renderMeshes(batch, mode, false, DEFAULT_ALPHA_THRESHOLD, true, true, true, false, args); opaqueMeshPartsRendered += renderMeshes(batch, mode, false, DEFAULT_ALPHA_THRESHOLD, true, true, true, false, args, true);
// render translucent meshes afterwards // render translucent meshes afterwards
//DependencyManager::get<TextureCache>()->setPrimaryDrawBuffers(false, true, true); //DependencyManager::get<TextureCache>()->setPrimaryDrawBuffers(false, true, true);
@ -753,14 +753,14 @@ bool Model::renderCore(float alpha, RenderMode mode, RenderArgs* args) {
int translucentMeshPartsRendered = 0; int translucentMeshPartsRendered = 0;
const float MOSTLY_OPAQUE_THRESHOLD = 0.75f; const float MOSTLY_OPAQUE_THRESHOLD = 0.75f;
translucentMeshPartsRendered += renderMeshes(batch, mode, true, MOSTLY_OPAQUE_THRESHOLD, false, false, false, false, args); translucentMeshPartsRendered += renderMeshes(batch, mode, true, MOSTLY_OPAQUE_THRESHOLD, false, false, false, false, args, true);
translucentMeshPartsRendered += renderMeshes(batch, mode, true, MOSTLY_OPAQUE_THRESHOLD, false, false, false, true, args); translucentMeshPartsRendered += renderMeshes(batch, mode, true, MOSTLY_OPAQUE_THRESHOLD, false, false, false, true, args, true);
translucentMeshPartsRendered += renderMeshes(batch, mode, true, MOSTLY_OPAQUE_THRESHOLD, false, false, true, false, args); translucentMeshPartsRendered += renderMeshes(batch, mode, true, MOSTLY_OPAQUE_THRESHOLD, false, false, true, false, args, true);
translucentMeshPartsRendered += renderMeshes(batch, mode, true, MOSTLY_OPAQUE_THRESHOLD, false, false, true, true, args); translucentMeshPartsRendered += renderMeshes(batch, mode, true, MOSTLY_OPAQUE_THRESHOLD, false, false, true, true, args, true);
translucentMeshPartsRendered += renderMeshes(batch, mode, true, MOSTLY_OPAQUE_THRESHOLD, false, true, false, false, args); translucentMeshPartsRendered += renderMeshes(batch, mode, true, MOSTLY_OPAQUE_THRESHOLD, false, true, false, false, args, true);
translucentMeshPartsRendered += renderMeshes(batch, mode, true, MOSTLY_OPAQUE_THRESHOLD, false, true, false, true, args); translucentMeshPartsRendered += renderMeshes(batch, mode, true, MOSTLY_OPAQUE_THRESHOLD, false, true, false, true, args, true);
translucentMeshPartsRendered += renderMeshes(batch, mode, true, MOSTLY_OPAQUE_THRESHOLD, false, true, true, false, args); translucentMeshPartsRendered += renderMeshes(batch, mode, true, MOSTLY_OPAQUE_THRESHOLD, false, true, true, false, args, true);
translucentMeshPartsRendered += renderMeshes(batch, mode, true, MOSTLY_OPAQUE_THRESHOLD, false, true, true, true, args); translucentMeshPartsRendered += renderMeshes(batch, mode, true, MOSTLY_OPAQUE_THRESHOLD, false, true, true, true, args, true);
GLBATCH(glDisable)(GL_ALPHA_TEST); GLBATCH(glDisable)(GL_ALPHA_TEST);
GLBATCH(glEnable)(GL_BLEND); GLBATCH(glEnable)(GL_BLEND);
@ -777,14 +777,14 @@ bool Model::renderCore(float alpha, RenderMode mode, RenderArgs* args) {
if (mode == DEFAULT_RENDER_MODE || mode == DIFFUSE_RENDER_MODE) { if (mode == DEFAULT_RENDER_MODE || mode == DIFFUSE_RENDER_MODE) {
const float MOSTLY_TRANSPARENT_THRESHOLD = 0.0f; const float MOSTLY_TRANSPARENT_THRESHOLD = 0.0f;
translucentMeshPartsRendered += renderMeshes(batch, mode, true, MOSTLY_TRANSPARENT_THRESHOLD, false, false, false, false, args); translucentMeshPartsRendered += renderMeshes(batch, mode, true, MOSTLY_TRANSPARENT_THRESHOLD, false, false, false, false, args, true);
translucentMeshPartsRendered += renderMeshes(batch, mode, true, MOSTLY_TRANSPARENT_THRESHOLD, false, false, false, true, args); translucentMeshPartsRendered += renderMeshes(batch, mode, true, MOSTLY_TRANSPARENT_THRESHOLD, false, false, false, true, args, true);
translucentMeshPartsRendered += renderMeshes(batch, mode, true, MOSTLY_TRANSPARENT_THRESHOLD, false, false, true, false, args); translucentMeshPartsRendered += renderMeshes(batch, mode, true, MOSTLY_TRANSPARENT_THRESHOLD, false, false, true, false, args, true);
translucentMeshPartsRendered += renderMeshes(batch, mode, true, MOSTLY_TRANSPARENT_THRESHOLD, false, false, true, true, args); translucentMeshPartsRendered += renderMeshes(batch, mode, true, MOSTLY_TRANSPARENT_THRESHOLD, false, false, true, true, args, true);
translucentMeshPartsRendered += renderMeshes(batch, mode, true, MOSTLY_TRANSPARENT_THRESHOLD, false, true, false, false, args); translucentMeshPartsRendered += renderMeshes(batch, mode, true, MOSTLY_TRANSPARENT_THRESHOLD, false, true, false, false, args, true);
translucentMeshPartsRendered += renderMeshes(batch, mode, true, MOSTLY_TRANSPARENT_THRESHOLD, false, true, false, true, args); translucentMeshPartsRendered += renderMeshes(batch, mode, true, MOSTLY_TRANSPARENT_THRESHOLD, false, true, false, true, args, true);
translucentMeshPartsRendered += renderMeshes(batch, mode, true, MOSTLY_TRANSPARENT_THRESHOLD, false, true, true, false, args); translucentMeshPartsRendered += renderMeshes(batch, mode, true, MOSTLY_TRANSPARENT_THRESHOLD, false, true, true, false, args, true);
translucentMeshPartsRendered += renderMeshes(batch, mode, true, MOSTLY_TRANSPARENT_THRESHOLD, false, true, true, true, args); translucentMeshPartsRendered += renderMeshes(batch, mode, true, MOSTLY_TRANSPARENT_THRESHOLD, false, true, true, true, args, true);
} }
GLBATCH(glDepthMask)(true); GLBATCH(glDepthMask)(true);
@ -843,9 +843,66 @@ bool Model::renderCore(float alpha, RenderMode mode, RenderArgs* args) {
args->_opaqueMeshPartsRendered = opaqueMeshPartsRendered; args->_opaqueMeshPartsRendered = opaqueMeshPartsRendered;
} }
#ifdef WANT_DEBUG_MESHBOXES
renderDebugMeshBoxes();
#endif
return true; return true;
} }
void Model::renderDebugMeshBoxes() {
int colorNdx = 0;
foreach(AABox box, _calculatedMeshBoxes) {
if (_debugMeshBoxesID == GeometryCache::UNKNOWN_ID) {
_debugMeshBoxesID = DependencyManager::get<GeometryCache>()->allocateID();
}
QVector<glm::vec3> points;
glm::vec3 brn = box.getCorner();
glm::vec3 bln = brn + glm::vec3(box.getDimensions().x, 0, 0);
glm::vec3 brf = brn + glm::vec3(0, 0, box.getDimensions().z);
glm::vec3 blf = brn + glm::vec3(box.getDimensions().x, 0, box.getDimensions().z);
glm::vec3 trn = brn + glm::vec3(0, box.getDimensions().y, 0);
glm::vec3 tln = bln + glm::vec3(0, box.getDimensions().y, 0);
glm::vec3 trf = brf + glm::vec3(0, box.getDimensions().y, 0);
glm::vec3 tlf = blf + glm::vec3(0, box.getDimensions().y, 0);
points << brn << bln;
points << brf << blf;
points << brn << brf;
points << bln << blf;
points << trn << tln;
points << trf << tlf;
points << trn << trf;
points << tln << tlf;
points << brn << trn;
points << brf << trf;
points << bln << tln;
points << blf << tlf;
glm::vec4 color[] = {
{ 1.0f, 0.0f, 0.0f, 1.0f }, // red
{ 0.0f, 1.0f, 0.0f, 1.0f }, // green
{ 0.0f, 0.0f, 1.0f, 1.0f }, // blue
{ 1.0f, 0.0f, 1.0f, 1.0f }, // purple
{ 1.0f, 1.0f, 0.0f, 1.0f }, // yellow
{ 0.0f, 1.0f, 1.0f, 1.0f }, // cyan
{ 1.0f, 1.0f, 1.0f, 1.0f }, // white
{ 0.0f, 0.5f, 0.0f, 1.0f },
{ 0.0f, 0.0f, 0.5f, 1.0f },
{ 0.5f, 0.0f, 0.5f, 1.0f },
{ 0.5f, 0.5f, 0.0f, 1.0f },
{ 0.0f, 0.5f, 0.5f, 1.0f } };
DependencyManager::get<GeometryCache>()->updateVertices(_debugMeshBoxesID, points, color[colorNdx]);
DependencyManager::get<GeometryCache>()->renderVertices(gpu::LINES, _debugMeshBoxesID);
colorNdx++;
}
}
Extents Model::getBindExtents() const { Extents Model::getBindExtents() const {
if (!isActive()) { if (!isActive()) {
return Extents(); return Extents();
@ -1306,8 +1363,11 @@ void Model::updateJointState(int index) {
glm::mat4 parentTransform = glm::scale(_scale) * glm::translate(_offset) * geometry.offset; glm::mat4 parentTransform = glm::scale(_scale) * glm::translate(_offset) * geometry.offset;
state.computeTransform(parentTransform); state.computeTransform(parentTransform);
} else { } else {
const JointState& parentState = _jointStates.at(parentIndex); // guard against out-of-bounds access to _jointStates
state.computeTransform(parentState.getTransform(), parentState.getTransformChanged()); if (joint.parentIndex >= 0 && joint.parentIndex < _jointStates.size()) {
const JointState& parentState = _jointStates.at(parentIndex);
state.computeTransform(parentState.getTransform(), parentState.getTransformChanged());
}
} }
} }
@ -2299,7 +2359,8 @@ int Model::renderMeshesForModelsInScene(gpu::Batch& batch, RenderMode mode, bool
} }
int Model::renderMeshes(gpu::Batch& batch, RenderMode mode, bool translucent, float alphaThreshold, int Model::renderMeshes(gpu::Batch& batch, RenderMode mode, bool translucent, float alphaThreshold,
bool hasLightmap, bool hasTangents, bool hasSpecular, bool isSkinned, RenderArgs* args) { bool hasLightmap, bool hasTangents, bool hasSpecular, bool isSkinned, RenderArgs* args,
bool forceRenderSomeMeshes) {
PROFILE_RANGE(__FUNCTION__); PROFILE_RANGE(__FUNCTION__);
int meshPartsRendered = 0; int meshPartsRendered = 0;
@ -2319,8 +2380,10 @@ int Model::renderMeshes(gpu::Batch& batch, RenderMode mode, bool translucent, fl
Locations* locations; Locations* locations;
SkinLocations* skinLocations; SkinLocations* skinLocations;
pickPrograms(batch, mode, translucent, alphaThreshold, hasLightmap, hasTangents, hasSpecular, isSkinned, args, locations, skinLocations); pickPrograms(batch, mode, translucent, alphaThreshold, hasLightmap, hasTangents, hasSpecular, isSkinned,
meshPartsRendered = renderMeshesFromList(list, batch, mode, translucent, alphaThreshold, args, locations, skinLocations); args, locations, skinLocations);
meshPartsRendered = renderMeshesFromList(list, batch, mode, translucent, alphaThreshold,
args, locations, skinLocations, forceRenderSomeMeshes);
GLBATCH(glUseProgram)(0); GLBATCH(glUseProgram)(0);
return meshPartsRendered; return meshPartsRendered;
@ -2328,7 +2391,7 @@ int Model::renderMeshes(gpu::Batch& batch, RenderMode mode, bool translucent, fl
int Model::renderMeshesFromList(QVector<int>& list, gpu::Batch& batch, RenderMode mode, bool translucent, float alphaThreshold, RenderArgs* args, int Model::renderMeshesFromList(QVector<int>& list, gpu::Batch& batch, RenderMode mode, bool translucent, float alphaThreshold, RenderArgs* args,
Locations* locations, SkinLocations* skinLocations) { Locations* locations, SkinLocations* skinLocations, bool forceRenderSomeMeshes) {
PROFILE_RANGE(__FUNCTION__); PROFILE_RANGE(__FUNCTION__);
auto textureCache = DependencyManager::get<TextureCache>(); auto textureCache = DependencyManager::get<TextureCache>();
@ -2364,11 +2427,21 @@ int Model::renderMeshesFromList(QVector<int>& list, gpu::Batch& batch, RenderMod
// if we got here, then check to see if this mesh is in view // if we got here, then check to see if this mesh is in view
if (args) { if (args) {
bool shouldRender = true; bool shouldRender = true;
bool forceRender = false;
args->_meshesConsidered++; args->_meshesConsidered++;
if (args->_viewFrustum) { if (args->_viewFrustum) {
shouldRender = args->_viewFrustum->boxInFrustum(_calculatedMeshBoxes.at(i)) != ViewFrustum::OUTSIDE;
if (shouldRender) { // NOTE: This is a hack to address the fact that for avatar meshes, the _calculatedMeshBoxes can be wrong
// for some meshes. Those meshes where the mesh's modelTransform is the identity matrix, and will have
// incorrectly calculated mesh boxes. In this case, we will ignore the box and assume it's visible.
if (forceRenderSomeMeshes && (geometry.meshes.at(i).modelTransform == glm::mat4())) {
forceRender = true;
}
shouldRender = forceRender || args->_viewFrustum->boxInFrustum(_calculatedMeshBoxes.at(i)) != ViewFrustum::OUTSIDE;
if (shouldRender && !forceRender) {
float distance = args->_viewFrustum->distanceToCamera(_calculatedMeshBoxes.at(i).calcCenter()); float distance = args->_viewFrustum->distanceToCamera(_calculatedMeshBoxes.at(i).calcCenter());
shouldRender = !_viewState ? false : _viewState->shouldRenderMesh(_calculatedMeshBoxes.at(i).getLargestDimension(), shouldRender = !_viewState ? false : _viewState->shouldRenderMesh(_calculatedMeshBoxes.at(i).getLargestDimension(),
distance); distance);

View file

@ -444,6 +444,9 @@ private:
QVector<int> _meshesOpaqueLightmapTangentsSpecular; QVector<int> _meshesOpaqueLightmapTangentsSpecular;
QVector<int> _meshesOpaqueLightmapSpecular; QVector<int> _meshesOpaqueLightmapSpecular;
// debug rendering support
void renderDebugMeshBoxes();
int _debugMeshBoxesID = GeometryCache::UNKNOWN_ID;
// Scene rendering support // Scene rendering support
static QVector<Model*> _modelsInScene; static QVector<Model*> _modelsInScene;
@ -456,12 +459,15 @@ private:
void renderSetup(RenderArgs* args); void renderSetup(RenderArgs* args);
bool renderCore(float alpha, RenderMode mode, RenderArgs* args); bool renderCore(float alpha, RenderMode mode, RenderArgs* args);
int renderMeshes(gpu::Batch& batch, RenderMode mode, bool translucent, float alphaThreshold, int renderMeshes(gpu::Batch& batch, RenderMode mode, bool translucent, float alphaThreshold,
bool hasLightmap, bool hasTangents, bool hasSpecular, bool isSkinned, RenderArgs* args = NULL); bool hasLightmap, bool hasTangents, bool hasSpecular, bool isSkinned, RenderArgs* args = NULL,
bool forceRenderSomeMeshes = false);
void setupBatchTransform(gpu::Batch& batch); void setupBatchTransform(gpu::Batch& batch);
QVector<int>* pickMeshList(bool translucent, float alphaThreshold, bool hasLightmap, bool hasTangents, bool hasSpecular, bool isSkinned); QVector<int>* pickMeshList(bool translucent, float alphaThreshold, bool hasLightmap, bool hasTangents, bool hasSpecular, bool isSkinned);
int renderMeshesFromList(QVector<int>& list, gpu::Batch& batch, RenderMode mode, bool translucent, float alphaThreshold, int renderMeshesFromList(QVector<int>& list, gpu::Batch& batch, RenderMode mode, bool translucent, float alphaThreshold,
RenderArgs* args, Locations* locations, SkinLocations* skinLocations); RenderArgs* args, Locations* locations, SkinLocations* skinLocations,
bool forceRenderSomeMeshes = false);
static void pickPrograms(gpu::Batch& batch, RenderMode mode, bool translucent, float alphaThreshold, static void pickPrograms(gpu::Batch& batch, RenderMode mode, bool translucent, float alphaThreshold,
bool hasLightmap, bool hasTangents, bool hasSpecular, bool isSkinned, RenderArgs* args, bool hasLightmap, bool hasTangents, bool hasSpecular, bool isSkinned, RenderArgs* args,