merge from upstream

This commit is contained in:
Seth Alves 2015-03-13 18:04:55 -07:00
commit 50798398ff
41 changed files with 1455 additions and 191 deletions

View file

@ -9,7 +9,7 @@
//
Script.load("progress.js");
Script.load("editEntities.js");
Script.load("edit.js");
Script.load("selectAudioDevice.js");
Script.load("controllers/hydra/hydraMove.js");
Script.load("headMove.js");

View file

@ -29,12 +29,15 @@ Script.include([
"libraries/entityCameraTool.js",
"libraries/gridTool.js",
"libraries/entityList.js",
"libraries/lightOverlayManager.js",
]);
var selectionDisplay = SelectionDisplay;
var selectionManager = SelectionManager;
var entityPropertyDialogBox = EntityPropertyDialogBox;
var lightOverlayManager = new LightOverlayManager();
var cameraManager = new CameraManager();
var grid = Grid();
@ -45,6 +48,7 @@ var entityListTool = EntityListTool();
selectionManager.addEventListener(function() {
selectionDisplay.updateHandles();
lightOverlayManager.updatePositions();
});
var windowDimensions = Controller.getViewportDimensions();
@ -70,13 +74,17 @@ var DEFAULT_DIMENSIONS = {
z: DEFAULT_DIMENSION
};
var DEFAULT_LIGHT_DIMENSIONS = Vec3.multiply(20, DEFAULT_DIMENSIONS);
var MENU_INSPECT_TOOL_ENABLED = "Inspect Tool";
var MENU_AUTO_FOCUS_ON_SELECT = "Auto Focus on Select";
var MENU_EASE_ON_FOCUS = "Ease Orientation on Focus";
var MENU_SHOW_LIGHTS_IN_EDIT_MODE = "Show Lights in Edit Mode";
var SETTING_INSPECT_TOOL_ENABLED = "inspectToolEnabled";
var SETTING_AUTO_FOCUS_ON_SELECT = "autoFocusOnSelect";
var SETTING_EASE_ON_FOCUS = "cameraEaseOnFocus";
var SETTING_SHOW_LIGHTS_IN_EDIT_MODE = "showLightsInEditMode";
var INSUFFICIENT_PERMISSIONS_ERROR_MSG = "You do not have the necessary permissions to edit on this domain."
@ -110,6 +118,10 @@ var importingSVOOverlay = Overlays.addOverlay("text", {
visible: false,
});
var MARKETPLACE_URL = "https://metaverse.highfidelity.io/marketplace";
var marketplaceWindow = new WebWindow('Marketplace', MARKETPLACE_URL, 900, 700, false);
marketplaceWindow.setVisible(false);
var toolBar = (function () {
var that = {},
toolBar,
@ -126,7 +138,7 @@ var toolBar = (function () {
// Hide active button for now - this may come back, so not deleting yet.
activeButton = toolBar.addTool({
imageURL: toolIconUrl + "models-tool.svg",
imageURL: toolIconUrl + "edit-status.svg",
subImage: { x: 0, y: Tool.IMAGE_WIDTH, width: Tool.IMAGE_WIDTH, height: Tool.IMAGE_HEIGHT },
width: toolWidth,
height: toolHeight,
@ -135,7 +147,7 @@ var toolBar = (function () {
}, true, false);
newModelButton = toolBar.addTool({
imageURL: toolIconUrl + "add-model-tool.svg",
imageURL: toolIconUrl + "upload.svg",
subImage: { x: 0, y: Tool.IMAGE_WIDTH, width: Tool.IMAGE_WIDTH, height: Tool.IMAGE_HEIGHT },
width: toolWidth,
height: toolHeight,
@ -144,7 +156,7 @@ var toolBar = (function () {
});
browseModelsButton = toolBar.addTool({
imageURL: toolIconUrl + "list-icon.svg",
imageURL: toolIconUrl + "marketplace.svg",
width: toolWidth,
height: toolHeight,
alpha: 0.9,
@ -186,6 +198,8 @@ var toolBar = (function () {
alpha: 0.9,
visible: false
});
that.setActive(false);
}
that.setActive = function(active) {
@ -214,6 +228,7 @@ var toolBar = (function () {
}
}
toolBar.selectTool(activeButton, isActive);
lightOverlayManager.setVisible(isActive && Menu.isOptionChecked(MENU_SHOW_LIGHTS_IN_EDIT_MODE));
};
// Sets visibility of tool buttons, excluding the power button
@ -311,7 +326,7 @@ var toolBar = (function () {
return true;
}
if (browseModelsButton === toolBar.clicked(clickedOverlay)) {
browseModelsButtonDown = true;
marketplaceWindow.setVisible(true);
return true;
}
@ -354,8 +369,8 @@ var toolBar = (function () {
if (position.x > 0 && position.y > 0 && position.z > 0) {
placingEntityID = Entities.addEntity({
type: "Light",
position: grid.snapToSurface(grid.snapToGrid(position, false, DEFAULT_DIMENSIONS), DEFAULT_DIMENSIONS),
dimensions: DEFAULT_DIMENSIONS,
position: grid.snapToSurface(grid.snapToGrid(position, false, DEFAULT_LIGHT_DIMENSIONS), DEFAULT_LIGHT_DIMENSIONS),
dimensions: DEFAULT_LIGHT_DIMENSIONS,
isSpotlight: false,
diffuseColor: { red: 255, green: 255, blue: 255 },
ambientColor: { red: 255, green: 255, blue: 255 },
@ -469,12 +484,31 @@ function rayPlaneIntersection(pickRay, point, normal) {
function findClickedEntity(event) {
var pickRay = Camera.computePickRay(event.x, event.y);
var foundIntersection = Entities.findRayIntersection(pickRay, true); // want precision picking
var entityResult = Entities.findRayIntersection(pickRay, true); // want precision picking
var lightResult = lightOverlayManager.findRayIntersection(pickRay);
lightResult.accurate = true;
if (!foundIntersection.accurate) {
var result;
if (!entityResult.intersects && !lightResult.intersects) {
return null;
} else if (entityResult.intersects && !lightResult.intersects) {
result = entityResult;
} else if (!entityResult.intersects && lightResult.intersects) {
result = lightResult;
} else {
if (entityResult.distance < lightResult.distance) {
result = entityResult;
} else {
result = lightResult;
}
}
if (!result.accurate) {
return null;
}
var foundEntity = foundIntersection.entityID;
var foundEntity = result.entityID;
if (!foundEntity.isKnownID) {
var identify = Entities.identifyEntity(foundEntity);
@ -730,6 +764,8 @@ function setupModelMenus() {
isCheckable: true, isChecked: Settings.getValue(SETTING_AUTO_FOCUS_ON_SELECT) == "true" });
Menu.addMenuItem({ menuName: "View", menuItemName: MENU_EASE_ON_FOCUS, afterItem: MENU_AUTO_FOCUS_ON_SELECT,
isCheckable: true, isChecked: Settings.getValue(SETTING_EASE_ON_FOCUS) == "true" });
Menu.addMenuItem({ menuName: "View", menuItemName: MENU_SHOW_LIGHTS_IN_EDIT_MODE, afterItem: MENU_EASE_ON_FOCUS,
isCheckable: true, isChecked: Settings.getValue(SETTING_SHOW_LIGHTS_IN_EDIT_MODE) == "true" });
Entities.setLightsArePickable(false);
}
@ -756,11 +792,13 @@ function cleanupModelMenus() {
Menu.removeMenuItem("View", MENU_INSPECT_TOOL_ENABLED);
Menu.removeMenuItem("View", MENU_AUTO_FOCUS_ON_SELECT);
Menu.removeMenuItem("View", MENU_EASE_ON_FOCUS);
Menu.removeMenuItem("View", MENU_SHOW_LIGHTS_IN_EDIT_MODE);
}
Script.scriptEnding.connect(function() {
Settings.setValue(SETTING_AUTO_FOCUS_ON_SELECT, Menu.isOptionChecked(MENU_AUTO_FOCUS_ON_SELECT));
Settings.setValue(SETTING_EASE_ON_FOCUS, Menu.isOptionChecked(MENU_EASE_ON_FOCUS));
Settings.setValue(SETTING_SHOW_LIGHTS_IN_EDIT_MODE, Menu.isOptionChecked(MENU_SHOW_LIGHTS_IN_EDIT_MODE));
progressDialog.cleanup();
toolBar.cleanup();
@ -837,6 +875,8 @@ function handeMenuEvent(menuItem) {
}
} else if (menuItem == "Entity List...") {
entityListTool.toggleVisible();
} else if (menuItem == MENU_SHOW_LIGHTS_IN_EDIT_MODE) {
lightOverlayManager.setVisible(isActive && Menu.isOptionChecked(MENU_SHOW_LIGHTS_IN_EDIT_MODE));
}
tooltip.show(false);
}
@ -861,6 +901,8 @@ function importSVO(importURL) {
if (isActive) {
selectionManager.setSelections(pastedEntityIDs);
}
Window.raiseMainWindow();
} else {
Window.alert("There was an error importing the entity file.");
}
@ -989,7 +1031,7 @@ PropertiesTool = function(opts) {
var that = {};
var url = Script.resolvePath('html/entityProperties.html');
var webView = new WebWindow('Entity Properties', url, 200, 280);
var webView = new WebWindow('Entity Properties', url, 200, 280, true);
var visible = false;

View file

@ -2,7 +2,7 @@ EntityListTool = function(opts) {
var that = {};
var url = Script.resolvePath('html/entityList.html');
var webView = new WebWindow('Entities', url, 200, 280);
var webView = new WebWindow('Entities', url, 200, 280, true);
var visible = false;

View file

@ -229,7 +229,7 @@ GridTool = function(opts) {
var listeners = [];
var url = Script.resolvePath('html/gridControls.html');
var webView = new WebWindow('Grid', url, 200, 280);
var webView = new WebWindow('Grid', url, 200, 280, true);
horizontalGrid.addListener(function(data) {
webView.eventBridge.emitScriptEvent(JSON.stringify(data));

View file

@ -0,0 +1,135 @@
var POINT_LIGHT_URL = "http://s3.amazonaws.com/hifi-public/images/tools/point-light.svg";
var SPOT_LIGHT_URL = "http://s3.amazonaws.com/hifi-public/images/tools/spot-light.svg";
LightOverlayManager = function() {
var self = this;
var visible = false;
// List of all created overlays
var allOverlays = [];
// List of overlays not currently being used
var unusedOverlays = [];
// Map from EntityItemID.id to overlay id
var entityOverlays = {};
// Map from EntityItemID.id to EntityItemID object
var entityIDs = {};
this.updatePositions = function(ids) {
for (var id in entityIDs) {
var entityID = entityIDs[id];
var properties = Entities.getEntityProperties(entityID);
Overlays.editOverlay(entityOverlays[entityID.id], {
position: properties.position
});
}
};
this.findRayIntersection = function(pickRay) {
var result = Overlays.findRayIntersection(pickRay);
var found = false;
if (result.intersects) {
for (var id in entityOverlays) {
if (result.overlayID == entityOverlays[id]) {
result.entityID = entityIDs[id];
found = true;
break;
}
}
if (!found) {
result.intersects = false;
}
}
return result;
};
this.setVisible = function(isVisible) {
if (visible != isVisible) {
visible = isVisible;
for (var id in entityOverlays) {
Overlays.editOverlay(entityOverlays[id], { visible: visible });
}
}
};
// Allocate or get an unused overlay
function getOverlay() {
if (unusedOverlays.length == 0) {
var overlay = Overlays.addOverlay("billboard", {
});
allOverlays.push(overlay);
} else {
var overlay = unusedOverlays.pop();
};
return overlay;
}
function releaseOverlay(overlay) {
unusedOverlays.push(overlay);
Overlays.editOverlay(overlay, { visible: false });
}
function addEntity(entityID) {
var properties = Entities.getEntityProperties(entityID);
if (properties.type == "Light" && !(entityID.id in entityOverlays)) {
var overlay = getOverlay();
entityOverlays[entityID.id] = overlay;
entityIDs[entityID.id] = entityID;
Overlays.editOverlay(overlay, {
position: properties.position,
url: properties.isSpotlight ? SPOT_LIGHT_URL : POINT_LIGHT_URL,
rotation: Quat.fromPitchYawRollDegrees(0, 0, 270),
visible: visible,
alpha: 0.9,
scale: 0.5,
color: { red: 255, green: 255, blue: 255 }
});
}
}
function deleteEntity(entityID) {
if (entityID.id in entityOverlays) {
releaseOverlay(entityOverlays[entityID.id]);
delete entityOverlays[entityID.id];
}
}
function changeEntityID(oldEntityID, newEntityID) {
entityOverlays[newEntityID.id] = entityOverlays[oldEntityID.id];
entityIDs[newEntityID.id] = newEntityID;
delete entityOverlays[oldEntityID.id];
delete entityIDs[oldEntityID.id];
}
function clearEntities() {
for (var id in entityOverlays) {
releaseOverlay(entityOverlays[id]);
}
entityOverlays = {};
entityIDs = {};
}
Entities.addingEntity.connect(addEntity);
Entities.changingEntityID.connect(changeEntityID);
Entities.deletingEntity.connect(deleteEntity);
Entities.clearingEntities.connect(clearEntities);
// Add existing entities
var ids = Entities.findEntities(MyAvatar.position, 100);
for (var i = 0; i < ids.length; i++) {
addEntity(ids[i]);
}
Script.scriptEnding.connect(function() {
for (var i = 0; i < allOverlays.length; i++) {
Overlays.deleteOverlay(allOverlays[i]);
}
});
};

View file

@ -94,7 +94,7 @@ Tool = function(properties, selectable, selected) { // selectable and selected a
}
selected = doSelect;
properties.subImage.y = (selected ? 2 : 1) * properties.subImage.height;
properties.subImage.y = (selected ? 0 : 1) * properties.subImage.height;
Overlays.editOverlay(this.overlay(), { subImage: properties.subImage });
}
this.toggle = function() {
@ -102,7 +102,7 @@ Tool = function(properties, selectable, selected) { // selectable and selected a
return;
}
selected = !selected;
properties.subImage.y = (selected ? 2 : 1) * properties.subImage.height;
properties.subImage.y = (selected ? 0 : 1) * properties.subImage.height;
Overlays.editOverlay(this.overlay(), { subImage: properties.subImage });
return selected;

View file

@ -573,10 +573,6 @@ void Application::cleanupBeforeQuit() {
_settingsThread.quit();
saveSettings();
_window->saveGeometry();
// TODO: now that this is in cleanupBeforeQuit do we really need it to stop and force
// an event loop to send the packet?
UserActivityLogger::getInstance().close();
// let the avatar mixer know we're out
MyAvatar::sendKillAvatar();
@ -877,6 +873,10 @@ void Application::controlledBroadcastToNodes(const QByteArray& packet, const Nod
}
}
void Application::importSVOFromURL(QUrl url) {
emit svoImportRequested(url.url());
}
bool Application::event(QEvent* event) {
// handle custom URL
@ -889,7 +889,7 @@ bool Application::event(QEvent* event) {
if (!url.isEmpty()) {
if (url.scheme() == HIFI_URL_SCHEME) {
DependencyManager::get<AddressManager>()->handleLookupString(fileEvent->url().toString());
} else if (url.url().toLower().endsWith(SVO_EXTENSION)) {
} else if (url.path().toLower().endsWith(SVO_EXTENSION)) {
emit svoImportRequested(url.url());
}
}
@ -1455,10 +1455,11 @@ void Application::dropEvent(QDropEvent *event) {
QString snapshotPath;
const QMimeData *mimeData = event->mimeData();
foreach (QUrl url, mimeData->urls()) {
if (url.url().toLower().endsWith(SNAPSHOT_EXTENSION)) {
auto lower = url.path().toLower();
if (lower.endsWith(SNAPSHOT_EXTENSION)) {
snapshotPath = url.toLocalFile();
break;
} else if (url.url().toLower().endsWith(SVO_EXTENSION)) {
} else if (lower.endsWith(SVO_EXTENSION)) {
emit svoImportRequested(url.url());
event->acceptProposedAction();
return;
@ -4093,5 +4094,8 @@ void Application::checkSkeleton() {
_myAvatar->setSkeletonModelURL(DEFAULT_BODY_MODEL_URL);
_myAvatar->sendIdentityPacket();
} else {
_myAvatar->updateLocalAABox();
_physicsEngine.setAvatarData(_myAvatar);
}
}

View file

@ -216,6 +216,8 @@ public:
float getFieldOfView() { return _fieldOfView.get(); }
void setFieldOfView(float fov) { _fieldOfView.set(fov); }
void importSVOFromURL(QUrl url);
NodeToOctreeSceneStats* getOcteeSceneStats() { return &_octreeServerSceneStats; }
void lockOctreeSceneStats() { _octreeSceneStatsLock.lockForRead(); }
void unlockOctreeSceneStats() { _octreeSceneStatsLock.unlock(); }

View file

@ -170,7 +170,7 @@ void GLCanvas::wheelEvent(QWheelEvent* event) {
void GLCanvas::dragEnterEvent(QDragEnterEvent* event) {
const QMimeData *mimeData = event->mimeData();
foreach (QUrl url, mimeData->urls()) {
auto lower = url.url().toLower();
auto lower = url.path().toLower();
if (lower.endsWith(SNAPSHOT_EXTENSION) || lower.endsWith(SVO_EXTENSION)) {
event->acceptProposedAction();
break;

View file

@ -1026,11 +1026,6 @@ float Avatar::getHeadHeight() const {
return DEFAULT_HEAD_HEIGHT;
}
float Avatar::getBoundingRadius() const {
// TODO: also use head model when computing the avatar's bounding radius
return _skeletonModel.getBoundingRadius();
}
float Avatar::getPelvisFloatingHeight() const {
return -_skeletonModel.getBindExtents().minimum.y;
}

View file

@ -133,9 +133,6 @@ public:
virtual void applyCollision(const glm::vec3& contactPoint, const glm::vec3& penetration) { }
/// \return bounding radius of avatar
virtual float getBoundingRadius() const;
Q_INVOKABLE void setSkeletonOffset(const glm::vec3& offset);
Q_INVOKABLE glm::vec3 getSkeletonOffset() { return _skeletonOffset; }
virtual glm::vec3 getSkeletonPosition() const;

View file

@ -954,6 +954,17 @@ glm::vec3 MyAvatar::getSkeletonPosition() const {
return Avatar::getPosition();
}
void MyAvatar::updateLocalAABox() {
const CapsuleShape& capsule = _skeletonModel.getBoundingShape();
float radius = capsule.getRadius();
float height = 2.0f * (capsule.getHalfHeight() + radius);
glm::vec3 offset = _skeletonModel.getBoundingShapeOffset();
glm::vec3 corner(-radius, -0.5f * height, -radius);
corner += offset;
glm::vec3 scale(2.0f * radius, height, 2.0f * radius);
_localAABox.setBox(corner, scale);
}
QString MyAvatar::getScriptedMotorFrame() const {
QString frame = "avatar";
if (_scriptedMotorFrame == SCRIPTED_MOTOR_CAMERA_FRAME) {

View file

@ -121,6 +121,7 @@ public:
virtual void setAttachmentData(const QVector<AttachmentData>& attachmentData);
virtual glm::vec3 getSkeletonPosition() const;
void updateLocalAABox();
void clearJointAnimationPriorities();

View file

@ -659,57 +659,56 @@ void SkeletonModel::buildShapes() {
void SkeletonModel::computeBoundingShape(const FBXGeometry& geometry) {
// compute default joint transforms
int numStates = _jointStates.size();
assert(numStates == _shapes.size());
QVector<glm::mat4> transforms;
transforms.fill(glm::mat4(), numStates);
// compute the default transforms
for (int i = 0; i < numStates; i++) {
JointState& state = _jointStates[i];
const FBXJoint& joint = state.getFBXJoint();
int parentIndex = joint.parentIndex;
if (parentIndex == -1) {
transforms[i] = _jointStates[i].getTransform();
continue;
}
glm::quat modifiedRotation = joint.preRotation * joint.rotation * joint.postRotation;
transforms[i] = transforms[parentIndex] * glm::translate(joint.translation)
* joint.preTransform * glm::mat4_cast(modifiedRotation) * joint.postTransform;
// TODO: Andrew to harvest transforms here to move shapes to correct positions so that
// bounding capsule calculations below are correct.
}
// compute bounding box that encloses all shapes
Extents totalExtents;
totalExtents.reset();
totalExtents.addPoint(glm::vec3(0.0f));
for (int i = 0; i < _shapes.size(); i++) {
for (int i = 0; i < numStates; i++) {
// compute the default transform of this joint
JointState& state = _jointStates[i];
const FBXJoint& joint = state.getFBXJoint();
int parentIndex = joint.parentIndex;
if (parentIndex == -1) {
transforms[i] = _jointStates[i].getTransform();
} else {
glm::quat modifiedRotation = joint.preRotation * joint.rotation * joint.postRotation;
transforms[i] = transforms[parentIndex] * glm::translate(joint.translation)
* joint.preTransform * glm::mat4_cast(modifiedRotation) * joint.postTransform;
}
Shape* shape = _shapes[i];
if (!shape) {
continue;
}
// Each joint with a shape contributes to the totalExtents: a box
// that contains the sphere centered at the end of the joint with radius of the bone.
// TODO: skip hand and arm shapes for bounding box calculation
Extents shapeExtents;
shapeExtents.reset();
glm::vec3 localPosition = shape->getTranslation();
glm::vec3 jointPosition = extractTranslation(transforms[i]);
int type = shape->getType();
if (type == CAPSULE_SHAPE) {
// add the two furthest surface points of the capsule
CapsuleShape* capsule = static_cast<CapsuleShape*>(shape);
glm::vec3 axis;
capsule->computeNormalizedAxis(axis);
float radius = capsule->getRadius();
float halfHeight = capsule->getHalfHeight();
axis = halfHeight * axis + glm::vec3(radius);
shapeExtents.addPoint(localPosition + axis);
shapeExtents.addPoint(localPosition - axis);
glm::vec3 axis(radius);
Extents shapeExtents;
shapeExtents.reset();
shapeExtents.addPoint(jointPosition + axis);
shapeExtents.addPoint(jointPosition - axis);
totalExtents.addExtents(shapeExtents);
} else if (type == SPHERE_SHAPE) {
float radius = shape->getBoundingRadius();
glm::vec3 axis = glm::vec3(radius);
shapeExtents.addPoint(localPosition + axis);
shapeExtents.addPoint(localPosition - axis);
glm::vec3 axis(radius);
Extents shapeExtents;
shapeExtents.reset();
shapeExtents.addPoint(jointPosition + axis);
shapeExtents.addPoint(jointPosition - axis);
totalExtents.addExtents(shapeExtents);
}
}

View file

@ -18,8 +18,10 @@
#include <QListWidget>
#include "Application.h"
#include "WindowScriptingInterface.h"
#include "ui/DataWebPage.h"
#include "MainWindow.h"
#include "WebWindowClass.h"
#include "WindowScriptingInterface.h"
ScriptEventBridge::ScriptEventBridge(QObject* parent) : QObject(parent) {
}
@ -32,27 +34,47 @@ void ScriptEventBridge::emitScriptEvent(const QString& data) {
emit scriptEventReceived(data);
}
WebWindowClass::WebWindowClass(const QString& title, const QString& url, int width, int height)
WebWindowClass::WebWindowClass(const QString& title, const QString& url, int width, int height, bool isToolWindow)
: QObject(NULL),
_eventBridge(new ScriptEventBridge(this)) {
_eventBridge(new ScriptEventBridge(this)),
_isToolWindow(isToolWindow) {
ToolWindow* toolWindow = Application::getInstance()->getToolWindow();
if (_isToolWindow) {
ToolWindow* toolWindow = Application::getInstance()->getToolWindow();
_dockWidget = new QDockWidget(title, toolWindow);
_dockWidget->setFeatures(QDockWidget::DockWidgetMovable);
auto dockWidget = new QDockWidget(title, toolWindow);
dockWidget->setFeatures(QDockWidget::DockWidgetMovable);
_webView = new QWebView(_dockWidget);
_webView = new QWebView(dockWidget);
addEventBridgeToWindowObject();
dockWidget->setWidget(_webView);
toolWindow->addDockWidget(Qt::RightDockWidgetArea, dockWidget);
_windowWidget = dockWidget;
} else {
_windowWidget = new QWidget(Application::getInstance()->getWindow(), Qt::Window);
_windowWidget->setMinimumSize(width, height);
auto layout = new QVBoxLayout(_windowWidget);
layout->setContentsMargins(0, 0, 0, 0);
_windowWidget->setLayout(layout);
_webView = new QWebView(_windowWidget);
layout->addWidget(_webView);
addEventBridgeToWindowObject();
}
_webView->setPage(new DataWebPage());
_webView->setUrl(url);
addEventBridgeToWindowObject();
_dockWidget->setWidget(_webView);
toolWindow->addDockWidget(Qt::RightDockWidgetArea, _dockWidget);
connect(this, &WebWindowClass::destroyed, _windowWidget, &QWidget::deleteLater);
connect(_webView->page()->mainFrame(), &QWebFrame::javaScriptWindowObjectCleared,
this, &WebWindowClass::addEventBridgeToWindowObject);
connect(this, &WebWindowClass::destroyed, _dockWidget, &QWidget::deleteLater);
}
WebWindowClass::~WebWindowClass() {
@ -64,10 +86,14 @@ void WebWindowClass::addEventBridgeToWindowObject() {
void WebWindowClass::setVisible(bool visible) {
if (visible) {
QMetaObject::invokeMethod(
Application::getInstance()->getToolWindow(), "setVisible", Qt::BlockingQueuedConnection, Q_ARG(bool, visible));
if (_isToolWindow) {
QMetaObject::invokeMethod(
Application::getInstance()->getToolWindow(), "setVisible", Qt::BlockingQueuedConnection, Q_ARG(bool, visible));
} else {
QMetaObject::invokeMethod(_windowWidget, "raise", Qt::BlockingQueuedConnection);
}
}
QMetaObject::invokeMethod(_dockWidget, "setVisible", Qt::BlockingQueuedConnection, Q_ARG(bool, visible));
QMetaObject::invokeMethod(_windowWidget, "setVisible", Qt::BlockingQueuedConnection, Q_ARG(bool, visible));
}
QScriptValue WebWindowClass::constructor(QScriptContext* context, QScriptEngine* engine) {
@ -78,7 +104,8 @@ QScriptValue WebWindowClass::constructor(QScriptContext* context, QScriptEngine*
Q_ARG(const QString&, file),
Q_ARG(QString, context->argument(1).toString()),
Q_ARG(int, context->argument(2).toInteger()),
Q_ARG(int, context->argument(3).toInteger()));
Q_ARG(int, context->argument(3).toInteger()),
Q_ARG(bool, context->argument(4).toBool()));
connect(engine, &QScriptEngine::destroyed, retVal, &WebWindowClass::deleteLater);

View file

@ -35,7 +35,7 @@ class WebWindowClass : public QObject {
Q_OBJECT
Q_PROPERTY(QObject* eventBridge READ getEventBridge)
public:
WebWindowClass(const QString& title, const QString& url, int width, int height);
WebWindowClass(const QString& title, const QString& url, int width, int height, bool isToolWindow = false);
~WebWindowClass();
static QScriptValue constructor(QScriptContext* context, QScriptEngine* engine);
@ -46,9 +46,10 @@ public slots:
void addEventBridgeToWindowObject();
private:
QDockWidget* _dockWidget;
QWidget* _windowWidget;
QWebView* _webView;
ScriptEventBridge* _eventBridge;
bool _isToolWindow;
};
#endif

View file

@ -35,8 +35,8 @@ WindowScriptingInterface::WindowScriptingInterface() :
connect(Application::getInstance(), &Application::svoImportRequested, this, &WindowScriptingInterface::svoImportRequested);
}
WebWindowClass* WindowScriptingInterface::doCreateWebWindow(const QString& title, const QString& url, int width, int height) {
return new WebWindowClass(title, url, width, height);
WebWindowClass* WindowScriptingInterface::doCreateWebWindow(const QString& title, const QString& url, int width, int height, bool isToolWindow) {
return new WebWindowClass(title, url, width, height, isToolWindow);
}
QScriptValue WindowScriptingInterface::hasFocus() {
@ -44,8 +44,13 @@ QScriptValue WindowScriptingInterface::hasFocus() {
}
void WindowScriptingInterface::setFocus() {
Application::getInstance()->getWindow()->activateWindow();
Application::getInstance()->getWindow()->setFocus();
auto window = Application::getInstance()->getWindow();
window->activateWindow();
window->setFocus();
}
void WindowScriptingInterface::raiseMainWindow() {
Application::getInstance()->getWindow()->raise();
}
void WindowScriptingInterface::setCursorVisible(bool visible) {

View file

@ -43,6 +43,7 @@ public slots:
void setCursorVisible(bool visible);
QScriptValue hasFocus();
void setFocus();
void raiseMainWindow();
QScriptValue alert(const QString& message = "");
QScriptValue confirm(const QString& message = "");
QScriptValue form(const QString& title, QScriptValue array);
@ -83,7 +84,7 @@ private slots:
void nonBlockingFormAccepted() { _nonBlockingFormActive = false; _formResult = QDialog::Accepted; emit nonBlockingFormClosed(); }
void nonBlockingFormRejected() { _nonBlockingFormActive = false; _formResult = QDialog::Rejected; emit nonBlockingFormClosed(); }
WebWindowClass* doCreateWebWindow(const QString& title, const QString& url, int width, int height);
WebWindowClass* doCreateWebWindow(const QString& title, const QString& url, int width, int height, bool isToolWindow);
private:
QString jsRegExp2QtRegExp(QString string);

View file

@ -11,6 +11,7 @@
#include <qnetworkrequest.h>
#include "Application.h"
#include <AddressManager.h>
#include <OAuthNetworkAccessManager.h>
@ -21,7 +22,7 @@ DataWebPage::DataWebPage(QObject* parent) :
{
// use an OAuthNetworkAccessManager instead of regular QNetworkAccessManager so our requests are authed
setNetworkAccessManager(OAuthNetworkAccessManager::getInstance());
// give the page an empty stylesheet
settings()->setUserStyleSheetUrl(QUrl());
}
@ -31,8 +32,12 @@ void DataWebPage::javaScriptConsoleMessage(const QString& message, int lineNumbe
}
bool DataWebPage::acceptNavigationRequest(QWebFrame* frame, const QNetworkRequest& request, QWebPage::NavigationType type) {
if (!request.url().toString().startsWith(HIFI_URL_SCHEME)) {
if (request.url().path().toLower().endsWith(SVO_EXTENSION)) {
Application::getInstance()->importSVOFromURL(request.url());
return false;
}
return true;
} else {
// this is a hifi URL - have the AddressManager handle it
@ -40,4 +45,8 @@ bool DataWebPage::acceptNavigationRequest(QWebFrame* frame, const QNetworkReques
Qt::AutoConnection, Q_ARG(const QString&, request.url().toString()));
return false;
}
}
}
QString DataWebPage::userAgentForUrl(const QUrl & url) const {
return INTERFACE_USER_AGENT;
}

View file

@ -14,12 +14,15 @@
#include <qwebpage.h>
const QString INTERFACE_USER_AGENT = "HighFidelityInterface/1.0";
class DataWebPage : public QWebPage {
public:
DataWebPage(QObject* parent = 0);
protected:
void javaScriptConsoleMessage(const QString & message, int lineNumber, const QString & sourceID);
bool acceptNavigationRequest(QWebFrame* frame, const QNetworkRequest& request, QWebPage::NavigationType type);
virtual QString userAgentForUrl(const QUrl & url) const;
};
#endif // hifi_DataWebPage_h
#endif // hifi_DataWebPage_h

View file

@ -58,6 +58,7 @@ void BillboardOverlay::render(RenderArgs* args) {
// rotate about vertical to face the camera
rotation = Application::getInstance()->getCamera()->getRotation();
rotation *= glm::angleAxis(glm::pi<float>(), glm::vec3(0.0f, 1.0f, 0.0f));
rotation *= getRotation();
} else {
rotation = getRotation();
}

View file

@ -32,6 +32,9 @@ quint64 DEFAULT_FILTERED_LOG_EXPIRY = 2 * USECS_PER_SECOND;
using namespace std;
const glm::vec3 DEFAULT_LOCAL_AABOX_CORNER(-0.5f);
const glm::vec3 DEFAULT_LOCAL_AABOX_SCALE(1.0f);
AvatarData::AvatarData() :
_sessionUUID(),
_position(0.0f),
@ -55,9 +58,9 @@ AvatarData::AvatarData() :
_errorLogExpiry(0),
_owningAvatarMixer(),
_lastUpdateTimer(),
_velocity(0.0f)
_velocity(0.0f),
_localAABox(DEFAULT_LOCAL_AABOX_CORNER, DEFAULT_LOCAL_AABOX_SCALE)
{
}
AvatarData::~AvatarData() {

View file

@ -50,6 +50,7 @@ typedef unsigned long long quint64;
#include <Node.h>
#include "AABox.h"
#include "HandData.h"
#include "HeadData.h"
#include "Player.h"
@ -296,8 +297,7 @@ public:
QElapsedTimer& getLastUpdateTimer() { return _lastUpdateTimer; }
virtual float getBoundingRadius() const { return 1.0f; }
const AABox& getLocalAABox() const { return _localAABox; }
const Referential* getReferential() const { return _referential; }
void togglePhysicsEnabled() { _enablePhysics = !_enablePhysics; }
@ -403,6 +403,8 @@ protected:
glm::vec3 _velocity;
AABox _localAABox;
private:
// privatize the copy constructor and assignment operator so they cannot be called
AvatarData(const AvatarData&);

View file

@ -35,6 +35,25 @@ bool EntityScriptingInterface::canAdjustLocks() {
}
void EntityScriptingInterface::setEntityTree(EntityTree* modelTree) {
if (_entityTree) {
disconnect(_entityTree, &EntityTree::addingEntity, this, &EntityScriptingInterface::addingEntity);
disconnect(_entityTree, &EntityTree::deletingEntity, this, &EntityScriptingInterface::deletingEntity);
disconnect(_entityTree, &EntityTree::changingEntityID, this, &EntityScriptingInterface::changingEntityID);
disconnect(_entityTree, &EntityTree::clearingEntities, this, &EntityScriptingInterface::clearingEntities);
}
_entityTree = modelTree;
if (_entityTree) {
connect(_entityTree, &EntityTree::addingEntity, this, &EntityScriptingInterface::addingEntity);
connect(_entityTree, &EntityTree::deletingEntity, this, &EntityScriptingInterface::deletingEntity);
connect(_entityTree, &EntityTree::changingEntityID, this, &EntityScriptingInterface::changingEntityID);
connect(_entityTree, &EntityTree::clearingEntities, this, &EntityScriptingInterface::clearingEntities);
}
}
EntityItemID EntityScriptingInterface::addEntity(const EntityItemProperties& properties) {
// The application will keep track of creatorTokenID

View file

@ -58,7 +58,7 @@ public:
virtual NodeType_t getServerNodeType() const { return NodeType::EntityServer; }
virtual OctreeEditPacketSender* createPacketSender() { return new EntityEditPacketSender(); }
void setEntityTree(EntityTree* modelTree) { _entityTree = modelTree; }
void setEntityTree(EntityTree* modelTree);
EntityTree* getEntityTree(EntityTree*) { return _entityTree; }
public slots:
@ -129,6 +129,11 @@ signals:
void enterEntity(const EntityItemID& entityItemID);
void leaveEntity(const EntityItemID& entityItemID);
void deletingEntity(const EntityItemID& entityID);
void addingEntity(const EntityItemID& entityID);
void changingEntityID(const EntityItemID& oldEntityID, const EntityItemID& newEntityID);
void clearingEntities();
private:
void queueEntityMessage(PacketType packetType, EntityItemID entityID, const EntityItemProperties& properties);

View file

@ -37,6 +37,8 @@ EntityTreeElement* EntityTree::createNewElement(unsigned char * octalCode) {
}
void EntityTree::eraseAllOctreeElements(bool createNewRoot) {
emit clearingEntities();
// this would be a good place to clean up our entities...
if (_simulation) {
_simulation->lock();

View file

@ -163,6 +163,7 @@ signals:
void addingEntity(const EntityItemID& entityID);
void entityScriptChanging(const EntityItemID& entityItemID);
void changingEntityID(const EntityItemID& oldEntityID, const EntityItemID& newEntityID);
void clearingEntities();
private:

View file

@ -19,7 +19,6 @@
#include <QtCore/QUrlQuery>
#include <QtNetwork/QHttpMultiPart>
#include <QtNetwork/QNetworkRequest>
#include <QEventLoop>
#include <qthread.h>
#include <SettingHandle.h>
@ -300,8 +299,6 @@ void AccountManager::processReply() {
passErrorToCallback(requestReply);
}
delete requestReply;
emit replyFinished();
}
void AccountManager::passSuccessToCallback(QNetworkReply* requestReply) {
@ -342,15 +339,6 @@ void AccountManager::passErrorToCallback(QNetworkReply* requestReply) {
}
}
void AccountManager::waitForAllPendingReplies() {
while (_pendingCallbackMap.size() > 0) {
QEventLoop loop;
QObject::connect(this, &AccountManager::replyFinished, &loop, &QEventLoop::quit);
loop.exec();
}
}
void AccountManager::persistAccountToSettings() {
if (_shouldPersistToSettingsFile) {
// store this access token into the local settings

View file

@ -72,8 +72,6 @@ public:
void requestProfile();
DataServerAccountInfo& getAccountInfo() { return _accountInfo; }
void waitForAllPendingReplies();
public slots:
void requestAccessToken(const QString& login, const QString& password);
@ -95,8 +93,6 @@ signals:
void loginFailed();
void logoutComplete();
void balanceChanged(qint64 newBalance);
void replyFinished();
private slots:
void processReply();
void handleKeypairGenerationError();

View file

@ -14,6 +14,7 @@
#include <QThreadStorage>
#include "AccountManager.h"
#include "LimitedNodeList.h"
#include "SharedUtil.h"
#include "OAuthNetworkAccessManager.h"
@ -32,7 +33,7 @@ QNetworkReply* OAuthNetworkAccessManager::createRequest(QNetworkAccessManager::O
QIODevice* outgoingData) {
AccountManager& accountManager = AccountManager::getInstance();
if (accountManager.hasValidAccessToken()) {
if (accountManager.hasValidAccessToken() && req.url().host() == DEFAULT_NODE_AUTH_URL.host()) {
QNetworkRequest authenticatedRequest(req);
authenticatedRequest.setHeader(QNetworkRequest::UserAgentHeader, HIGH_FIDELITY_USER_AGENT);
authenticatedRequest.setRawHeader(ACCESS_TOKEN_AUTHORIZATION_HEADER,

View file

@ -224,8 +224,12 @@ void Resource::refresh() {
}
if (_reply) {
ResourceCache::requestCompleted(this);
delete _reply;
_reply->disconnect(this);
_replyTimer->disconnect(this);
_reply->deleteLater();
_reply = nullptr;
_replyTimer->deleteLater();
_replyTimer = nullptr;
}
init();
_request.setAttribute(QNetworkRequest::CacheLoadControlAttribute, QNetworkRequest::AlwaysNetwork);
@ -297,9 +301,9 @@ void Resource::handleDownloadProgress(qint64 bytesReceived, qint64 bytesTotal) {
return;
}
_reply->disconnect(this);
_replyTimer->disconnect(this);
QNetworkReply* reply = _reply;
_reply = nullptr;
_replyTimer->disconnect(this);
_replyTimer->deleteLater();
_replyTimer = nullptr;
ResourceCache::requestCompleted(this);
@ -329,6 +333,10 @@ void Resource::maybeRefresh() {
// We don't need to update, return
return;
}
} else if (!variant.isValid() || !variant.canConvert<QDateTime>() ||
!variant.value<QDateTime>().isValid() || variant.value<QDateTime>().isNull()) {
qDebug() << "Cannot determine when" << _url.fileName() << "was modified last, cached version might be outdated";
return;
}
qDebug() << "Loaded" << _url.fileName() << "from the disk cache but the network version is newer, refreshing.";
refresh();
@ -352,10 +360,19 @@ void Resource::makeRequest() {
} else {
if (Q_LIKELY(NetworkAccessManager::getInstance().cache())) {
QNetworkCacheMetaData metaData = NetworkAccessManager::getInstance().cache()->metaData(_url);
bool needUpdate = false;
if (metaData.expirationDate().isNull() || metaData.expirationDate() <= QDateTime::currentDateTime()) {
// If the expiration date is NULL or in the past,
// put one far enough away that it won't be an issue.
metaData.setExpirationDate(QDateTime::currentDateTime().addYears(100));
needUpdate = true;
}
if (metaData.lastModified().isNull()) {
// If the lastModified date is NULL, set it to now.
metaData.setLastModified(QDateTime::currentDateTime());
needUpdate = true;
}
if (needUpdate) {
NetworkAccessManager::getInstance().cache()->updateMetaData(metaData);
}
}
@ -370,9 +387,9 @@ void Resource::makeRequest() {
void Resource::handleReplyError(QNetworkReply::NetworkError error, QDebug debug) {
_reply->disconnect(this);
_replyTimer->disconnect(this);
_reply->deleteLater();
_reply = nullptr;
_replyTimer->disconnect(this);
_replyTimer->deleteLater();
_replyTimer = nullptr;
ResourceCache::requestCompleted(this);

View file

@ -61,7 +61,7 @@ void UserActivityLogger::logAction(QString action, QJsonObject details, JSONCall
params.errorCallbackReceiver = this;
params.errorCallbackMethod = "requestError";
}
accountManager.authenticatedRequest(USER_ACTIVITY_URL,
QNetworkAccessManager::PostOperation,
params,
@ -86,13 +86,6 @@ void UserActivityLogger::launch(QString applicationVersion) {
logAction(ACTION_NAME, actionDetails);
}
void UserActivityLogger::close() {
const QString ACTION_NAME = "close";
logAction(ACTION_NAME, QJsonObject());
AccountManager::getInstance().waitForAllPendingReplies();
}
void UserActivityLogger::changedDisplayName(QString displayName) {
const QString ACTION_NAME = "changed_display_name";
QJsonObject actionDetails;

View file

@ -30,7 +30,7 @@ public slots:
void logAction(QString action, QJsonObject details = QJsonObject(), JSONCallbackParameters params = JSONCallbackParameters());
void launch(QString applicationVersion);
void close();
void changedDisplayName(QString displayName);
void changedModel(QString typeOfModel, QString modelURL);
void changedDomain(QString domainURL);

View file

@ -0,0 +1,706 @@
/*
Bullet Continuous Collision Detection and Physics Library
Copyright (c) 2003-2008 Erwin Coumans http://bulletphysics.com
This software is provided 'as-is', without any express or implied warranty.
In no event will the authors be held liable for any damages arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it freely,
subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software.
If you use this software in a product, an acknowledgment in the product documentation would be appreciated
but is not required.
2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
*/
//#include <stdio.h>
#include "BulletCollision/CollisionDispatch/btGhostObject.h"
#include "CharacterController.h"
// static helper method
static btVector3 getNormalizedVector(const btVector3& v) {
// NOTE: check the length first, then normalize
// --> avoids assert when trying to normalize zero-length vectors
btScalar vLength = v.length();
if (vLength < FLT_EPSILON) {
return btVector3(0.0f, 0.0f, 0.0f);
}
btVector3 n = v;
n /= vLength;
return n;
}
///@todo Interact with dynamic objects,
///Ride kinematicly animated platforms properly
///More realistic (or maybe just a config option) falling
/// -> Should integrate falling velocity manually and use that in stepDown()
///Support jumping
///Support ducking
/* This callback is unused, but we're keeping it around just in case we figure out how to use it.
class btKinematicClosestNotMeRayResultCallback : public btCollisionWorld::ClosestRayResultCallback
{
public:
btKinematicClosestNotMeRayResultCallback(btCollisionObject* me) : btCollisionWorld::ClosestRayResultCallback(btVector3(0.0, 0.0, 0.0), btVector3(0.0, 0.0, 0.0))
{
m_me = me;
}
virtual btScalar addSingleResult(btCollisionWorld::LocalRayResult& rayResult, bool normalInWorldSpace)
{
if(rayResult.m_collisionObject == m_me)
return 1.0;
return ClosestRayResultCallback::addSingleResult(rayResult, normalInWorldSpace);
}
protected:
btCollisionObject* m_me;
};
*/
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))
, m_me(me)
, m_up(up)
, m_minSlopeDot(minSlopeDot)
{
}
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;
}
btScalar dotUp = m_up.dot(hitNormalWorld);
if (dotUp < m_minSlopeDot) {
return btScalar(1.0);
}
return ClosestConvexResultCallback::addSingleResult(convexResult, normalInWorldSpace);
}
protected:
btCollisionObject* m_me;
const btVector3 m_up;
btScalar m_minSlopeDot;
};
/*
* 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)
{
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)
{
btScalar magnitude = direction.dot(normal);
return normal * magnitude;
}
/*
* Returns the portion of 'direction' that is perpindicular to '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;
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_wasOnGround = false;
m_wasJumping = false;
m_interpolateUp = true;
setMaxSlope(btRadians(45.0));
m_currentStepOffset = 0;
// internal state data members
full_drop = false;
bounce_fix = false;
}
CharacterController::~CharacterController() {
}
btPairCachingGhostObject* CharacterController::getGhostObject() {
return m_ghostObject;
}
bool CharacterController::recoverFromPenetration(btCollisionWorld* collisionWorld) {
// Here we must refresh the overlapping paircache as the penetrating movement itself or the
// previous recovery iteration might have used setWorldTransform and pushed us into an object
// that is not in the previous cache contents from the last timestep, as will happen if we
// are pushed into a new AABB overlap. Unhandled this means the next convex sweep gets stuck.
//
// Do this by calling the broadphase's setAabb with the moved AABB, this will update the broadphase
// paircache and the ghostobject's internal paircache at the same time. /BW
btVector3 minAabb, maxAabb;
m_convexShape->getAabb(m_ghostObject->getWorldTransform(), minAabb, maxAabb);
collisionWorld->getBroadphase()->setAabb(m_ghostObject->getBroadphaseHandle(),
minAabb,
maxAabb,
collisionWorld->getDispatcher());
bool penetration = false;
collisionWorld->getDispatcher()->dispatchAllCollisionPairs(m_ghostObject->getOverlappingPairCache(), collisionWorld->getDispatchInfo(), collisionWorld->getDispatcher());
m_currentPosition = m_ghostObject->getWorldTransform().getOrigin();
btScalar maxPen = btScalar(0.0);
for (int i = 0; i < m_ghostObject->getOverlappingPairCache()->getNumOverlappingPairs(); i++) {
m_manifoldArray.resize(0);
btBroadphasePair* collisionPair = &m_ghostObject->getOverlappingPairCache()->getOverlappingPairArray()[i];
btCollisionObject* obj0 = static_cast<btCollisionObject*>(collisionPair->m_pProxy0->m_clientObject);
btCollisionObject* obj1 = static_cast<btCollisionObject*>(collisionPair->m_pProxy1->m_clientObject);
if ((obj0 && !obj0->hasContactResponse()) || (obj1 && !obj1->hasContactResponse())) {
continue;
}
if (collisionPair->m_algorithm) {
collisionPair->m_algorithm->getAllContactManifolds(m_manifoldArray);
}
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++) {
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;//??
}
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
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));
end.setOrigin(m_targetPosition);
btKinematicClosestNotMeConvexResultCallback callback(m_ghostObject, -getUpAxisDirections()[m_upAxis], 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);
}
if (callback.hasHit()) {
// 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;
if (m_interpolateUp == true) {
m_currentPosition.setInterpolate3 (m_currentPosition, m_targetPosition, callback.m_closestHitFraction);
} else {
m_currentPosition = m_targetPosition;
}
}
m_verticalVelocity = 0.0;
m_verticalOffset = 0.0;
} else {
m_currentStepOffset = m_stepHeight;
m_currentPosition = m_targetPosition;
}
}
void CharacterController::updateTargetPositionBasedOnCollision(const btVector3& hitNormal, btScalar tangentMag, btScalar normalMag) {
btVector3 movementDirection = m_targetPosition - m_currentPosition;
btScalar movementLength = movementDirection.length();
if (movementLength>SIMD_EPSILON) {
movementDirection.normalize();
btVector3 reflectDir = computeReflectionDirection(movementDirection, hitNormal);
reflectDir.normalize();
btVector3 parallelDir, perpindicularDir;
parallelDir = parallelComponent(reflectDir, hitNormal);
perpindicularDir = perpindicularComponent(reflectDir, hitNormal);
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]);
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]);
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;
start.setIdentity();
end.setIdentity();
btScalar fraction = 1.0;
btScalar distance2 = (m_currentPosition-m_targetPosition).length2();
//printf("distance2=%f\n", distance2);
if (m_touchingContact) {
if (m_normalizedDirection.dot(m_touchingNormal) > btScalar(0.0)) {
//interferes with step movement
//updateTargetPositionBasedOnCollision(m_touchingNormal);
}
}
int maxIter = 10;
while (fraction > btScalar(0.01) && maxIter-- > 0) {
start.setOrigin(m_currentPosition);
end.setOrigin(m_targetPosition);
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;
if (callback.hasHit()) {
// we moved only a fraction
//btScalar hitDistance;
//hitDistance = (callback.m_hitPointWorld - m_currentPosition).length();
//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;
}
} else {
// we moved whole way
m_currentPosition = m_targetPosition;
}
//if (callback.m_closestHitFraction == 0.f) {
// break;
//}
}
}
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);*/
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;
}
btVector3 step_drop = getUpAxisDirections()[m_upAxis] * (m_currentStepOffset + downVelocity);
m_targetPosition -= step_drop;
btKinematicClosestNotMeConvexResultCallback callback(m_ghostObject, getUpAxisDirections()[m_upAxis], m_maxSlopeCosine);
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;
while (1) {
start.setIdentity();
end.setIdentity();
end_double.setIdentity();
start.setOrigin(m_currentPosition);
end.setOrigin(m_targetPosition);
//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);
}
} 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);
}
}
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);
m_velocityTimeInterval += timeInterval;
}
void CharacterController::reset( btCollisionWorld* collisionWorld ) {
m_verticalVelocity = 0.0;
m_verticalOffset = 0.0;
m_wasOnGround = false;
m_wasJumping = false;
m_walkDirection.setValue(0,0,0);
m_velocityTimeInterval = 0.0;
//clear pair cache
btHashedOverlappingPairCache *cache = m_ghostObject->getOverlappingPairCache();
while (cache->getOverlappingPairArray().size() > 0) {
cache->removeOverlappingPair(cache->getOverlappingPairArray()[0].m_pProxy0, cache->getOverlappingPairArray()[0].m_pProxy1, collisionWorld->getDispatcher());
}
}
void CharacterController::warp(const btVector3& origin) {
btTransform xform;
xform.setIdentity();
xform.setOrigin(origin);
m_ghostObject->setWorldTransform(xform);
}
void CharacterController::preStep( btCollisionWorld* collisionWorld) {
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");
return; // no motion
}
m_wasOnGround = onGround();
// Update fall velocity.
m_verticalVelocity -= m_gravity * dt;
if (m_verticalVelocity > 0.0 && m_verticalVelocity > m_jumpSpeed) {
m_verticalVelocity = m_jumpSpeed;
}
if (m_verticalVelocity < 0.0 && btFabs(m_verticalVelocity) > btFabs(m_fallSpeed)) {
m_verticalVelocity = -btFabs(m_fallSpeed);
}
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);
stepUp (collisionWorld);
if (m_useWalkDirection) {
stepForwardAndStrafe(collisionWorld, m_walkDirection);
} else {
//printf(" time: %f", m_velocityTimeInterval);
// still have some time left for moving!
btScalar dtMoving =
(dt < m_velocityTimeInterval) ? dt : m_velocityTimeInterval;
m_velocityTimeInterval -= dt;
// how far will we move while we are moving?
btVector3 move = m_walkDirection * dtMoving;
//printf(" dtMoving: %f", dtMoving);
// okay, step
stepForwardAndStrafe(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::setJumpSpeed(btScalar jumpSpeed) {
m_jumpSpeed = jumpSpeed;
}
void CharacterController::setMaxJumpHeight(btScalar maxJumpHeight) {
m_maxJumpHeight = maxJumpHeight;
}
bool CharacterController::canJump() const {
return onGround();
}
void CharacterController::jump() {
if (!canJump()) {
return;
}
m_verticalVelocity = m_jumpSpeed;
m_wasJumping = true;
#if 0
currently no jumping.
btTransform xform;
m_rigidBody->getMotionState()->getWorldTransform(xform);
btVector3 up = xform.getBasis()[1];
up.normalize();
btScalar magnitude = (btScalar(1.0)/m_rigidBody->getInvMass()) * btScalar(8.0);
m_rigidBody->applyCentralImpulse (up * magnitude);
#endif
}
void CharacterController::setGravity(btScalar gravity) {
m_gravity = gravity;
}
btScalar CharacterController::getGravity() const {
return m_gravity;
}
void CharacterController::setMaxSlope(btScalar slopeRadians) {
m_maxSlopeRadians = slopeRadians;
m_maxSlopeCosine = btCos(slopeRadians);
}
btScalar CharacterController::getMaxSlope() const {
return m_maxSlopeRadians;
}
bool CharacterController::onGround() const {
return m_verticalVelocity == 0.0 && m_verticalOffset == 0.0;
}
btVector3* CharacterController::getUpAxisDirections() {
static btVector3 sUpAxisDirection[3] = { btVector3(1.0f, 0.0f, 0.0f), btVector3(0.0f, 1.0f, 0.0f), btVector3(0.0f, 0.0f, 1.0f) };
return sUpAxisDirection;
}
void CharacterController::debugDraw(btIDebugDraw* debugDrawer) {
}
void CharacterController::setUpInterpolate(bool value) {
m_interpolateUp = value;
}

View file

@ -0,0 +1,172 @@
/*
Bullet Continuous Collision Detection and Physics Library
Copyright (c) 2003-2008 Erwin Coumans http://bulletphysics.com
This software is provided 'as-is', without any express or implied warranty.
In no event will the authors be held liable for any damages arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it freely,
subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software.
If you use this software in a product, an acknowledgment in the product documentation would be appreciated but
is not required.
2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
*/
#ifndef hifi_CharacterController_h
#define hifi_CharacterController_h
#include <btBulletDynamicsCommon.h>
#include <BulletDynamics/Character/btCharacterControllerInterface.h>
#include <BulletCollision/BroadphaseCollision/btCollisionAlgorithm.h>
class btConvexShape;
class btCollisionWorld;
class btCollisionDispatcher;
class btPairCachingGhostObject;
///CharacterController is a custom version of btKinematicCharacterController
///btKinematicCharacterController is an object that supports a sliding motion in a world.
///It uses a ghost object and convex sweep test to test for upcoming collisions. This is combined with discrete collision detection to recover from penetrations.
///Interaction between btKinematicCharacterController and dynamic rigid bodies needs to be explicity implemented by the user.
ATTRIBUTE_ALIGNED16(class) CharacterController : public btCharacterControllerInterface
{
protected:
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_jumpSpeed;
btScalar m_maxJumpHeight;
btScalar m_maxSlopeRadians; // Slope angle that is set (used for returning the exact value)
btScalar m_maxSlopeCosine; // Cosine equivalent of m_maxSlopeRadians (calculated once when set, for optimization)
btScalar m_gravity;
btScalar m_turnAngle;
btScalar m_stepHeight;
btScalar m_addedMargin;//@todo: remove this and fix the code
///this is the desired walk direction, set by the user
btVector3 m_walkDirection;
btVector3 m_normalizedDirection;
//some internal variables
btVector3 m_currentPosition;
btScalar m_currentStepOffset;
btVector3 m_targetPosition;
///keep track of the contact manifolds
btManifoldArray m_manifoldArray;
bool m_touchingContact;
btVector3 m_touchingNormal;
bool m_wasOnGround;
bool m_wasJumping;
bool m_useGhostObjectSweepTest;
bool m_useWalkDirection;
btScalar m_velocityTimeInterval;
int m_upAxis;
static btVector3* getUpAxisDirections();
bool m_interpolateUp;
bool full_drop;
bool bounce_fix;
btVector3 computeReflectionDirection(const btVector3& direction, const btVector3& normal);
btVector3 parallelComponent(const btVector3& direction, const btVector3& normal);
btVector3 perpindicularComponent(const btVector3& direction, const btVector3& normal);
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 stepDown(btCollisionWorld* collisionWorld, btScalar dt);
public:
BT_DECLARE_ALIGNED_ALLOCATOR();
CharacterController(
btPairCachingGhostObject* ghostObject,
btConvexShape* convexShape,
btScalar stepHeight,
int upAxis = 1);
~CharacterController();
///btActionInterface interface
virtual void updateAction(btCollisionWorld* collisionWorld, btScalar deltaTime) {
preStep(collisionWorld);
playerStep(collisionWorld, deltaTime);
}
///btActionInterface interface
void debugDraw(btIDebugDraw* debugDrawer);
void setUpAxis(int axis) {
if (axis < 0)
axis = 0;
if (axis > 2)
axis = 2;
m_upAxis = axis;
}
/// This should probably be called setPositionIncrementPerSimulatorStep.
/// This is neither a direction nor a velocity, but the amount to
/// increment the position each simulation iteration, regardless
/// of dt.
/// This call will reset any velocity set by setVelocityForTimeInterval().
virtual void setWalkDirection(const btVector3& walkDirection);
/// Caller provides a velocity with which the character should move for
/// the given time period. After the time period, velocity is reset
/// to zero.
/// This call will reset any walk direction set by setWalkDirection().
/// Negative time intervals will result in no motion.
virtual void setVelocityForTimeInterval(const btVector3& velocity,
btScalar timeInterval);
void reset(btCollisionWorld* collisionWorld );
void warp(const btVector3& origin);
void preStep(btCollisionWorld* collisionWorld);
void playerStep(btCollisionWorld* collisionWorld, btScalar dt);
void setFallSpeed(btScalar fallSpeed);
void setJumpSpeed(btScalar jumpSpeed);
void setMaxJumpHeight(btScalar maxJumpHeight);
bool canJump() const;
void jump();
void setGravity(btScalar gravity);
btScalar getGravity() const;
/// The max slope determines the maximum angle that the controller can walk up.
/// The slope angle is measured in radians.
void setMaxSlope(btScalar slopeRadians);
btScalar getMaxSlope() const;
btPairCachingGhostObject* getGhostObject();
void setUseGhostSweepTest(bool useGhostObjectSweepTest) {
m_useGhostObjectSweepTest = useGhostObjectSweepTest;
}
bool onGround() const;
void setUpInterpolate(bool value);
};
#endif // hifi_CharacterController_h

View file

@ -9,11 +9,12 @@
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#include <AABox.h>
#include "PhysicsEngine.h"
#include "ShapeInfoUtil.h"
#include "PhysicsHelpers.h"
#include "ThreadSafeDynamicsWorld.h"
#include "AvatarData.h"
static uint32_t _numSubsteps;
@ -23,10 +24,12 @@ uint32_t PhysicsEngine::getNumSubsteps() {
}
PhysicsEngine::PhysicsEngine(const glm::vec3& offset)
: _originOffset(offset) {
: _originOffset(offset),
_avatarShapeLocalOffset(0.0f) {
}
PhysicsEngine::~PhysicsEngine() {
// TODO: delete engine components... if we ever plan to create more than one instance
}
// begin EntitySimulation overrides
@ -348,6 +351,9 @@ void PhysicsEngine::init(EntityEditPacketSender* packetSender) {
_constraintSolver = new btSequentialImpulseConstraintSolver;
_dynamicsWorld = new ThreadSafeDynamicsWorld(_collisionDispatcher, _broadphaseFilter, _constraintSolver, _collisionConfig);
_ghostPairCallback = new btGhostPairCallback();
_dynamicsWorld->getPairCache()->setInternalGhostPairCallback(_ghostPairCallback);
// default gravity of the world is zero, so each object must specify its own gravity
// TODO: set up gravity zones
_dynamicsWorld->setGravity(btVector3(0.0f, 0.0f, 0.0f));
@ -359,6 +365,9 @@ 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
@ -376,17 +385,13 @@ void PhysicsEngine::stepSimulation() {
float timeStep = btMin(dt, MAX_TIMESTEP);
if (_avatarData->isPhysicsEnabled()) {
_avatarGhostObject->setWorldTransform(btTransform(glmToBullet(_avatarData->getOrientation()),
glmToBullet(_avatarData->getPosition())));
// WORKAROUND: there is a bug in the debug Bullet-2.82 libs where a zero length walk velocity will trigger
// an assert when the getNormalizedVector() helper function in btKinematicCharacterController.cpp tries to
// first normalize a vector before checking its length. Here we workaround the problem by checking the
// length first. NOTE: the character's velocity is reset to zero after each step, so when we DON'T set
// the velocity for this time interval it is the same thing as setting its velocity to zero.
// 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());
if (walkVelocity.length2() > FLT_EPSILON * FLT_EPSILON) {
_characterController->setVelocityForTimeInterval(walkVelocity, timeStep);
}
_characterController->setVelocityForTimeInterval(walkVelocity, timeStep);
}
// This is step (2).
@ -411,8 +416,10 @@ void PhysicsEngine::stepSimulation() {
if (_avatarData->isPhysicsEnabled()) {
const btTransform& avatarTransform = _avatarGhostObject->getWorldTransform();
_avatarData->setOrientation(bulletToGLM(avatarTransform.getRotation()));
_avatarData->setPosition(bulletToGLM(avatarTransform.getOrigin()));
glm::quat rotation = bulletToGLM(avatarTransform.getRotation());
glm::vec3 offset = rotation * _avatarShapeLocalOffset;
_avatarData->setOrientation(rotation);
_avatarData->setPosition(bulletToGLM(avatarTransform.getOrigin()) - offset);
}
unlock();
@ -698,28 +705,73 @@ bool PhysicsEngine::updateObjectHard(btRigidBody* body, ObjectMotionState* motio
void PhysicsEngine::setAvatarData(AvatarData *avatarData) {
_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;
}
// delete old controller and friends
_dynamicsWorld->removeCollisionObject(_avatarGhostObject);
_dynamicsWorld->removeAction(_characterController);
delete _characterController;
_characterController = NULL;
delete _avatarGhostObject;
_avatarGhostObject = NULL;
delete capsule;
}
// set offset
_avatarShapeLocalOffset = offset;
// build ghost, shape, and controller
_avatarGhostObject = new btPairCachingGhostObject();
_avatarGhostObject->setWorldTransform(btTransform(glmToBullet(_avatarData->getOrientation()),
glmToBullet(_avatarData->getPosition())));
glmToBullet(_avatarData->getPosition())));
// ?TODO: use ShapeManager to get avatar's shape?
btCapsuleShape* capsule = new btCapsuleShape(radius, 2.0f * halfHeight);
// XXX these values should be computed from the character model.
btScalar characterRadius = 0.3f;
btScalar characterHeight = 1.75 - 2.0f * characterRadius;
btScalar stepHeight = btScalar(0.35);
btConvexShape* capsule = new btCapsuleShape(characterRadius, characterHeight);
_avatarGhostObject->setCollisionShape(capsule);
_avatarGhostObject->setCollisionFlags(btCollisionObject::CF_CHARACTER_OBJECT);
_characterController = new btKinematicCharacterController(_avatarGhostObject, capsule, stepHeight);
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);
btBroadphaseProxy::StaticFilter | btBroadphaseProxy::DefaultFilter);
_dynamicsWorld->addAction(_characterController);
_characterController->reset (_dynamicsWorld);
// _characterController->warp (btVector3(10.210001,-2.0306311,16.576973));
btGhostPairCallback* ghostPairCallback = new btGhostPairCallback();
_dynamicsWorld->getPairCache()->setInternalGhostPairCallback(ghostPairCallback);
_characterController->reset(_dynamicsWorld);
}

View file

@ -17,21 +17,19 @@
#include <QSet>
#include <QReadWriteLock>
#include <btBulletDynamicsCommon.h>
#include <BulletDynamics/Dynamics/btDiscreteDynamicsWorld.h>
#include <BulletCollision/CollisionDispatch/btGhostObject.h>
#include <BulletDynamics/Character/btCharacterControllerInterface.h>
#include <BulletCollision/CollisionShapes/btCapsuleShape.h>
#include <BulletDynamics/Character/btKinematicCharacterController.h>
//#include <BulletCollision/CollisionShapes/btCapsuleShape.h>
#include <AvatarData.h>
#include <EntityItem.h>
#include <EntitySimulation.h>
#include "BulletUtil.h"
#include "CharacterController.h"
#include "ContactInfo.h"
#include "EntityMotionState.h"
#include "ShapeManager.h"
#include "ThreadSafeDynamicsWorld.h"
#include "AvatarData.h"
const float HALF_SIMULATION_EXTENT = 512.0f; // meters
@ -112,6 +110,7 @@ private:
btBroadphaseInterface* _broadphaseFilter = NULL;
btSequentialImpulseConstraintSolver* _constraintSolver = NULL;
ThreadSafeDynamicsWorld* _dynamicsWorld = NULL;
btGhostPairCallback* _ghostPairCallback = NULL;
ShapeManager _shapeManager;
glm::vec3 _originOffset;
@ -129,15 +128,10 @@ private:
uint32_t _lastNumSubstepsAtUpdateInternal = 0;
/// character collisions
btCharacterControllerInterface* _characterController = 0;
class btPairCachingGhostObject* _avatarGhostObject = 0;
AvatarData *_avatarData = 0;
// QReadWriteLock _busyLock;
// void lockBusyForRead() { _busyLock.lockForRead(); }
// void lockBusyForWrite() { _busyLock.lockForWrite(); }
// void unlockBusy() { _busyLock.unlock(); }
// QMap<QUuid, bool> _busyComputingShape;
CharacterController* _characterController = NULL;
class btPairCachingGhostObject* _avatarGhostObject = NULL;
AvatarData* _avatarData = NULL;
glm::vec3 _avatarShapeLocalOffset;
};
#endif // hifi_PhysicsEngine_h

View file

@ -22,7 +22,7 @@ ShapeManager::~ShapeManager() {
int numShapes = _shapeMap.size();
for (int i = 0; i < numShapes; ++i) {
ShapeReference* shapeRef = _shapeMap.getAtIndex(i);
delete shapeRef->_shape;
delete shapeRef->shape;
}
_shapeMap.clear();
}
@ -44,26 +44,33 @@ btCollisionShape* ShapeManager::getShape(const ShapeInfo& info) {
ShapeReference* shapeRef = _shapeMap.find(key);
if (shapeRef) {
// qDebug() << "OOOOOO ref";
shapeRef->_refCount++;
return shapeRef->_shape;
shapeRef->refCount++;
return shapeRef->shape;
}
btCollisionShape* shape = ShapeInfoUtil::createShapeFromInfo(info);
if (shape) {
// qDebug() << "OOOOOO shape";
ShapeReference newRef;
newRef._refCount = 1;
newRef._shape = shape;
newRef.refCount = 1;
newRef.shape = shape;
newRef.key = key;
_shapeMap.insert(key, newRef);
}
return shape;
}
<<<<<<< HEAD
void ShapeManager::dereferenceShapeReferece(ShapeReference* shapeRef, DoubleHashKey key) {
=======
// private helper method
bool ShapeManager::releaseShape(const DoubleHashKey& key) {
ShapeReference* shapeRef = _shapeMap.find(key);
>>>>>>> 641581a825cbc656ef23b8d1198733d20ebef62e
if (shapeRef) {
if (shapeRef->_refCount > 0) {
shapeRef->_refCount--;
if (shapeRef->_refCount == 0) {
if (shapeRef->refCount > 0) {
shapeRef->refCount--;
if (shapeRef->refCount == 0) {
_pendingGarbage.push_back(key);
const int MAX_GARBAGE_CAPACITY = 127;
if (_pendingGarbage.size() > MAX_GARBAGE_CAPACITY) {
@ -82,6 +89,7 @@ void ShapeManager::dereferenceShapeReferece(ShapeReference* shapeRef, DoubleHash
assert(false);
}
<<<<<<< HEAD
void ShapeManager::releaseShape(const ShapeInfo& info) {
DoubleHashKey key = info.getHash();
@ -105,6 +113,21 @@ void ShapeManager::releaseShape(const btCollisionShape* shape) {
// ShapeInfo info;
// ShapeInfoUtil::collectInfoFromShape(shape, info);
// releaseShape(info);
=======
bool ShapeManager::releaseShape(const ShapeInfo& info) {
return releaseShape(info.getHash());
}
bool ShapeManager::releaseShape(const btCollisionShape* shape) {
int numShapes = _shapeMap.size();
for (int i = 0; i < numShapes; ++i) {
ShapeReference* shapeRef = _shapeMap.getAtIndex(i);
if (shape == shapeRef->shape) {
return releaseShape(shapeRef->key);
}
}
return false;
>>>>>>> 641581a825cbc656ef23b8d1198733d20ebef62e
}
void ShapeManager::collectGarbage() {
@ -112,8 +135,8 @@ void ShapeManager::collectGarbage() {
for (int i = 0; i < numShapes; ++i) {
DoubleHashKey& key = _pendingGarbage[i];
ShapeReference* shapeRef = _shapeMap.find(key);
if (shapeRef && shapeRef->_refCount == 0) {
delete shapeRef->_shape;
if (shapeRef && shapeRef->refCount == 0) {
delete shapeRef->shape;
_shapeMap.remove(key);
}
}
@ -124,7 +147,29 @@ int ShapeManager::getNumReferences(const ShapeInfo& info) const {
DoubleHashKey key = info.getHash();
const ShapeReference* shapeRef = _shapeMap.find(key);
if (shapeRef) {
return shapeRef->_refCount;
return shapeRef->refCount;
}
return -1;
return 0;
}
int ShapeManager::getNumReferences(const btCollisionShape* shape) const {
int numShapes = _shapeMap.size();
for (int i = 0; i < numShapes; ++i) {
const ShapeReference* shapeRef = _shapeMap.getAtIndex(i);
if (shape == shapeRef->shape) {
return shapeRef->refCount;
}
}
return 0;
}
bool ShapeManager::hasShape(const btCollisionShape* shape) const {
int numShapes = _shapeMap.size();
for (int i = 0; i < numShapes; ++i) {
const ShapeReference* shapeRef = _shapeMap.getAtIndex(i);
if (shape == shapeRef->shape) {
return true;
}
}
return false;
}

View file

@ -39,12 +39,17 @@ public:
// validation methods
int getNumShapes() const { return _shapeMap.size(); }
int getNumReferences(const ShapeInfo& info) const;
int getNumReferences(const btCollisionShape* shape) const;
bool hasShape(const btCollisionShape* shape) const;
private:
bool releaseShape(const DoubleHashKey& key);
struct ShapeReference {
int _refCount;
btCollisionShape* _shape;
ShapeReference() : _refCount(0), _shape(NULL) {}
int refCount;
btCollisionShape* shape;
DoubleHashKey key;
ShapeReference() : refCount(0), shape(NULL) {}
};
void dereferenceShapeReferece(ShapeReference* shapeRef, DoubleHashKey key);

View file

@ -104,15 +104,16 @@ void MeshInfoTests::testWithTetrahedronAsMesh(){
glm::vec3 p2(52.61236, 5.00000, -5.38580);
glm::vec3 p3(2.00000, 5.00000, 3.00000);
glm::vec3 centroid(15.92492, 0.782813, 3.72962);
float volume = 1873.233236f;
float inertia_a = 43520.33257f;
//actual should be 194711.28938f. But for some reason it becomes 194711.296875 during
/* TODO: actually test inertia/volume calculations here
//float volume = 1873.233236f;
//runtime due to how floating points are stored.
float inertia_a = 43520.33257f;
float inertia_b = 194711.289f;
float inertia_c = 191168.76173f;
float inertia_aa = 4417.66150f;
float inertia_bb = -46343.16662f;
float inertia_cc = 11996.20119f;
*/
std::cout << std::setprecision(12);
vector<glm::vec3> vertices = { p0, p1, p2, p3 };
vector<int> triangles = { 0, 2, 1, 0, 3, 2, 0, 1, 3, 1, 2, 3 };
@ -129,6 +130,7 @@ void MeshInfoTests::testWithTetrahedronAsMesh(){
p2 -= centroid;
p3 -= centroid;
}
void MeshInfoTests::testWithCube(){
glm::vec3 p0(1.0, -1.0, -1.0);
glm::vec3 p1(1.0, -1.0, 1.0);
@ -232,4 +234,4 @@ void MeshInfoTests::runAllTests(){
testWithTetrahedronAsMesh();
testWithUnitCube();
testWithCube();
}
}

View file

@ -21,10 +21,8 @@ void ShapeManagerTests::testShapeAccounting() {
ShapeInfo info;
info.setBox(glm::vec3(1.0f, 1.0f, 1.0f));
// NOTE: ShapeManager returns -1 as refcount when the shape is unknown,
// which is distinct from "known but with zero references"
int numReferences = shapeManager.getNumReferences(info);
if (numReferences != -1) {
if (numReferences != 0) {
std::cout << __FILE__ << ":" << __LINE__
<< " ERROR: expected ignorant ShapeManager after initialization" << std::endl;
}
@ -99,8 +97,7 @@ void ShapeManagerTests::testShapeAccounting() {
if (shapeManager.getNumShapes() != 0) {
std::cout << __FILE__ << ":" << __LINE__ << " ERROR: expected zero shapes after release" << std::endl;
}
numReferences = shapeManager.getNumReferences(info);
if (numReferences != -1) {
if (shapeManager.hasShape(shape)) {
std::cout << __FILE__ << ":" << __LINE__
<< " ERROR: expected ignorant ShapeManager after garbage collection" << std::endl;
}
@ -117,33 +114,64 @@ void ShapeManagerTests::testShapeAccounting() {
void ShapeManagerTests::addManyShapes() {
ShapeManager shapeManager;
QVector<btCollisionShape*> shapes;
int numSizes = 100;
float startSize = 1.0f;
float endSize = 99.0f;
float deltaSize = (endSize - startSize) / (float)numSizes;
ShapeInfo info;
for (int i = 0; i < numSizes; ++i) {
// make a sphere
float s = startSize + (float)i * deltaSize;
glm::vec3 scale(s, 1.23f + s, s - 0.573f);
info.setBox(0.5f * scale);
btCollisionShape* shape = shapeManager.getShape(info);
shapes.push_back(shape);
if (!shape) {
std::cout << __FILE__ << ":" << __LINE__
<< " ERROR: i = " << i << " null box shape for scale = " << scale << std::endl;
}
// make a box
float radius = 0.5f * s;
info.setSphere(radius);
shape = shapeManager.getShape(info);
shapes.push_back(shape);
if (!shape) {
std::cout << __FILE__ << ":" << __LINE__
<< " ERROR: i = " << i << " null sphere shape for radius = " << radius << std::endl;
}
}
// verify shape count
int numShapes = shapeManager.getNumShapes();
if (numShapes != 2 * numSizes) {
std::cout << __FILE__ << ":" << __LINE__
<< " ERROR: expected numShapes = " << numSizes << " but found numShapes = " << numShapes << std::endl;
}
// release each shape by pointer
for (int i = 0; i < numShapes; ++i) {
btCollisionShape* shape = shapes[i];
bool success = shapeManager.releaseShape(shape);
if (!success) {
std::cout << __FILE__ << ":" << __LINE__
<< " ERROR: failed to release shape index " << i << std::endl;
break;
}
}
// verify zero references
for (int i = 0; i < numShapes; ++i) {
btCollisionShape* shape = shapes[i];
int numReferences = shapeManager.getNumReferences(shape);
if (numReferences != 0) {
std::cout << __FILE__ << ":" << __LINE__
<< " ERROR: expected zero references for shape " << i
<< " but refCount = " << numReferences << std::endl;
}
}
}
void ShapeManagerTests::addBoxShape() {