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("look.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("notifications.js");
Script.load("controllers/oculus/goTo.js");
Script.load("utilities/LODWarning.js");
//Script.load("scripts.js"); // Not created yet

View file

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

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

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

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

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 QString INFO_HELP_PATH = "html/interface-welcome.html";
static const QString INFO_EDIT_ENTITIES_PATH = "html/edit-entities-commands.html";
static const QString INFO_EDIT_ENTITIES_PATH = "html/edit-commands.html";
#ifdef Q_OS_WIN
static const UINT UWM_IDENTIFY_INSTANCES =

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -701,6 +701,219 @@
</property>
</widget>
</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>
<spacer name="verticalSpacer_8">
<property name="orientation">
@ -717,6 +930,7 @@
</property>
</spacer>
</item>
<item>
<widget class="QLabel" name="avatarTitleLabel">
<property name="font">
@ -738,6 +952,7 @@
</property>
</widget>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_111">
<property name="spacing">
@ -820,6 +1035,9 @@
</item>
</layout>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_3">
<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;
// 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;

View file

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

View file

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

View file

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

View file

@ -123,9 +123,6 @@ private:
/// character collisions
CharacterController* _characterController = NULL;
class btPairCachingGhostObject* _avatarGhostObject = NULL;
AvatarData* _avatarData = NULL;
glm::vec3 _avatarShapeLocalOffset;
};
#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;
//renderMeshes(RenderMode mode, bool translucent, float alphaThreshold, bool hasTangents, bool hasSpecular, book isSkinned, args);
//renderMeshes(batch, mode, translucent, alphaThreshold, hasTangents, hasSpecular, isSkinned, args, forceRenderMeshes);
int opaqueMeshPartsRendered = 0;
opaqueMeshPartsRendered += renderMeshes(batch, mode, false, DEFAULT_ALPHA_THRESHOLD, false, false, false, false, args);
opaqueMeshPartsRendered += renderMeshes(batch, mode, false, DEFAULT_ALPHA_THRESHOLD, false, false, false, true, args);
opaqueMeshPartsRendered += renderMeshes(batch, mode, false, DEFAULT_ALPHA_THRESHOLD, false, false, true, false, args);
opaqueMeshPartsRendered += renderMeshes(batch, mode, false, DEFAULT_ALPHA_THRESHOLD, false, false, true, true, args);
opaqueMeshPartsRendered += renderMeshes(batch, mode, false, DEFAULT_ALPHA_THRESHOLD, false, true, false, false, args);
opaqueMeshPartsRendered += renderMeshes(batch, mode, false, DEFAULT_ALPHA_THRESHOLD, false, true, false, true, args);
opaqueMeshPartsRendered += renderMeshes(batch, mode, false, DEFAULT_ALPHA_THRESHOLD, false, true, true, false, args);
opaqueMeshPartsRendered += renderMeshes(batch, mode, false, DEFAULT_ALPHA_THRESHOLD, false, true, true, true, args);
opaqueMeshPartsRendered += renderMeshes(batch, mode, false, DEFAULT_ALPHA_THRESHOLD, false, false, false, false, args, true);
opaqueMeshPartsRendered += renderMeshes(batch, mode, false, DEFAULT_ALPHA_THRESHOLD, false, false, false, true, args, true);
opaqueMeshPartsRendered += renderMeshes(batch, mode, false, DEFAULT_ALPHA_THRESHOLD, false, false, true, false, args, true);
opaqueMeshPartsRendered += renderMeshes(batch, mode, false, DEFAULT_ALPHA_THRESHOLD, false, false, true, true, args, true);
opaqueMeshPartsRendered += renderMeshes(batch, mode, false, DEFAULT_ALPHA_THRESHOLD, false, true, false, false, args, true);
opaqueMeshPartsRendered += renderMeshes(batch, mode, false, DEFAULT_ALPHA_THRESHOLD, false, true, false, true, args, true);
opaqueMeshPartsRendered += renderMeshes(batch, mode, false, DEFAULT_ALPHA_THRESHOLD, false, true, true, false, args, true);
opaqueMeshPartsRendered += renderMeshes(batch, mode, false, DEFAULT_ALPHA_THRESHOLD, false, true, true, true, args, true);
opaqueMeshPartsRendered += renderMeshes(batch, mode, false, DEFAULT_ALPHA_THRESHOLD, true, false, false, false, args);
opaqueMeshPartsRendered += renderMeshes(batch, mode, false, DEFAULT_ALPHA_THRESHOLD, true, false, true, false, args);
opaqueMeshPartsRendered += renderMeshes(batch, mode, false, DEFAULT_ALPHA_THRESHOLD, true, true, false, false, args);
opaqueMeshPartsRendered += renderMeshes(batch, mode, false, DEFAULT_ALPHA_THRESHOLD, true, true, true, false, args);
opaqueMeshPartsRendered += renderMeshes(batch, mode, false, DEFAULT_ALPHA_THRESHOLD, true, false, false, false, args, true);
opaqueMeshPartsRendered += renderMeshes(batch, mode, false, DEFAULT_ALPHA_THRESHOLD, true, false, true, false, args, true);
opaqueMeshPartsRendered += renderMeshes(batch, mode, false, DEFAULT_ALPHA_THRESHOLD, true, true, false, false, args, true);
opaqueMeshPartsRendered += renderMeshes(batch, mode, false, DEFAULT_ALPHA_THRESHOLD, true, true, true, false, args, true);
// render translucent meshes afterwards
//DependencyManager::get<TextureCache>()->setPrimaryDrawBuffers(false, true, true);
@ -753,14 +753,14 @@ bool Model::renderCore(float alpha, RenderMode mode, RenderArgs* args) {
int translucentMeshPartsRendered = 0;
const float MOSTLY_OPAQUE_THRESHOLD = 0.75f;
translucentMeshPartsRendered += renderMeshes(batch, mode, true, MOSTLY_OPAQUE_THRESHOLD, false, false, false, false, args);
translucentMeshPartsRendered += renderMeshes(batch, mode, true, MOSTLY_OPAQUE_THRESHOLD, false, false, false, true, args);
translucentMeshPartsRendered += renderMeshes(batch, mode, true, MOSTLY_OPAQUE_THRESHOLD, false, false, true, false, args);
translucentMeshPartsRendered += renderMeshes(batch, mode, true, MOSTLY_OPAQUE_THRESHOLD, false, false, true, true, args);
translucentMeshPartsRendered += renderMeshes(batch, mode, true, MOSTLY_OPAQUE_THRESHOLD, false, true, false, false, args);
translucentMeshPartsRendered += renderMeshes(batch, mode, true, MOSTLY_OPAQUE_THRESHOLD, false, true, false, true, args);
translucentMeshPartsRendered += renderMeshes(batch, mode, true, MOSTLY_OPAQUE_THRESHOLD, false, true, true, false, args);
translucentMeshPartsRendered += renderMeshes(batch, mode, true, MOSTLY_OPAQUE_THRESHOLD, false, true, true, true, args);
translucentMeshPartsRendered += renderMeshes(batch, mode, true, MOSTLY_OPAQUE_THRESHOLD, false, false, false, false, args, true);
translucentMeshPartsRendered += renderMeshes(batch, mode, true, MOSTLY_OPAQUE_THRESHOLD, false, false, false, true, args, true);
translucentMeshPartsRendered += renderMeshes(batch, mode, true, MOSTLY_OPAQUE_THRESHOLD, false, false, true, false, args, true);
translucentMeshPartsRendered += renderMeshes(batch, mode, true, MOSTLY_OPAQUE_THRESHOLD, false, false, true, true, args, true);
translucentMeshPartsRendered += renderMeshes(batch, mode, true, MOSTLY_OPAQUE_THRESHOLD, false, true, false, false, args, true);
translucentMeshPartsRendered += renderMeshes(batch, mode, true, MOSTLY_OPAQUE_THRESHOLD, false, true, false, true, args, true);
translucentMeshPartsRendered += renderMeshes(batch, mode, true, MOSTLY_OPAQUE_THRESHOLD, false, true, true, false, args, true);
translucentMeshPartsRendered += renderMeshes(batch, mode, true, MOSTLY_OPAQUE_THRESHOLD, false, true, true, true, args, true);
GLBATCH(glDisable)(GL_ALPHA_TEST);
GLBATCH(glEnable)(GL_BLEND);
@ -777,14 +777,14 @@ bool Model::renderCore(float alpha, RenderMode mode, RenderArgs* args) {
if (mode == DEFAULT_RENDER_MODE || mode == DIFFUSE_RENDER_MODE) {
const float MOSTLY_TRANSPARENT_THRESHOLD = 0.0f;
translucentMeshPartsRendered += renderMeshes(batch, mode, true, MOSTLY_TRANSPARENT_THRESHOLD, false, false, false, false, args);
translucentMeshPartsRendered += renderMeshes(batch, mode, true, MOSTLY_TRANSPARENT_THRESHOLD, false, false, false, true, args);
translucentMeshPartsRendered += renderMeshes(batch, mode, true, MOSTLY_TRANSPARENT_THRESHOLD, false, false, true, false, args);
translucentMeshPartsRendered += renderMeshes(batch, mode, true, MOSTLY_TRANSPARENT_THRESHOLD, false, false, true, true, args);
translucentMeshPartsRendered += renderMeshes(batch, mode, true, MOSTLY_TRANSPARENT_THRESHOLD, false, true, false, false, args);
translucentMeshPartsRendered += renderMeshes(batch, mode, true, MOSTLY_TRANSPARENT_THRESHOLD, false, true, false, true, args);
translucentMeshPartsRendered += renderMeshes(batch, mode, true, MOSTLY_TRANSPARENT_THRESHOLD, false, true, true, false, args);
translucentMeshPartsRendered += renderMeshes(batch, mode, true, MOSTLY_TRANSPARENT_THRESHOLD, false, true, true, true, args);
translucentMeshPartsRendered += renderMeshes(batch, mode, true, MOSTLY_TRANSPARENT_THRESHOLD, false, false, false, false, args, true);
translucentMeshPartsRendered += renderMeshes(batch, mode, true, MOSTLY_TRANSPARENT_THRESHOLD, false, false, false, true, args, true);
translucentMeshPartsRendered += renderMeshes(batch, mode, true, MOSTLY_TRANSPARENT_THRESHOLD, false, false, true, false, args, true);
translucentMeshPartsRendered += renderMeshes(batch, mode, true, MOSTLY_TRANSPARENT_THRESHOLD, false, false, true, true, args, true);
translucentMeshPartsRendered += renderMeshes(batch, mode, true, MOSTLY_TRANSPARENT_THRESHOLD, false, true, false, false, args, true);
translucentMeshPartsRendered += renderMeshes(batch, mode, true, MOSTLY_TRANSPARENT_THRESHOLD, false, true, false, true, args, true);
translucentMeshPartsRendered += renderMeshes(batch, mode, true, MOSTLY_TRANSPARENT_THRESHOLD, false, true, true, false, args, true);
translucentMeshPartsRendered += renderMeshes(batch, mode, true, MOSTLY_TRANSPARENT_THRESHOLD, false, true, true, true, args, true);
}
GLBATCH(glDepthMask)(true);
@ -842,10 +842,67 @@ bool Model::renderCore(float alpha, RenderMode mode, RenderArgs* args) {
args->_translucentMeshPartsRendered = translucentMeshPartsRendered;
args->_opaqueMeshPartsRendered = opaqueMeshPartsRendered;
}
#ifdef WANT_DEBUG_MESHBOXES
renderDebugMeshBoxes();
#endif
return true;
}
void Model::renderDebugMeshBoxes() {
int colorNdx = 0;
foreach(AABox box, _calculatedMeshBoxes) {
if (_debugMeshBoxesID == GeometryCache::UNKNOWN_ID) {
_debugMeshBoxesID = DependencyManager::get<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 {
if (!isActive()) {
return Extents();
@ -1306,8 +1363,11 @@ void Model::updateJointState(int index) {
glm::mat4 parentTransform = glm::scale(_scale) * glm::translate(_offset) * geometry.offset;
state.computeTransform(parentTransform);
} else {
const JointState& parentState = _jointStates.at(parentIndex);
state.computeTransform(parentState.getTransform(), parentState.getTransformChanged());
// guard against out-of-bounds access to _jointStates
if (joint.parentIndex >= 0 && joint.parentIndex < _jointStates.size()) {
const JointState& parentState = _jointStates.at(parentIndex);
state.computeTransform(parentState.getTransform(), parentState.getTransformChanged());
}
}
}
@ -2299,7 +2359,8 @@ int Model::renderMeshesForModelsInScene(gpu::Batch& batch, RenderMode mode, bool
}
int Model::renderMeshes(gpu::Batch& batch, RenderMode mode, bool translucent, float alphaThreshold,
bool hasLightmap, bool hasTangents, bool hasSpecular, bool isSkinned, RenderArgs* args) {
bool hasLightmap, bool hasTangents, bool hasSpecular, bool isSkinned, RenderArgs* args,
bool forceRenderSomeMeshes) {
PROFILE_RANGE(__FUNCTION__);
int meshPartsRendered = 0;
@ -2319,8 +2380,10 @@ int Model::renderMeshes(gpu::Batch& batch, RenderMode mode, bool translucent, fl
Locations* locations;
SkinLocations* skinLocations;
pickPrograms(batch, mode, translucent, alphaThreshold, hasLightmap, hasTangents, hasSpecular, isSkinned, args, locations, skinLocations);
meshPartsRendered = renderMeshesFromList(list, batch, mode, translucent, alphaThreshold, args, locations, skinLocations);
pickPrograms(batch, mode, translucent, alphaThreshold, hasLightmap, hasTangents, hasSpecular, isSkinned,
args, locations, skinLocations);
meshPartsRendered = renderMeshesFromList(list, batch, mode, translucent, alphaThreshold,
args, locations, skinLocations, forceRenderSomeMeshes);
GLBATCH(glUseProgram)(0);
return meshPartsRendered;
@ -2328,7 +2391,7 @@ int Model::renderMeshes(gpu::Batch& batch, RenderMode mode, bool translucent, fl
int Model::renderMeshesFromList(QVector<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__);
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 (args) {
bool shouldRender = true;
bool forceRender = false;
args->_meshesConsidered++;
if (args->_viewFrustum) {
shouldRender = args->_viewFrustum->boxInFrustum(_calculatedMeshBoxes.at(i)) != ViewFrustum::OUTSIDE;
if (shouldRender) {
// NOTE: This is a hack to address the fact that for avatar meshes, the _calculatedMeshBoxes can be wrong
// for some meshes. Those meshes where the mesh's modelTransform is the identity matrix, and will have
// incorrectly calculated mesh boxes. In this case, we will ignore the box and assume it's visible.
if (forceRenderSomeMeshes && (geometry.meshes.at(i).modelTransform == glm::mat4())) {
forceRender = true;
}
shouldRender = forceRender || args->_viewFrustum->boxInFrustum(_calculatedMeshBoxes.at(i)) != ViewFrustum::OUTSIDE;
if (shouldRender && !forceRender) {
float distance = args->_viewFrustum->distanceToCamera(_calculatedMeshBoxes.at(i).calcCenter());
shouldRender = !_viewState ? false : _viewState->shouldRenderMesh(_calculatedMeshBoxes.at(i).getLargestDimension(),
distance);

View file

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