mirror of
https://github.com/lubosz/overte.git
synced 2025-04-16 06:16:18 +02:00
Merge branch 'master' of https://github.com/highfidelity/hifi into metavoxels
This commit is contained in:
commit
9ded9b0696
49 changed files with 1665 additions and 501 deletions
|
@ -335,7 +335,7 @@ void AudioMixer::prepareMixForListeningNode(Node* node) {
|
|||
AudioMixerClientData* otherNodeClientData = (AudioMixerClientData*) otherNode->getLinkedData();
|
||||
|
||||
// enumerate the ARBs attached to the otherNode and add all that should be added to mix
|
||||
for (unsigned int i = 0; i < otherNodeClientData->getRingBuffers().size(); i++) {
|
||||
for (int i = 0; i < otherNodeClientData->getRingBuffers().size(); i++) {
|
||||
PositionalAudioRingBuffer* otherNodeBuffer = otherNodeClientData->getRingBuffers()[i];
|
||||
|
||||
if ((*otherNode != *node
|
||||
|
|
|
@ -25,14 +25,14 @@ AudioMixerClientData::AudioMixerClientData() :
|
|||
}
|
||||
|
||||
AudioMixerClientData::~AudioMixerClientData() {
|
||||
for (unsigned int i = 0; i < _ringBuffers.size(); i++) {
|
||||
for (int i = 0; i < _ringBuffers.size(); i++) {
|
||||
// delete this attached PositionalAudioRingBuffer
|
||||
delete _ringBuffers[i];
|
||||
}
|
||||
}
|
||||
|
||||
AvatarAudioRingBuffer* AudioMixerClientData::getAvatarAudioRingBuffer() const {
|
||||
for (unsigned int i = 0; i < _ringBuffers.size(); i++) {
|
||||
for (int i = 0; i < _ringBuffers.size(); i++) {
|
||||
if (_ringBuffers[i]->getType() == PositionalAudioRingBuffer::Microphone) {
|
||||
return (AvatarAudioRingBuffer*) _ringBuffers[i];
|
||||
}
|
||||
|
@ -79,7 +79,7 @@ int AudioMixerClientData::parseData(const QByteArray& packet) {
|
|||
|
||||
InjectedAudioRingBuffer* matchingInjectedRingBuffer = NULL;
|
||||
|
||||
for (unsigned int i = 0; i < _ringBuffers.size(); i++) {
|
||||
for (int i = 0; i < _ringBuffers.size(); i++) {
|
||||
if (_ringBuffers[i]->getType() == PositionalAudioRingBuffer::Injector
|
||||
&& ((InjectedAudioRingBuffer*) _ringBuffers[i])->getStreamIdentifier() == streamIdentifier) {
|
||||
matchingInjectedRingBuffer = (InjectedAudioRingBuffer*) _ringBuffers[i];
|
||||
|
@ -99,7 +99,7 @@ int AudioMixerClientData::parseData(const QByteArray& packet) {
|
|||
}
|
||||
|
||||
void AudioMixerClientData::checkBuffersBeforeFrameSend(int jitterBufferLengthSamples) {
|
||||
for (unsigned int i = 0; i < _ringBuffers.size(); i++) {
|
||||
for (int i = 0; i < _ringBuffers.size(); i++) {
|
||||
if (_ringBuffers[i]->shouldBeAddedToMix(jitterBufferLengthSamples)) {
|
||||
// this is a ring buffer that is ready to go
|
||||
// set its flag so we know to push its buffer when all is said and done
|
||||
|
@ -113,7 +113,7 @@ void AudioMixerClientData::checkBuffersBeforeFrameSend(int jitterBufferLengthSam
|
|||
}
|
||||
|
||||
void AudioMixerClientData::pushBuffersAfterFrameSend() {
|
||||
for (unsigned int i = 0; i < _ringBuffers.size(); i++) {
|
||||
for (int i = 0; i < _ringBuffers.size(); i++) {
|
||||
// this was a used buffer, push the output pointer forwards
|
||||
PositionalAudioRingBuffer* audioBuffer = _ringBuffers[i];
|
||||
|
||||
|
|
|
@ -659,7 +659,7 @@ void DomainServer::readAvailableDatagrams() {
|
|||
wasNoisyTimerStarted = true;
|
||||
}
|
||||
|
||||
const quint64 NOISY_MESSAGE_INTERVAL_MSECS = 5 * 1000;
|
||||
const qint64 NOISY_MESSAGE_INTERVAL_MSECS = 5 * 1000;
|
||||
|
||||
if (requestAssignment.getType() != Assignment::AgentType
|
||||
|| noisyMessageTimer.elapsed() > NOISY_MESSAGE_INTERVAL_MSECS) {
|
||||
|
|
|
@ -691,6 +691,10 @@ function rayPlaneIntersection(pickRay, point, normal) {
|
|||
}
|
||||
|
||||
function mousePressEvent(event) {
|
||||
if (altIsPressed) {
|
||||
return;
|
||||
}
|
||||
|
||||
mouseLastPosition = { x: event.x, y: event.y };
|
||||
modelSelected = false;
|
||||
var clickedOverlay = Overlays.getOverlayAtPoint({x: event.x, y: event.y});
|
||||
|
@ -790,6 +794,10 @@ var oldModifier = 0;
|
|||
var modifier = 0;
|
||||
var wasShifted = false;
|
||||
function mouseMoveEvent(event) {
|
||||
if (altIsPressed) {
|
||||
return;
|
||||
}
|
||||
|
||||
var pickRay = Camera.computePickRay(event.x, event.y);
|
||||
|
||||
if (!modelSelected) {
|
||||
|
@ -894,6 +902,10 @@ function mouseMoveEvent(event) {
|
|||
}
|
||||
|
||||
function mouseReleaseEvent(event) {
|
||||
if (altIsPressed) {
|
||||
return;
|
||||
}
|
||||
|
||||
modelSelected = false;
|
||||
|
||||
glowedModelID.id = -1;
|
||||
|
@ -962,4 +974,16 @@ Menu.menuItemEvent.connect(function(menuItem){
|
|||
}
|
||||
});
|
||||
|
||||
// handling of inspect.js concurrence
|
||||
altIsPressed = false;
|
||||
Controller.keyPressEvent.connect(function(event) {
|
||||
if (event.text == "ALT") {
|
||||
altIsPressed = true;
|
||||
}
|
||||
});
|
||||
Controller.keyReleaseEvent.connect(function(event) {
|
||||
if (event.text == "ALT") {
|
||||
altIsPressed = false;
|
||||
}
|
||||
});
|
||||
|
||||
|
|
|
@ -34,6 +34,7 @@ var noMode = 0;
|
|||
var orbitMode = 1;
|
||||
var radialMode = 2;
|
||||
var panningMode = 3;
|
||||
var detachedMode = 4;
|
||||
|
||||
var mode = noMode;
|
||||
|
||||
|
@ -48,6 +49,9 @@ var radius = 0.0;
|
|||
var azimuth = 0.0;
|
||||
var altitude = 0.0;
|
||||
|
||||
var avatarPosition;
|
||||
var avatarOrientation;
|
||||
|
||||
|
||||
function handleRadialMode(dx, dy) {
|
||||
azimuth += dx / AZIMUTH_RATE;
|
||||
|
@ -108,7 +112,7 @@ function restoreCameraState() {
|
|||
}
|
||||
|
||||
function handleModes() {
|
||||
var newMode = noMode;
|
||||
var newMode = (mode == noMode) ? noMode : detachedMode;
|
||||
if (alt) {
|
||||
if (control) {
|
||||
if (shift) {
|
||||
|
@ -121,6 +125,22 @@ function handleModes() {
|
|||
}
|
||||
}
|
||||
|
||||
// if entering detachMode
|
||||
if (newMode == detachedMode && mode != detachedMode) {
|
||||
avatarPosition = MyAvatar.position;
|
||||
avatarOrientation = MyAvatar.orientation;
|
||||
}
|
||||
// if leaving detachMode
|
||||
if (mode == detachedMode && newMode == detachedMode &&
|
||||
(avatarPosition.x != MyAvatar.position.x ||
|
||||
avatarPosition.y != MyAvatar.position.y ||
|
||||
avatarPosition.z != MyAvatar.position.z ||
|
||||
avatarOrientation.x != MyAvatar.orientation.x ||
|
||||
avatarOrientation.y != MyAvatar.orientation.y ||
|
||||
avatarOrientation.z != MyAvatar.orientation.z ||
|
||||
avatarOrientation.w != MyAvatar.orientation.w)) {
|
||||
newMode = noMode;
|
||||
}
|
||||
// if leaving noMode
|
||||
if (mode == noMode && newMode != noMode) {
|
||||
saveCameraState();
|
||||
|
@ -177,30 +197,45 @@ function keyReleaseEvent(event) {
|
|||
|
||||
function mousePressEvent(event) {
|
||||
if (alt && !isActive) {
|
||||
isActive = true;
|
||||
mouseLastX = event.x;
|
||||
mouseLastY = event.y;
|
||||
|
||||
// Compute trajectories related values
|
||||
var pickRay = Camera.computePickRay(mouseLastX, mouseLastY);
|
||||
var intersection = Voxels.findRayIntersection(pickRay);
|
||||
var voxelIntersection = Voxels.findRayIntersection(pickRay);
|
||||
var modelIntersection = Models.findRayIntersection(pickRay);
|
||||
|
||||
position = Camera.getPosition();
|
||||
|
||||
avatarTarget = MyAvatar.getTargetAvatarPosition();
|
||||
voxelTarget = intersection.intersection;
|
||||
if (Vec3.length(Vec3.subtract(avatarTarget, position)) < Vec3.length(Vec3.subtract(voxelTarget, position))) {
|
||||
if (avatarTarget.x != 0 || avatarTarget.y != 0 || avatarTarget.z != 0) {
|
||||
center = avatarTarget;
|
||||
} else {
|
||||
center = voxelTarget;
|
||||
}
|
||||
} else {
|
||||
if (voxelTarget.x != 0 || voxelTarget.y != 0 || voxelTarget.z != 0) {
|
||||
center = voxelTarget;
|
||||
} else {
|
||||
center = avatarTarget;
|
||||
}
|
||||
var avatarTarget = MyAvatar.getTargetAvatarPosition();
|
||||
var voxelTarget = voxelIntersection.intersection;
|
||||
|
||||
|
||||
var distance = -1;
|
||||
var string;
|
||||
|
||||
if (modelIntersection.intersects && modelIntersection.accurate) {
|
||||
distance = modelIntersection.distance;
|
||||
center = modelIntersection.modelProperties.position;
|
||||
string = "Inspecting model";
|
||||
}
|
||||
|
||||
if ((distance == -1 || Vec3.length(Vec3.subtract(avatarTarget, position)) < distance) &&
|
||||
(avatarTarget.x != 0 || avatarTarget.y != 0 || avatarTarget.z != 0)) {
|
||||
distance = Vec3.length(Vec3.subtract(avatarTarget, position));
|
||||
center = avatarTarget;
|
||||
string = "Inspecting avatar";
|
||||
}
|
||||
|
||||
if ((distance == -1 || Vec3.length(Vec3.subtract(voxelTarget, position)) < distance) &&
|
||||
(voxelTarget.x != 0 || voxelTarget.y != 0 || voxelTarget.z != 0)) {
|
||||
distance = Vec3.length(Vec3.subtract(voxelTarget, position));
|
||||
center = voxelTarget;
|
||||
string = "Inspecting voxel";
|
||||
}
|
||||
|
||||
if (distance == -1) {
|
||||
return;
|
||||
}
|
||||
|
||||
vector = Vec3.subtract(position, center);
|
||||
|
@ -209,6 +244,8 @@ function mousePressEvent(event) {
|
|||
altitude = Math.asin(vector.y / Vec3.length(vector));
|
||||
|
||||
Camera.keepLookingAt(center);
|
||||
print(string);
|
||||
isActive = true;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -235,6 +272,10 @@ function mouseMoveEvent(event) {
|
|||
}
|
||||
}
|
||||
|
||||
function update() {
|
||||
handleModes();
|
||||
}
|
||||
|
||||
function scriptEnding() {
|
||||
if (mode != noMode) {
|
||||
restoreCameraState();
|
||||
|
@ -248,4 +289,5 @@ Controller.mousePressEvent.connect(mousePressEvent);
|
|||
Controller.mouseReleaseEvent.connect(mouseReleaseEvent);
|
||||
Controller.mouseMoveEvent.connect(mouseMoveEvent);
|
||||
|
||||
Script.update.connect(update);
|
||||
Script.scriptEnding.connect(scriptEnding);
|
BIN
interface/resources/images/sixense-reticle.png
Normal file
BIN
interface/resources/images/sixense-reticle.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 26 KiB |
|
@ -3523,12 +3523,13 @@ ScriptEngine* Application::loadScript(const QString& scriptName, bool loadScript
|
|||
} else {
|
||||
// start the script on a new thread...
|
||||
scriptEngine = new ScriptEngine(scriptUrl, &_controllerScriptingInterface);
|
||||
_scriptEnginesHash.insert(scriptURLString, scriptEngine);
|
||||
|
||||
if (!scriptEngine->hasScript()) {
|
||||
qDebug() << "Application::loadScript(), script failed to load...";
|
||||
return NULL;
|
||||
}
|
||||
|
||||
_scriptEnginesHash.insert(scriptURLString, scriptEngine);
|
||||
_runningScriptsWidget->setRunningScripts(getRunningScripts());
|
||||
}
|
||||
|
||||
|
|
|
@ -1419,7 +1419,7 @@ bool Audio::switchOutputToAudioDevice(const QAudioDeviceInfo& outputDeviceInfo)
|
|||
// proportional to the accelerator ratio.
|
||||
|
||||
#ifdef Q_OS_WIN
|
||||
const float Audio::CALLBACK_ACCELERATOR_RATIO = 0.4f;
|
||||
const float Audio::CALLBACK_ACCELERATOR_RATIO = 0.1f;
|
||||
#endif
|
||||
|
||||
#ifdef Q_OS_MAC
|
||||
|
|
|
@ -45,6 +45,7 @@
|
|||
#include "ui/ModelsBrowser.h"
|
||||
#include "ui/LoginDialog.h"
|
||||
#include "ui/NodeBounds.h"
|
||||
#include "devices/OculusManager.h"
|
||||
|
||||
|
||||
Menu* Menu::_instance = NULL;
|
||||
|
@ -83,6 +84,7 @@ Menu::Menu() :
|
|||
_audioJitterBufferSamples(0),
|
||||
_bandwidthDialog(NULL),
|
||||
_fieldOfView(DEFAULT_FIELD_OF_VIEW_DEGREES),
|
||||
_realWorldFieldOfView(DEFAULT_REAL_WORLD_FIELD_OF_VIEW_DEGREES),
|
||||
_faceshiftEyeDeflection(DEFAULT_FACESHIFT_EYE_DEFLECTION),
|
||||
_frustumDrawMode(FRUSTUM_DRAW_MODE_ALL),
|
||||
_viewFrustumOffset(DEFAULT_FRUSTUM_OFFSET),
|
||||
|
@ -91,6 +93,9 @@ Menu::Menu() :
|
|||
_lodToolsDialog(NULL),
|
||||
_maxVoxels(DEFAULT_MAX_VOXELS_PER_SYSTEM),
|
||||
_voxelSizeScale(DEFAULT_OCTREE_SIZE_SCALE),
|
||||
_oculusUIAngularSize(DEFAULT_OCULUS_UI_ANGULAR_SIZE),
|
||||
_sixenseReticleMoveSpeed(DEFAULT_SIXENSE_RETICLE_MOVE_SPEED),
|
||||
_invertSixenseButtons(DEFAULT_INVERT_SIXENSE_MOUSE_BUTTONS),
|
||||
_automaticAvatarLOD(true),
|
||||
_avatarLODDecreaseFPS(DEFAULT_ADJUST_AVATAR_LOD_DOWN_FPS),
|
||||
_avatarLODIncreaseFPS(ADJUST_LOD_UP_FPS),
|
||||
|
@ -127,7 +132,7 @@ Menu::Menu() :
|
|||
toggleLoginMenuItem();
|
||||
|
||||
// connect to the appropriate slots of the AccountManager so that we can change the Login/Logout menu item
|
||||
connect(&accountManager, &AccountManager::accessTokenChanged, this, &Menu::toggleLoginMenuItem);
|
||||
connect(&accountManager, &AccountManager::profileChanged, this, &Menu::toggleLoginMenuItem);
|
||||
connect(&accountManager, &AccountManager::logoutComplete, this, &Menu::toggleLoginMenuItem);
|
||||
|
||||
addDisabledActionAndSeparator(fileMenu, "Scripts");
|
||||
|
@ -165,6 +170,8 @@ Menu::Menu() :
|
|||
Qt::Key_At,
|
||||
this,
|
||||
SLOT(goTo()));
|
||||
connect(&LocationManager::getInstance(), &LocationManager::multipleDestinationsFound,
|
||||
this, &Menu::multipleDestinationsDecision);
|
||||
|
||||
addDisabledActionAndSeparator(fileMenu, "Upload Avatar Model");
|
||||
addActionToQMenuAndActionHash(fileMenu, MenuOption::UploadHead, 0, Application::getInstance(), SLOT(uploadHead()));
|
||||
|
@ -326,7 +333,7 @@ Menu::Menu() :
|
|||
shadowGroup->addAction(addCheckableActionToQMenuAndActionHash(shadowMenu, "None", 0, true));
|
||||
shadowGroup->addAction(addCheckableActionToQMenuAndActionHash(shadowMenu, MenuOption::SimpleShadows, 0, false));
|
||||
shadowGroup->addAction(addCheckableActionToQMenuAndActionHash(shadowMenu, MenuOption::CascadedShadows, 0, false));
|
||||
|
||||
|
||||
addCheckableActionToQMenuAndActionHash(renderOptionsMenu, MenuOption::Metavoxels, 0, true);
|
||||
addCheckableActionToQMenuAndActionHash(renderOptionsMenu, MenuOption::BuckyBalls, 0, false);
|
||||
addCheckableActionToQMenuAndActionHash(renderOptionsMenu, MenuOption::Particles, 0, true);
|
||||
|
@ -387,7 +394,6 @@ Menu::Menu() :
|
|||
|
||||
QMenu* sixenseOptionsMenu = developerMenu->addMenu("Sixense Options");
|
||||
addCheckableActionToQMenuAndActionHash(sixenseOptionsMenu, MenuOption::SixenseMouseInput, 0, true);
|
||||
addCheckableActionToQMenuAndActionHash(sixenseOptionsMenu, MenuOption::SixenseInvertInputButtons, 0, false);
|
||||
|
||||
QMenu* handOptionsMenu = developerMenu->addMenu("Hand Options");
|
||||
|
||||
|
@ -407,7 +413,7 @@ Menu::Menu() :
|
|||
addCheckableActionToQMenuAndActionHash(developerMenu, MenuOption::DisableNackPackets, 0, false);
|
||||
|
||||
addDisabledActionAndSeparator(developerMenu, "Testing");
|
||||
|
||||
|
||||
QMenu* timingMenu = developerMenu->addMenu("Timing and Statistics Tools");
|
||||
QMenu* perfTimerMenu = timingMenu->addMenu("Performance Timer");
|
||||
addCheckableActionToQMenuAndActionHash(perfTimerMenu, MenuOption::DisplayTimingDetails, 0, true);
|
||||
|
@ -462,7 +468,7 @@ Menu::Menu() :
|
|||
false,
|
||||
appInstance->getAudio(),
|
||||
SLOT(toggleToneInjection()));
|
||||
addCheckableActionToQMenuAndActionHash(audioDebugMenu, MenuOption::AudioScope,
|
||||
addCheckableActionToQMenuAndActionHash(audioDebugMenu, MenuOption::AudioScope,
|
||||
Qt::CTRL | Qt::Key_P, false,
|
||||
appInstance->getAudio(),
|
||||
SLOT(toggleScope()));
|
||||
|
@ -1072,9 +1078,7 @@ bool Menu::goToURL(QString location) {
|
|||
}
|
||||
|
||||
void Menu::goToUser(const QString& user) {
|
||||
LocationManager* manager = &LocationManager::getInstance();
|
||||
manager->goTo(user);
|
||||
connect(manager, &LocationManager::multipleDestinationsFound, this, &Menu::multipleDestinationsDecision);
|
||||
LocationManager::getInstance().goTo(user);
|
||||
}
|
||||
|
||||
/// Open a url, shortcutting any "hifi" scheme URLs to the local application.
|
||||
|
@ -1096,13 +1100,10 @@ void Menu::multipleDestinationsDecision(const QJsonObject& userData, const QJson
|
|||
int userResponse = msgBox.exec();
|
||||
|
||||
if (userResponse == QMessageBox::Ok) {
|
||||
Application::getInstance()->getAvatar()->goToLocationFromResponse(userData);
|
||||
Application::getInstance()->getAvatar()->goToLocationFromAddress(userData["address"].toObject());
|
||||
} else if (userResponse == QMessageBox::Open) {
|
||||
Application::getInstance()->getAvatar()->goToLocationFromResponse(userData);
|
||||
Application::getInstance()->getAvatar()->goToLocationFromAddress(placeData["address"].toObject());
|
||||
}
|
||||
|
||||
LocationManager* manager = reinterpret_cast<LocationManager*>(sender());
|
||||
disconnect(manager, &LocationManager::multipleDestinationsFound, this, &Menu::multipleDestinationsDecision);
|
||||
}
|
||||
|
||||
void Menu::muteEnvironment() {
|
||||
|
@ -1775,4 +1776,3 @@ QString Menu::getSnapshotsLocation() const {
|
|||
}
|
||||
return _snapshotsLocation;
|
||||
}
|
||||
|
||||
|
|
|
@ -90,6 +90,12 @@ public:
|
|||
void setFieldOfView(float fieldOfView) { _fieldOfView = fieldOfView; }
|
||||
float getRealWorldFieldOfView() const { return _realWorldFieldOfView; }
|
||||
void setRealWorldFieldOfView(float realWorldFieldOfView) { _realWorldFieldOfView = realWorldFieldOfView; }
|
||||
float getOculusUIAngularSize() const { return _oculusUIAngularSize; }
|
||||
void setOculusUIAngularSize(float oculusUIAngularSize) { _oculusUIAngularSize = oculusUIAngularSize; }
|
||||
float getSixenseReticleMoveSpeed() const { return _sixenseReticleMoveSpeed; }
|
||||
void setSixenseReticleMoveSpeed(float sixenseReticleMoveSpeed) { _sixenseReticleMoveSpeed = sixenseReticleMoveSpeed; }
|
||||
bool getInvertSixenseButtons() const { return _invertSixenseButtons; }
|
||||
void setInvertSixenseButtons(bool invertSixenseButtons) { _invertSixenseButtons = invertSixenseButtons; }
|
||||
|
||||
float getFaceshiftEyeDeflection() const { return _faceshiftEyeDeflection; }
|
||||
void setFaceshiftEyeDeflection(float faceshiftEyeDeflection) { _faceshiftEyeDeflection = faceshiftEyeDeflection; }
|
||||
|
@ -255,6 +261,9 @@ private:
|
|||
LodToolsDialog* _lodToolsDialog;
|
||||
int _maxVoxels;
|
||||
float _voxelSizeScale;
|
||||
float _oculusUIAngularSize;
|
||||
float _sixenseReticleMoveSpeed;
|
||||
bool _invertSixenseButtons;
|
||||
bool _automaticAvatarLOD;
|
||||
float _avatarLODDecreaseFPS;
|
||||
float _avatarLODIncreaseFPS;
|
||||
|
@ -400,7 +409,6 @@ namespace MenuOption {
|
|||
const QString SettingsExport = "Export Settings";
|
||||
const QString SettingsImport = "Import Settings";
|
||||
const QString SimpleShadows = "Simple";
|
||||
const QString SixenseInvertInputButtons = "Invert Sixense Mouse Input Buttons";
|
||||
const QString SixenseMouseInput = "Enable Sixense Mouse Input";
|
||||
const QString ShowBordersVoxelNodes = "Show Voxel Nodes";
|
||||
const QString ShowBordersModelNodes = "Show Model Nodes";
|
||||
|
|
|
@ -21,6 +21,8 @@
|
|||
|
||||
#include <SharedUtil.h>
|
||||
|
||||
#include <QThread>
|
||||
|
||||
#include "InterfaceConfig.h"
|
||||
#include "ui/TextRenderer.h"
|
||||
#include "VoxelConstants.h"
|
||||
|
@ -409,8 +411,44 @@ void runTimingTests() {
|
|||
|
||||
float NSEC_TO_USEC = 1.0f / 1000.0f;
|
||||
elapsedUsecs = (float)startTime.nsecsElapsed() * NSEC_TO_USEC;
|
||||
qDebug("QElapsedTimer::nsecElapsed() usecs: %f", elapsedUsecs / (float) numTests);
|
||||
qDebug("QElapsedTimer::nsecElapsed() usecs: %f", elapsedUsecs);
|
||||
|
||||
// Test sleep functions for accuracy
|
||||
startTime.start();
|
||||
QThread::msleep(1);
|
||||
elapsedUsecs = (float)startTime.nsecsElapsed() * NSEC_TO_USEC;
|
||||
qDebug("QThread::msleep(1) ms: %f", elapsedUsecs / 1000.0f);
|
||||
|
||||
startTime.start();
|
||||
QThread::sleep(1);
|
||||
elapsedUsecs = (float)startTime.nsecsElapsed() * NSEC_TO_USEC;
|
||||
qDebug("QThread::sleep(1) ms: %f", elapsedUsecs / 1000.0f);
|
||||
|
||||
startTime.start();
|
||||
usleep(1);
|
||||
elapsedUsecs = (float)startTime.nsecsElapsed() * NSEC_TO_USEC;
|
||||
qDebug("usleep(1) ms: %f", elapsedUsecs / 1000.0f);
|
||||
|
||||
startTime.start();
|
||||
usleep(10);
|
||||
elapsedUsecs = (float)startTime.nsecsElapsed() * NSEC_TO_USEC;
|
||||
qDebug("usleep(10) ms: %f", elapsedUsecs / 1000.0f);
|
||||
|
||||
startTime.start();
|
||||
usleep(100);
|
||||
elapsedUsecs = (float)startTime.nsecsElapsed() * NSEC_TO_USEC;
|
||||
qDebug("usleep(100) ms: %f", elapsedUsecs / 1000.0f);
|
||||
|
||||
startTime.start();
|
||||
usleep(1000);
|
||||
elapsedUsecs = (float)startTime.nsecsElapsed() * NSEC_TO_USEC;
|
||||
qDebug("usleep(1000) ms: %f", elapsedUsecs / 1000.0f);
|
||||
|
||||
startTime.start();
|
||||
usleep(15000);
|
||||
elapsedUsecs = (float)startTime.nsecsElapsed() * NSEC_TO_USEC;
|
||||
qDebug("usleep(15000) ms: %f", elapsedUsecs / 1000.0f);
|
||||
|
||||
// Random number generation
|
||||
startTime.start();
|
||||
for (int i = 0; i < numTests; i++) {
|
||||
|
|
|
@ -23,7 +23,7 @@ XmppClient::XmppClient() :
|
|||
_xmppMUCManager()
|
||||
{
|
||||
AccountManager& accountManager = AccountManager::getInstance();
|
||||
connect(&accountManager, SIGNAL(accessTokenChanged()), this, SLOT(connectToServer()));
|
||||
connect(&accountManager, SIGNAL(profileChanged()), this, SLOT(connectToServer()));
|
||||
connect(&accountManager, SIGNAL(logoutComplete()), this, SLOT(disconnectFromServer()));
|
||||
}
|
||||
|
||||
|
|
|
@ -1626,44 +1626,46 @@ void MyAvatar::resetSize() {
|
|||
}
|
||||
|
||||
void MyAvatar::goToLocationFromResponse(const QJsonObject& jsonObject) {
|
||||
|
||||
if (jsonObject["status"].toString() == "success") {
|
||||
|
||||
// send a node kill request, indicating to other clients that they should play the "disappeared" effect
|
||||
sendKillAvatar();
|
||||
|
||||
QJsonObject locationObject = jsonObject["data"].toObject()["address"].toObject();
|
||||
QString positionString = locationObject["position"].toString();
|
||||
QString orientationString = locationObject["orientation"].toString();
|
||||
QString domainHostnameString = locationObject["domain"].toString();
|
||||
|
||||
qDebug() << "Changing domain to" << domainHostnameString <<
|
||||
", position to" << positionString <<
|
||||
", and orientation to" << orientationString;
|
||||
|
||||
QStringList coordinateItems = positionString.split(',');
|
||||
QStringList orientationItems = orientationString.split(',');
|
||||
|
||||
NodeList::getInstance()->getDomainHandler().setHostname(domainHostnameString);
|
||||
|
||||
// orient the user to face the target
|
||||
glm::quat newOrientation = glm::quat(glm::radians(glm::vec3(orientationItems[0].toFloat(),
|
||||
orientationItems[1].toFloat(),
|
||||
orientationItems[2].toFloat())))
|
||||
* glm::angleAxis(PI, glm::vec3(0.0f, 1.0f, 0.0f));
|
||||
setOrientation(newOrientation);
|
||||
|
||||
// move the user a couple units away
|
||||
const float DISTANCE_TO_USER = 2.0f;
|
||||
glm::vec3 newPosition = glm::vec3(coordinateItems[0].toFloat(), coordinateItems[1].toFloat(),
|
||||
coordinateItems[2].toFloat()) - newOrientation * IDENTITY_FRONT * DISTANCE_TO_USER;
|
||||
setPosition(newPosition);
|
||||
emit transformChanged();
|
||||
goToLocationFromAddress(locationObject);
|
||||
} else {
|
||||
QMessageBox::warning(Application::getInstance()->getWindow(), "", "That user or location could not be found.");
|
||||
}
|
||||
}
|
||||
|
||||
void MyAvatar::goToLocationFromAddress(const QJsonObject& locationObject) {
|
||||
// send a node kill request, indicating to other clients that they should play the "disappeared" effect
|
||||
sendKillAvatar();
|
||||
|
||||
QString positionString = locationObject["position"].toString();
|
||||
QString orientationString = locationObject["orientation"].toString();
|
||||
QString domainHostnameString = locationObject["domain"].toString();
|
||||
|
||||
qDebug() << "Changing domain to" << domainHostnameString <<
|
||||
", position to" << positionString <<
|
||||
", and orientation to" << orientationString;
|
||||
|
||||
QStringList coordinateItems = positionString.split(',');
|
||||
QStringList orientationItems = orientationString.split(',');
|
||||
|
||||
NodeList::getInstance()->getDomainHandler().setHostname(domainHostnameString);
|
||||
|
||||
// orient the user to face the target
|
||||
glm::quat newOrientation = glm::quat(glm::radians(glm::vec3(orientationItems[0].toFloat(),
|
||||
orientationItems[1].toFloat(),
|
||||
orientationItems[2].toFloat())))
|
||||
* glm::angleAxis(PI, glm::vec3(0.0f, 1.0f, 0.0f));
|
||||
setOrientation(newOrientation);
|
||||
|
||||
// move the user a couple units away
|
||||
const float DISTANCE_TO_USER = 2.0f;
|
||||
glm::vec3 newPosition = glm::vec3(coordinateItems[0].toFloat(), coordinateItems[1].toFloat(),
|
||||
coordinateItems[2].toFloat()) - newOrientation * IDENTITY_FRONT * DISTANCE_TO_USER;
|
||||
setPosition(newPosition);
|
||||
emit transformChanged();
|
||||
}
|
||||
|
||||
void MyAvatar::updateMotionBehaviorsFromMenu() {
|
||||
Menu* menu = Menu::getInstance();
|
||||
if (menu->isOptionChecked(MenuOption::ObeyEnvironmentalGravity)) {
|
||||
|
|
|
@ -129,6 +129,7 @@ public slots:
|
|||
void resetSize();
|
||||
|
||||
void goToLocationFromResponse(const QJsonObject& jsonObject);
|
||||
void goToLocationFromAddress(const QJsonObject& jsonObject);
|
||||
|
||||
// Set/Get update the thrust that will move the avatar around
|
||||
void addThrust(glm::vec3 newThrust) { _thrust += newThrust; };
|
||||
|
|
|
@ -20,6 +20,8 @@
|
|||
|
||||
#include "renderer/ProgramObject.h"
|
||||
|
||||
const float DEFAULT_OCULUS_UI_ANGULAR_SIZE = 72.0f;
|
||||
|
||||
class Camera;
|
||||
|
||||
/// Handles interaction with the Oculus Rift.
|
||||
|
|
|
@ -185,6 +185,17 @@ void SixenseManager::update(float deltaTime) {
|
|||
#endif // HAVE_SIXENSE
|
||||
}
|
||||
|
||||
//Constants for getCursorPixelRangeMultiplier()
|
||||
const float MIN_PIXEL_RANGE_MULT = 0.4f;
|
||||
const float MAX_PIXEL_RANGE_MULT = 2.0f;
|
||||
const float RANGE_MULT = (MAX_PIXEL_RANGE_MULT - MIN_PIXEL_RANGE_MULT) * 0.01;
|
||||
|
||||
//Returns a multiplier to be applied to the cursor range for the controllers
|
||||
float SixenseManager::getCursorPixelRangeMult() const {
|
||||
//scales (0,100) to (MINIMUM_PIXEL_RANGE_MULT, MAXIMUM_PIXEL_RANGE_MULT)
|
||||
return Menu::getInstance()->getSixenseReticleMoveSpeed() * RANGE_MULT + MIN_PIXEL_RANGE_MULT;
|
||||
}
|
||||
|
||||
#ifdef HAVE_SIXENSE
|
||||
|
||||
// the calibration sequence is:
|
||||
|
@ -339,7 +350,7 @@ void SixenseManager::emulateMouse(PalmData* palm, int index) {
|
|||
Qt::MouseButton bumperButton;
|
||||
Qt::MouseButton triggerButton;
|
||||
|
||||
if (Menu::getInstance()->isOptionChecked(MenuOption::SixenseInvertInputButtons)) {
|
||||
if (Menu::getInstance()->getInvertSixenseButtons()) {
|
||||
bumperButton = Qt::LeftButton;
|
||||
triggerButton = Qt::RightButton;
|
||||
} else {
|
||||
|
@ -347,14 +358,15 @@ void SixenseManager::emulateMouse(PalmData* palm, int index) {
|
|||
triggerButton = Qt::LeftButton;
|
||||
}
|
||||
|
||||
// Get the angles, scaled between 0-1
|
||||
float xAngle = (atan2(direction.z, direction.x) + M_PI_2) + 0.5f;
|
||||
float yAngle = 1.0f - ((atan2(direction.z, direction.y) + M_PI_2) + 0.5f);
|
||||
// Get the angles, scaled between (-0.5,0.5)
|
||||
float xAngle = (atan2(direction.z, direction.x) + M_PI_2);
|
||||
float yAngle = 0.5f - ((atan2(direction.z, direction.y) + M_PI_2));
|
||||
|
||||
float cursorRange = widget->width();
|
||||
// Get the pixel range over which the xAngle and yAngle are scaled
|
||||
float cursorRange = widget->width() * getCursorPixelRangeMult();
|
||||
|
||||
pos.setX(cursorRange * xAngle);
|
||||
pos.setY(cursorRange * yAngle);
|
||||
pos.setX(widget->width() / 2.0f + cursorRange * xAngle);
|
||||
pos.setY(widget->height() / 2.0f + cursorRange * yAngle);
|
||||
|
||||
//If we are off screen then we should stop processing, and if a trigger or bumper is pressed,
|
||||
//we should unpress them.
|
||||
|
|
|
@ -30,6 +30,9 @@ const unsigned int BUTTON_FWD = 1U << 7;
|
|||
// Event type that represents moving the controller
|
||||
const unsigned int CONTROLLER_MOVE_EVENT = 1500U;
|
||||
|
||||
const float DEFAULT_SIXENSE_RETICLE_MOVE_SPEED = 37.5f;
|
||||
const bool DEFAULT_INVERT_SIXENSE_MOUSE_BUTTONS = false;
|
||||
|
||||
/// Handles interaction with the Sixense SDK (e.g., Razer Hydra).
|
||||
class SixenseManager : public QObject {
|
||||
Q_OBJECT
|
||||
|
@ -39,6 +42,7 @@ public:
|
|||
~SixenseManager();
|
||||
|
||||
void update(float deltaTime);
|
||||
float getCursorPixelRangeMult() const;
|
||||
|
||||
public slots:
|
||||
|
||||
|
|
|
@ -16,10 +16,11 @@
|
|||
|
||||
const QString GET_USER_ADDRESS = "/api/v1/users/%1/address";
|
||||
const QString GET_PLACE_ADDRESS = "/api/v1/places/%1/address";
|
||||
const QString GET_ADDRESSES = "/api/v1/addresses/%1";
|
||||
const QString POST_PLACE_CREATE = "/api/v1/places/";
|
||||
|
||||
|
||||
LocationManager::LocationManager() : _userData(), _placeData() {
|
||||
LocationManager::LocationManager() {
|
||||
|
||||
};
|
||||
|
||||
|
@ -74,58 +75,38 @@ void LocationManager::goTo(QString destination) {
|
|||
|
||||
// go to coordinate destination or to Username
|
||||
if (!goToDestination(destination)) {
|
||||
// reset data on local variables
|
||||
_userData = QJsonObject();
|
||||
_placeData = QJsonObject();
|
||||
destination = QString(QUrl::toPercentEncoding(destination));
|
||||
|
||||
JSONCallbackParameters callbackParams;
|
||||
callbackParams.jsonCallbackReceiver = this;
|
||||
callbackParams.jsonCallbackMethod = "goToUserFromResponse";
|
||||
AccountManager::getInstance().authenticatedRequest(GET_USER_ADDRESS.arg(destination),
|
||||
QNetworkAccessManager::GetOperation,
|
||||
callbackParams);
|
||||
|
||||
callbackParams.jsonCallbackMethod = "goToLocationFromResponse";
|
||||
AccountManager::getInstance().authenticatedRequest(GET_PLACE_ADDRESS.arg(destination),
|
||||
callbackParams.jsonCallbackMethod = "goToAddressFromResponse";
|
||||
AccountManager::getInstance().authenticatedRequest(GET_ADDRESSES.arg(destination),
|
||||
QNetworkAccessManager::GetOperation,
|
||||
callbackParams);
|
||||
}
|
||||
}
|
||||
|
||||
void LocationManager::goToUserFromResponse(const QJsonObject& jsonObject) {
|
||||
_userData = jsonObject;
|
||||
checkForMultipleDestinations();
|
||||
}
|
||||
void LocationManager::goToAddressFromResponse(const QJsonObject& responseData) {
|
||||
QJsonValue status = responseData["status"];
|
||||
qDebug() << responseData;
|
||||
if (!status.isUndefined() && status.toString() == "success") {
|
||||
const QJsonObject& data = responseData["data"].toObject();
|
||||
const QJsonValue& userObject = data["user"];
|
||||
const QJsonValue& placeObject = data["place"];
|
||||
|
||||
void LocationManager::goToLocationFromResponse(const QJsonObject& jsonObject) {
|
||||
_placeData = jsonObject;
|
||||
checkForMultipleDestinations();
|
||||
}
|
||||
|
||||
void LocationManager::checkForMultipleDestinations() {
|
||||
if (!_userData.isEmpty() && !_placeData.isEmpty()) {
|
||||
if (_userData.contains("status") && _userData["status"].toString() == "success" &&
|
||||
_placeData.contains("status") && _placeData["status"].toString() == "success") {
|
||||
emit multipleDestinationsFound(_userData, _placeData);
|
||||
return;
|
||||
if (!placeObject.isUndefined() && !userObject.isUndefined()) {
|
||||
emit multipleDestinationsFound(userObject.toObject(), placeObject.toObject());
|
||||
} else if (placeObject.isUndefined()) {
|
||||
Application::getInstance()->getAvatar()->goToLocationFromAddress(userObject.toObject()["address"].toObject());
|
||||
} else {
|
||||
Application::getInstance()->getAvatar()->goToLocationFromAddress(placeObject.toObject()["address"].toObject());
|
||||
}
|
||||
|
||||
if (_userData.contains("status") && _userData["status"].toString() == "success") {
|
||||
Application::getInstance()->getAvatar()->goToLocationFromResponse(_userData);
|
||||
return;
|
||||
}
|
||||
|
||||
if (_placeData.contains("status") && _placeData["status"].toString() == "success") {
|
||||
Application::getInstance()->getAvatar()->goToLocationFromResponse(_placeData);
|
||||
return;
|
||||
}
|
||||
|
||||
} else {
|
||||
QMessageBox::warning(Application::getInstance()->getWindow(), "", "That user or location could not be found.");
|
||||
}
|
||||
}
|
||||
|
||||
void LocationManager::goToUser(QString userName) {
|
||||
|
||||
JSONCallbackParameters callbackParams;
|
||||
callbackParams.jsonCallbackReceiver = Application::getInstance()->getAvatar();
|
||||
callbackParams.jsonCallbackMethod = "goToLocationFromResponse";
|
||||
|
|
|
@ -39,11 +39,7 @@ public:
|
|||
bool goToDestination(QString destination);
|
||||
|
||||
private:
|
||||
QJsonObject _userData;
|
||||
QJsonObject _placeData;
|
||||
|
||||
void replaceLastOccurrence(const QChar search, const QChar replace, QString& string);
|
||||
void checkForMultipleDestinations();
|
||||
|
||||
signals:
|
||||
void creationCompleted(LocationManager::NamedLocationCreateResponse response);
|
||||
|
@ -52,8 +48,7 @@ signals:
|
|||
private slots:
|
||||
void namedLocationDataReceived(const QJsonObject& data);
|
||||
void errorDataReceived(QNetworkReply::NetworkError error, const QString& message);
|
||||
void goToLocationFromResponse(const QJsonObject& jsonObject);
|
||||
void goToUserFromResponse(const QJsonObject& jsonObject);
|
||||
void goToAddressFromResponse(const QJsonObject& jsonObject);
|
||||
|
||||
};
|
||||
|
||||
|
|
|
@ -33,8 +33,8 @@ ApplicationOverlay::ApplicationOverlay() :
|
|||
_framebufferObject(NULL),
|
||||
_oculusAngle(65.0f * RADIANS_PER_DEGREE),
|
||||
_distance(0.5f),
|
||||
_textureFov(PI / 2.5f),
|
||||
_uiType(HEMISPHERE) {
|
||||
_textureFov(DEFAULT_OCULUS_UI_ANGULAR_SIZE * RADIANS_PER_DEGREE),
|
||||
_crosshairTexture(0) {
|
||||
|
||||
}
|
||||
|
||||
|
@ -46,10 +46,14 @@ ApplicationOverlay::~ApplicationOverlay() {
|
|||
|
||||
const float WHITE_TEXT[] = { 0.93f, 0.93f, 0.93f };
|
||||
|
||||
const float RETICLE_COLOR[] = { 0.0f, 198.0f / 255.0f, 244.0f / 255.0f };
|
||||
|
||||
// Renders the overlays either to a texture or to the screen
|
||||
void ApplicationOverlay::renderOverlay(bool renderToTexture) {
|
||||
PerformanceWarning warn(Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings), "ApplicationOverlay::displayOverlay()");
|
||||
|
||||
_textureFov = Menu::getInstance()->getOculusUIAngularSize() * RADIANS_PER_DEGREE;
|
||||
|
||||
Application* application = Application::getInstance();
|
||||
|
||||
Overlays& overlays = application->getOverlays();
|
||||
|
@ -150,31 +154,9 @@ void ApplicationOverlay::displayOverlayTextureOculus(Camera& whichCamera) {
|
|||
|
||||
Application* application = Application::getInstance();
|
||||
|
||||
QGLWidget* glWidget = application->getGLWidget();
|
||||
MyAvatar* myAvatar = application->getAvatar();
|
||||
const glm::vec3& viewMatrixTranslation = application->getViewMatrixTranslation();
|
||||
|
||||
|
||||
|
||||
// Get vertical FoV of the displayed overlay texture
|
||||
const float halfVerticalAngle = _oculusAngle / 2.0f;
|
||||
const float overlayAspectRatio = glWidget->width() / (float)glWidget->height();
|
||||
const float halfOverlayHeight = _distance * tan(halfVerticalAngle);
|
||||
const float overlayHeight = halfOverlayHeight * 2.0f;
|
||||
|
||||
// The more vertices, the better the curve
|
||||
const int numHorizontalVertices = 20;
|
||||
const int numVerticalVertices = 20;
|
||||
// U texture coordinate width at each quad
|
||||
const float quadTexWidth = 1.0f / (numHorizontalVertices - 1);
|
||||
const float quadTexHeight = 1.0f / (numVerticalVertices - 1);
|
||||
|
||||
// Get horizontal angle and angle increment from vertical angle and aspect ratio
|
||||
const float horizontalAngle = halfVerticalAngle * 2.0f * overlayAspectRatio;
|
||||
const float angleIncrement = horizontalAngle / (numHorizontalVertices - 1);
|
||||
const float halfHorizontalAngle = horizontalAngle / 2;
|
||||
|
||||
const float verticalAngleIncrement = _oculusAngle / (numVerticalVertices - 1);
|
||||
|
||||
glActiveTexture(GL_TEXTURE0);
|
||||
|
||||
|
@ -210,8 +192,6 @@ void ApplicationOverlay::displayOverlayTextureOculus(Camera& whichCamera) {
|
|||
glEnable(GL_ALPHA_TEST);
|
||||
glAlphaFunc(GL_GREATER, 0.01f);
|
||||
|
||||
float leftX, rightX, leftZ, rightZ, topZ, bottomZ;
|
||||
|
||||
//Draw the magnifiers
|
||||
for (int i = 0; i < _numMagnifiers; i++) {
|
||||
renderMagnifier(_mouseX[i], _mouseY[i]);
|
||||
|
@ -220,41 +200,9 @@ void ApplicationOverlay::displayOverlayTextureOculus(Camera& whichCamera) {
|
|||
glDepthMask(GL_FALSE);
|
||||
glDisable(GL_ALPHA_TEST);
|
||||
|
||||
//TODO: Remove immediate mode in favor of VBO
|
||||
if (_uiType == HEMISPHERE) {
|
||||
renderTexturedHemisphere();
|
||||
} else{
|
||||
glBegin(GL_QUADS);
|
||||
// Place the vertices in a semicircle curve around the camera
|
||||
for (int i = 0; i < numHorizontalVertices - 1; i++) {
|
||||
for (int j = 0; j < numVerticalVertices - 1; j++) {
|
||||
|
||||
// Calculate the X and Z coordinates from the angles and radius from camera
|
||||
leftX = sin(angleIncrement * i - halfHorizontalAngle) * _distance;
|
||||
rightX = sin(angleIncrement * (i + 1) - halfHorizontalAngle) * _distance;
|
||||
leftZ = -cos(angleIncrement * i - halfHorizontalAngle) * _distance;
|
||||
rightZ = -cos(angleIncrement * (i + 1) - halfHorizontalAngle) * _distance;
|
||||
if (_uiType == 2) {
|
||||
topZ = -cos((verticalAngleIncrement * (j + 1) - halfVerticalAngle) * overlayAspectRatio) * _distance;
|
||||
bottomZ = -cos((verticalAngleIncrement * j - halfVerticalAngle) * overlayAspectRatio) * _distance;
|
||||
} else {
|
||||
topZ = -99999;
|
||||
bottomZ = -99999;
|
||||
}
|
||||
|
||||
glTexCoord2f(quadTexWidth * i, (j + 1) * quadTexHeight);
|
||||
glVertex3f(leftX, (j + 1) * quadTexHeight * overlayHeight - halfOverlayHeight, max(topZ, leftZ));
|
||||
glTexCoord2f(quadTexWidth * (i + 1), (j + 1) * quadTexHeight);
|
||||
glVertex3f(rightX, (j + 1) * quadTexHeight * overlayHeight - halfOverlayHeight, max(topZ, rightZ));
|
||||
glTexCoord2f(quadTexWidth * (i + 1), j * quadTexHeight);
|
||||
glVertex3f(rightX, j * quadTexHeight * overlayHeight - halfOverlayHeight, max(bottomZ, rightZ));
|
||||
glTexCoord2f(quadTexWidth * i, j * quadTexHeight);
|
||||
glVertex3f(leftX, j * quadTexHeight * overlayHeight - halfOverlayHeight, max(bottomZ, leftZ));
|
||||
}
|
||||
}
|
||||
|
||||
glEnd();
|
||||
}
|
||||
renderTexturedHemisphere();
|
||||
|
||||
renderControllerPointersOculus();
|
||||
|
||||
glPopMatrix();
|
||||
|
||||
|
@ -272,51 +220,37 @@ void ApplicationOverlay::renderPointers() {
|
|||
Application* application = Application::getInstance();
|
||||
// Render a crosshair over the mouse when in Oculus
|
||||
_numMagnifiers = 0;
|
||||
int mouseX = application->getMouseX();
|
||||
int mouseY = application->getMouseY();
|
||||
|
||||
//lazily load crosshair texture
|
||||
if (_crosshairTexture == 0) {
|
||||
_crosshairTexture = Application::getInstance()->getGLWidget()->bindTexture(QImage(Application::resourcesPath() + "images/sixense-reticle.png"));
|
||||
}
|
||||
glEnable(GL_TEXTURE_2D);
|
||||
|
||||
glActiveTexture(GL_TEXTURE0);
|
||||
glBindTexture(GL_TEXTURE_2D, _crosshairTexture);
|
||||
|
||||
|
||||
if (OculusManager::isConnected() && application->getLastMouseMoveType() == QEvent::MouseMove) {
|
||||
const float pointerWidth = 10;
|
||||
const float pointerHeight = 10;
|
||||
const float crossPad = 4;
|
||||
|
||||
//If we are in oculus, render reticle later
|
||||
_numMagnifiers = 1;
|
||||
_mouseX[0] = application->getMouseX();
|
||||
_mouseY[0] = application->getMouseY();
|
||||
|
||||
mouseX -= pointerWidth / 2.0f;
|
||||
mouseY += pointerHeight / 2.0f;
|
||||
|
||||
glBegin(GL_QUADS);
|
||||
|
||||
glColor3f(1, 0, 0);
|
||||
|
||||
//Horizontal crosshair
|
||||
glVertex2i(mouseX, mouseY - crossPad);
|
||||
glVertex2i(mouseX + pointerWidth, mouseY - crossPad);
|
||||
glVertex2i(mouseX + pointerWidth, mouseY - pointerHeight + crossPad);
|
||||
glVertex2i(mouseX, mouseY - pointerHeight + crossPad);
|
||||
|
||||
//Vertical crosshair
|
||||
glVertex2i(mouseX + crossPad, mouseY);
|
||||
glVertex2i(mouseX + pointerWidth - crossPad, mouseY);
|
||||
glVertex2i(mouseX + pointerWidth - crossPad, mouseY - pointerHeight);
|
||||
glVertex2i(mouseX + crossPad, mouseY - pointerHeight);
|
||||
|
||||
glEnd();
|
||||
} else if (application->getLastMouseMoveType() == CONTROLLER_MOVE_EVENT && Menu::getInstance()->isOptionChecked(MenuOption::SixenseMouseInput)) {
|
||||
//only render controller pointer if we aren't already rendering a mouse pointer
|
||||
renderControllerPointer();
|
||||
renderControllerPointers();
|
||||
}
|
||||
glBindTexture(GL_TEXTURE_2D, 0);
|
||||
glDisable(GL_TEXTURE_2D);
|
||||
|
||||
}
|
||||
|
||||
void ApplicationOverlay::renderControllerPointer() {
|
||||
void ApplicationOverlay::renderControllerPointers() {
|
||||
Application* application = Application::getInstance();
|
||||
QGLWidget* glWidget = application->getGLWidget();
|
||||
MyAvatar* myAvatar = application->getAvatar();
|
||||
|
||||
const HandData* handData = Application::getInstance()->getAvatar()->getHandData();
|
||||
int numberOfPalms = handData->getNumPalms();
|
||||
|
||||
for (unsigned int palmIndex = 2; palmIndex < 4; palmIndex++) {
|
||||
const PalmData* palmData = NULL;
|
||||
|
@ -334,14 +268,15 @@ void ApplicationOverlay::renderControllerPointer() {
|
|||
// Get directon relative to avatar orientation
|
||||
glm::vec3 direction = glm::inverse(myAvatar->getOrientation()) * palmData->getFingerDirection();
|
||||
|
||||
// Get the angles, scaled between 0-1
|
||||
float xAngle = (atan2(direction.z, direction.x) + M_PI_2) + 0.5f;
|
||||
float yAngle = 1.0f - ((atan2(direction.z, direction.y) + M_PI_2) + 0.5f);
|
||||
// Get the angles, scaled between (-0.5,0.5)
|
||||
float xAngle = (atan2(direction.z, direction.x) + M_PI_2) ;
|
||||
float yAngle = 0.5f - ((atan2(direction.z, direction.y) + M_PI_2));
|
||||
|
||||
float cursorRange = glWidget->width();
|
||||
// Get the pixel range over which the xAngle and yAngle are scaled
|
||||
float cursorRange = glWidget->width() * application->getSixenseManager()->getCursorPixelRangeMult();
|
||||
|
||||
int mouseX = cursorRange * xAngle;
|
||||
int mouseY = cursorRange * yAngle;
|
||||
int mouseX = glWidget->width() / 2.0f + cursorRange * xAngle;
|
||||
int mouseY = glWidget->height() / 2.0f + cursorRange * yAngle;
|
||||
|
||||
//If the cursor is out of the screen then don't render it
|
||||
if (mouseX < 0 || mouseX >= glWidget->width() || mouseY < 0 || mouseY >= glWidget->height()) {
|
||||
|
@ -350,18 +285,16 @@ void ApplicationOverlay::renderControllerPointer() {
|
|||
|
||||
float pointerWidth = 40;
|
||||
float pointerHeight = 40;
|
||||
float crossPad = 16;
|
||||
|
||||
//if we have the oculus, we should make the cursor smaller since it will be
|
||||
//magnified
|
||||
if (OculusManager::isConnected()) {
|
||||
pointerWidth /= 4;
|
||||
pointerHeight /= 4;
|
||||
crossPad /= 4;
|
||||
|
||||
_mouseX[_numMagnifiers] = mouseX;
|
||||
_mouseY[_numMagnifiers] = mouseY;
|
||||
_numMagnifiers++;
|
||||
|
||||
//If oculus is enabled, we draw the crosshairs later
|
||||
continue;
|
||||
}
|
||||
|
||||
mouseX -= pointerWidth / 2.0f;
|
||||
|
@ -369,98 +302,41 @@ void ApplicationOverlay::renderControllerPointer() {
|
|||
|
||||
glBegin(GL_QUADS);
|
||||
|
||||
glColor3f(0.0f, 0.0f, 1.0f);
|
||||
glColor3f(RETICLE_COLOR[0], RETICLE_COLOR[1], RETICLE_COLOR[2]);
|
||||
|
||||
//Horizontal crosshair
|
||||
glVertex2i(mouseX, mouseY - crossPad);
|
||||
glVertex2i(mouseX + pointerWidth, mouseY - crossPad);
|
||||
glVertex2i(mouseX + pointerWidth, mouseY - pointerHeight + crossPad);
|
||||
glVertex2i(mouseX, mouseY - pointerHeight + crossPad);
|
||||
|
||||
//Vertical crosshair
|
||||
glVertex2i(mouseX + crossPad, mouseY);
|
||||
glVertex2i(mouseX + pointerWidth - crossPad, mouseY);
|
||||
glVertex2i(mouseX + pointerWidth - crossPad, mouseY - pointerHeight);
|
||||
glVertex2i(mouseX + crossPad, mouseY - pointerHeight);
|
||||
glTexCoord2d(0.0f, 0.0f); glVertex2i(mouseX, mouseY);
|
||||
glTexCoord2d(1.0f, 0.0f); glVertex2i(mouseX + pointerWidth, mouseY);
|
||||
glTexCoord2d(1.0f, 1.0f); glVertex2i(mouseX + pointerWidth, mouseY - pointerHeight);
|
||||
glTexCoord2d(0.0f, 1.0f); glVertex2i(mouseX, mouseY - pointerHeight);
|
||||
|
||||
glEnd();
|
||||
}
|
||||
}
|
||||
|
||||
//Renders a small magnification of the currently bound texture at the coordinates
|
||||
void ApplicationOverlay::renderMagnifier(int mouseX, int mouseY)
|
||||
{
|
||||
void ApplicationOverlay::renderControllerPointersOculus() {
|
||||
Application* application = Application::getInstance();
|
||||
QGLWidget* glWidget = application->getGLWidget();
|
||||
MyAvatar* myAvatar = application->getAvatar();
|
||||
const glm::vec3& viewMatrixTranslation = application->getViewMatrixTranslation();
|
||||
|
||||
float leftX, rightX, leftZ, rightZ, topZ, bottomZ;
|
||||
|
||||
const int widgetWidth = glWidget->width();
|
||||
const int widgetHeight = glWidget->height();
|
||||
const float magnification = 4.0f;
|
||||
|
||||
const float reticleSize = 50.0f;
|
||||
|
||||
// Get vertical FoV of the displayed overlay texture
|
||||
const float halfVerticalAngle = _oculusAngle / 2.0f;
|
||||
const float overlayAspectRatio = glWidget->width() / (float)glWidget->height();
|
||||
const float halfOverlayHeight = _distance * tan(halfVerticalAngle);
|
||||
|
||||
// Get horizontal angle and angle increment from vertical angle and aspect ratio
|
||||
const float horizontalAngle = halfVerticalAngle * 2.0f * overlayAspectRatio;
|
||||
const float halfHorizontalAngle = horizontalAngle / 2;
|
||||
|
||||
|
||||
float magnifyWidth = 80.0f;
|
||||
float magnifyHeight = 60.0f;
|
||||
|
||||
mouseX -= magnifyWidth / 2;
|
||||
mouseY -= magnifyHeight / 2;
|
||||
|
||||
//clamp the magnification
|
||||
if (mouseX < 0) {
|
||||
magnifyWidth += mouseX;
|
||||
mouseX = 0;
|
||||
} else if (mouseX + magnifyWidth > widgetWidth) {
|
||||
magnifyWidth = widgetWidth - mouseX;
|
||||
}
|
||||
if (mouseY < 0) {
|
||||
magnifyHeight += mouseY;
|
||||
mouseY = 0;
|
||||
} else if (mouseY + magnifyHeight > widgetHeight) {
|
||||
magnifyHeight = widgetHeight - mouseY;
|
||||
}
|
||||
|
||||
const float halfMagnifyHeight = magnifyHeight / 2.0f;
|
||||
|
||||
float newWidth = magnifyWidth * magnification;
|
||||
float newHeight = magnifyHeight * magnification;
|
||||
|
||||
// Magnification Texture Coordinates
|
||||
float magnifyULeft = mouseX / (float)widgetWidth;
|
||||
float magnifyURight = (mouseX + magnifyWidth) / (float)widgetWidth;
|
||||
float magnifyVBottom = 1.0f - mouseY / (float)widgetHeight;
|
||||
float magnifyVTop = 1.0f - (mouseY + magnifyHeight) / (float)widgetHeight;
|
||||
|
||||
// Coordinates of magnification overlay
|
||||
float newMouseX = (mouseX + magnifyWidth / 2) - newWidth / 2.0f;
|
||||
float newMouseY = (mouseY + magnifyHeight / 2) + newHeight / 2.0f;
|
||||
|
||||
// Get angle on the UI
|
||||
float leftAngle = (newMouseX / (float)widgetWidth) * horizontalAngle - halfHorizontalAngle;
|
||||
float rightAngle = ((newMouseX + newWidth) / (float)widgetWidth) * horizontalAngle - halfHorizontalAngle;
|
||||
|
||||
float bottomAngle = (newMouseY / (float)widgetHeight) * _oculusAngle - halfVerticalAngle;
|
||||
float topAngle = ((newMouseY - newHeight) / (float)widgetHeight) * _oculusAngle - halfVerticalAngle;
|
||||
|
||||
// Get position on hemisphere using angle
|
||||
if (_uiType == HEMISPHERE) {
|
||||
glBindTexture(GL_TEXTURE_2D, _crosshairTexture);
|
||||
glDisable(GL_DEPTH_TEST);
|
||||
|
||||
for (int i = 0; i < _numMagnifiers; i++) {
|
||||
|
||||
float mouseX = (float)_mouseX[i];
|
||||
float mouseY = (float)_mouseY[i];
|
||||
mouseX -= reticleSize / 2;
|
||||
mouseY += reticleSize / 2;
|
||||
|
||||
//Get new UV coordinates from our magnification window
|
||||
float newULeft = newMouseX / widgetWidth;
|
||||
float newURight = (newMouseX + newWidth) / widgetWidth;
|
||||
float newVBottom = 1.0 - newMouseY / widgetHeight;
|
||||
float newVTop = 1.0 - (newMouseY - newHeight) / widgetHeight;
|
||||
float newULeft = mouseX / widgetWidth;
|
||||
float newURight = (mouseX + reticleSize) / widgetWidth;
|
||||
float newVBottom = 1.0 - mouseY / widgetHeight;
|
||||
float newVTop = 1.0 - (mouseY - reticleSize) / widgetHeight;
|
||||
|
||||
// Project our position onto the hemisphere using the UV coordinates
|
||||
float lX = sin((newULeft - 0.5f) * _textureFov);
|
||||
|
@ -484,40 +360,86 @@ void ApplicationOverlay::renderMagnifier(int mouseX, int mouseY)
|
|||
|
||||
glBegin(GL_QUADS);
|
||||
|
||||
glTexCoord2f(magnifyULeft, magnifyVBottom); glVertex3f(lX, tY, -tlZ);
|
||||
glTexCoord2f(magnifyURight, magnifyVBottom); glVertex3f(rX, tY, -trZ);
|
||||
glTexCoord2f(magnifyURight, magnifyVTop); glVertex3f(rX, bY, -brZ);
|
||||
glTexCoord2f(magnifyULeft, magnifyVTop); glVertex3f(lX, bY, -blZ);
|
||||
|
||||
glEnd();
|
||||
|
||||
} else {
|
||||
leftX = sin(leftAngle) * _distance;
|
||||
rightX = sin(rightAngle) * _distance;
|
||||
leftZ = -cos(leftAngle) * _distance;
|
||||
rightZ = -cos(rightAngle) * _distance;
|
||||
if (_uiType == CURVED_SEMICIRCLE) {
|
||||
topZ = -cos(topAngle * overlayAspectRatio) * _distance;
|
||||
bottomZ = -cos(bottomAngle * overlayAspectRatio) * _distance;
|
||||
} else {
|
||||
// Dont want to use topZ or bottomZ for SEMICIRCLE
|
||||
topZ = -99999;
|
||||
bottomZ = -99999;
|
||||
}
|
||||
|
||||
float bottomY = (1.0 - newMouseY / (float)widgetHeight) * halfOverlayHeight * 2.0f - halfOverlayHeight;
|
||||
float topY = bottomY + (newHeight / widgetHeight) * halfOverlayHeight * 2;
|
||||
|
||||
//TODO: Remove immediate mode in favor of VBO
|
||||
glBegin(GL_QUADS);
|
||||
|
||||
glTexCoord2f(magnifyULeft, magnifyVBottom); glVertex3f(leftX, topY, max(topZ, leftZ));
|
||||
glTexCoord2f(magnifyURight, magnifyVBottom); glVertex3f(rightX, topY, max(topZ, rightZ));
|
||||
glTexCoord2f(magnifyURight, magnifyVTop); glVertex3f(rightX, bottomY, max(bottomZ, rightZ));
|
||||
glTexCoord2f(magnifyULeft, magnifyVTop); glVertex3f(leftX, bottomY, max(bottomZ, leftZ));
|
||||
glColor3f(RETICLE_COLOR[0], RETICLE_COLOR[1], RETICLE_COLOR[2]);
|
||||
|
||||
glTexCoord2f(0.0f, 0.0f); glVertex3f(lX, tY, -tlZ);
|
||||
glTexCoord2f(1.0f, 0.0f); glVertex3f(rX, tY, -trZ);
|
||||
glTexCoord2f(1.0f, 1.0f); glVertex3f(rX, bY, -brZ);
|
||||
glTexCoord2f(0.0f, 1.0f); glVertex3f(lX, bY, -blZ);
|
||||
|
||||
glEnd();
|
||||
|
||||
}
|
||||
glEnable(GL_DEPTH_TEST);
|
||||
}
|
||||
|
||||
//Renders a small magnification of the currently bound texture at the coordinates
|
||||
void ApplicationOverlay::renderMagnifier(int mouseX, int mouseY)
|
||||
{
|
||||
Application* application = Application::getInstance();
|
||||
QGLWidget* glWidget = application->getGLWidget();
|
||||
|
||||
|
||||
const int widgetWidth = glWidget->width();
|
||||
const int widgetHeight = glWidget->height();
|
||||
const float magnification = 4.0f;
|
||||
|
||||
float magnifyWidth = 80.0f;
|
||||
float magnifyHeight = 60.0f;
|
||||
|
||||
mouseX -= magnifyWidth / 2;
|
||||
mouseY -= magnifyHeight / 2;
|
||||
|
||||
float newWidth = magnifyWidth * magnification;
|
||||
float newHeight = magnifyHeight * magnification;
|
||||
|
||||
// Magnification Texture Coordinates
|
||||
float magnifyULeft = mouseX / (float)widgetWidth;
|
||||
float magnifyURight = (mouseX + magnifyWidth) / (float)widgetWidth;
|
||||
float magnifyVBottom = 1.0f - mouseY / (float)widgetHeight;
|
||||
float magnifyVTop = 1.0f - (mouseY + magnifyHeight) / (float)widgetHeight;
|
||||
|
||||
// Coordinates of magnification overlay
|
||||
float newMouseX = (mouseX + magnifyWidth / 2) - newWidth / 2.0f;
|
||||
float newMouseY = (mouseY + magnifyHeight / 2) + newHeight / 2.0f;
|
||||
|
||||
// Get position on hemisphere using angle
|
||||
|
||||
//Get new UV coordinates from our magnification window
|
||||
float newULeft = newMouseX / widgetWidth;
|
||||
float newURight = (newMouseX + newWidth) / widgetWidth;
|
||||
float newVBottom = 1.0 - newMouseY / widgetHeight;
|
||||
float newVTop = 1.0 - (newMouseY - newHeight) / widgetHeight;
|
||||
|
||||
// Project our position onto the hemisphere using the UV coordinates
|
||||
float lX = sin((newULeft - 0.5f) * _textureFov);
|
||||
float rX = sin((newURight - 0.5f) * _textureFov);
|
||||
float bY = sin((newVBottom - 0.5f) * _textureFov);
|
||||
float tY = sin((newVTop - 0.5f) * _textureFov);
|
||||
|
||||
float dist;
|
||||
//Bottom Left
|
||||
dist = sqrt(lX * lX + bY * bY);
|
||||
float blZ = sqrt(1.0f - dist * dist);
|
||||
//Top Left
|
||||
dist = sqrt(lX * lX + tY * tY);
|
||||
float tlZ = sqrt(1.0f - dist * dist);
|
||||
//Bottom Right
|
||||
dist = sqrt(rX * rX + bY * bY);
|
||||
float brZ = sqrt(1.0f - dist * dist);
|
||||
//Top Right
|
||||
dist = sqrt(rX * rX + tY * tY);
|
||||
float trZ = sqrt(1.0f - dist * dist);
|
||||
|
||||
glBegin(GL_QUADS);
|
||||
|
||||
glTexCoord2f(magnifyULeft, magnifyVBottom); glVertex3f(lX, tY, -tlZ);
|
||||
glTexCoord2f(magnifyURight, magnifyVBottom); glVertex3f(rX, tY, -trZ);
|
||||
glTexCoord2f(magnifyURight, magnifyVTop); glVertex3f(rX, bY, -brZ);
|
||||
glTexCoord2f(magnifyULeft, magnifyVTop); glVertex3f(lX, bY, -blZ);
|
||||
|
||||
glEnd();
|
||||
|
||||
}
|
||||
|
||||
void ApplicationOverlay::renderAudioMeter() {
|
||||
|
@ -696,8 +618,11 @@ void ApplicationOverlay::renderTexturedHemisphere() {
|
|||
static VerticesIndices vbo(0, 0);
|
||||
int vertices = slices * (stacks - 1) + 1;
|
||||
int indices = slices * 2 * 3 * (stacks - 2) + slices * 3;
|
||||
//We only generate the VBO once
|
||||
if (vbo.first == 0) {
|
||||
|
||||
static float oldTextureFOV = _textureFov;
|
||||
//We only generate the VBO when the _textureFov changes
|
||||
if (vbo.first == 0 || oldTextureFOV != _textureFov) {
|
||||
oldTextureFOV = _textureFov;
|
||||
TextureVertex* vertexData = new TextureVertex[vertices];
|
||||
TextureVertex* vertex = vertexData;
|
||||
for (int i = 0; i < stacks - 1; i++) {
|
||||
|
@ -722,7 +647,9 @@ void ApplicationOverlay::renderTexturedHemisphere() {
|
|||
vertex->uv.y = 0.5f;
|
||||
vertex++;
|
||||
|
||||
glGenBuffers(1, &vbo.first);
|
||||
if (vbo.first == 0){
|
||||
glGenBuffers(1, &vbo.first);
|
||||
}
|
||||
glBindBuffer(GL_ARRAY_BUFFER, vbo.first);
|
||||
const int BYTES_PER_VERTEX = sizeof(TextureVertex);
|
||||
glBufferData(GL_ARRAY_BUFFER, vertices * BYTES_PER_VERTEX, vertexData, GL_STATIC_DRAW);
|
||||
|
|
|
@ -19,8 +19,6 @@ class QOpenGLFramebufferObject;
|
|||
class ApplicationOverlay {
|
||||
public:
|
||||
|
||||
enum UIType { HEMISPHERE, SEMICIRCLE, CURVED_SEMICIRCLE };
|
||||
|
||||
ApplicationOverlay();
|
||||
~ApplicationOverlay();
|
||||
|
||||
|
@ -31,12 +29,7 @@ public:
|
|||
|
||||
// Getters
|
||||
QOpenGLFramebufferObject* getFramebufferObject();
|
||||
float getOculusAngle() const { return _oculusAngle; }
|
||||
|
||||
// Setters
|
||||
void setOculusAngle(float oculusAngle) { _oculusAngle = oculusAngle; }
|
||||
void setUIType(UIType uiType) { _uiType = uiType; }
|
||||
|
||||
|
||||
private:
|
||||
// Interleaved vertex data
|
||||
struct TextureVertex {
|
||||
|
@ -47,7 +40,8 @@ private:
|
|||
typedef QPair<GLuint, GLuint> VerticesIndices;
|
||||
|
||||
void renderPointers();
|
||||
void renderControllerPointer();
|
||||
void renderControllerPointers();
|
||||
void renderControllerPointersOculus();
|
||||
void renderMagnifier(int mouseX, int mouseY);
|
||||
void renderAudioMeter();
|
||||
void renderStatsAndLogs();
|
||||
|
@ -58,10 +52,11 @@ private:
|
|||
float _oculusAngle;
|
||||
float _distance;
|
||||
float _textureFov;
|
||||
UIType _uiType;
|
||||
int _mouseX[2];
|
||||
int _mouseY[2];
|
||||
int _numMagnifiers;
|
||||
|
||||
GLuint _crosshairTexture;
|
||||
};
|
||||
|
||||
#endif // hifi_ApplicationOverlay_h
|
|
@ -137,6 +137,13 @@ void PreferencesDialog::loadPreferences() {
|
|||
ui.maxVoxelsSpin->setValue(menuInstance->getMaxVoxels());
|
||||
|
||||
ui.maxVoxelsPPSSpin->setValue(menuInstance->getMaxVoxelPacketsPerSecond());
|
||||
|
||||
ui.oculusUIAngularSizeSpin->setValue(menuInstance->getOculusUIAngularSize());
|
||||
|
||||
ui.sixenseReticleMoveSpeedSpin->setValue(menuInstance->getSixenseReticleMoveSpeed());
|
||||
|
||||
ui.invertSixenseButtonsCheckBox->setChecked(menuInstance->getInvertSixenseButtons());
|
||||
|
||||
}
|
||||
|
||||
void PreferencesDialog::savePreferences() {
|
||||
|
@ -189,6 +196,12 @@ void PreferencesDialog::savePreferences() {
|
|||
(float)ui.faceshiftEyeDeflectionSider->maximum());
|
||||
Menu::getInstance()->setMaxVoxelPacketsPerSecond(ui.maxVoxelsPPSSpin->value());
|
||||
|
||||
Menu::getInstance()->setOculusUIAngularSize(ui.oculusUIAngularSizeSpin->value());
|
||||
|
||||
Menu::getInstance()->setSixenseReticleMoveSpeed(ui.sixenseReticleMoveSpeedSpin->value());
|
||||
|
||||
Menu::getInstance()->setInvertSixenseButtons(ui.invertSixenseButtonsCheckBox->isChecked());
|
||||
|
||||
Menu::getInstance()->setAudioJitterBufferSamples(ui.audioJitterSpin->value());
|
||||
Application::getInstance()->getAudio()->setJitterBufferSamples(ui.audioJitterSpin->value());
|
||||
|
||||
|
|
|
@ -154,9 +154,9 @@ color: #0e7077</string>
|
|||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>-204</y>
|
||||
<width>494</width>
|
||||
<height>1091</height>
|
||||
<y>-1002</y>
|
||||
<width>477</width>
|
||||
<height>1386</height>
|
||||
</rect>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_2">
|
||||
|
@ -1605,6 +1605,331 @@ padding: 10px;margin-top:10px</string>
|
|||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="oculusRiftTitleLabel">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>0</width>
|
||||
<height>40</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="font">
|
||||
<font>
|
||||
<family>Arial</family>
|
||||
<pointsize>20</pointsize>
|
||||
<weight>50</weight>
|
||||
<bold>false</bold>
|
||||
</font>
|
||||
</property>
|
||||
<property name="styleSheet">
|
||||
<string notr="true">color: #0e7077</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Oculus Rift</string>
|
||||
</property>
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignBottom|Qt::AlignLeading|Qt::AlignLeft</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_15">
|
||||
<property name="spacing">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="topMargin">
|
||||
<number>10</number>
|
||||
</property>
|
||||
<property name="rightMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="bottomMargin">
|
||||
<number>10</number>
|
||||
</property>
|
||||
<item>
|
||||
<widget class="QLabel" name="label_12">
|
||||
<property name="font">
|
||||
<font>
|
||||
<family>Arial</family>
|
||||
</font>
|
||||
</property>
|
||||
<property name="styleSheet">
|
||||
<string notr="true">color: rgb(51, 51, 51)</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>User Interface Angular Size</string>
|
||||
</property>
|
||||
<property name="indent">
|
||||
<number>15</number>
|
||||
</property>
|
||||
<property name="buddy">
|
||||
<cstring>maxVoxelsSpin</cstring>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="horizontalSpacer_15">
|
||||
<property name="font">
|
||||
<font>
|
||||
<family>Arial</family>
|
||||
</font>
|
||||
</property>
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>40</width>
|
||||
<height>20</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QSpinBox" name="oculusUIAngularSizeSpin">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Minimum" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>125</width>
|
||||
<height>36</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="font">
|
||||
<font>
|
||||
<family>Arial</family>
|
||||
</font>
|
||||
</property>
|
||||
<property name="minimum">
|
||||
<number>30</number>
|
||||
</property>
|
||||
<property name="maximum">
|
||||
<number>160</number>
|
||||
</property>
|
||||
<property name="singleStep">
|
||||
<number>1</number>
|
||||
</property>
|
||||
<property name="value">
|
||||
<number>72</number>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="sixenseControllersTitleLabel">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>0</width>
|
||||
<height>40</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="font">
|
||||
<font>
|
||||
<family>Arial</family>
|
||||
<pointsize>20</pointsize>
|
||||
<weight>50</weight>
|
||||
<bold>false</bold>
|
||||
</font>
|
||||
</property>
|
||||
<property name="styleSheet">
|
||||
<string notr="true">color: #0e7077</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Sixense Controllers</string>
|
||||
</property>
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignBottom|Qt::AlignLeading|Qt::AlignLeft</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_17">
|
||||
<property name="spacing">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="topMargin">
|
||||
<number>10</number>
|
||||
</property>
|
||||
<property name="rightMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="bottomMargin">
|
||||
<number>10</number>
|
||||
</property>
|
||||
<item>
|
||||
<widget class="QLabel" name="label_14">
|
||||
<property name="font">
|
||||
<font>
|
||||
<family>Arial</family>
|
||||
</font>
|
||||
</property>
|
||||
<property name="styleSheet">
|
||||
<string notr="true">color: rgb(51, 51, 51)</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Invert Mouse Buttons</string>
|
||||
</property>
|
||||
<property name="indent">
|
||||
<number>15</number>
|
||||
</property>
|
||||
<property name="buddy">
|
||||
<cstring>maxVoxelsSpin</cstring>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="horizontalSpacer_17">
|
||||
<property name="font">
|
||||
<font>
|
||||
<family>Arial</family>
|
||||
</font>
|
||||
</property>
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>40</width>
|
||||
<height>20</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QCheckBox" name="invertSixenseButtonsCheckBox">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Minimum" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>32</width>
|
||||
<height>0</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="baseSize">
|
||||
<size>
|
||||
<width>0</width>
|
||||
<height>0</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string/>
|
||||
</property>
|
||||
<property name="iconSize">
|
||||
<size>
|
||||
<width>32</width>
|
||||
<height>32</height>
|
||||
</size>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_16">
|
||||
<property name="spacing">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="topMargin">
|
||||
<number>10</number>
|
||||
</property>
|
||||
<property name="rightMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="bottomMargin">
|
||||
<number>10</number>
|
||||
</property>
|
||||
<item>
|
||||
<widget class="QLabel" name="label_13">
|
||||
<property name="font">
|
||||
<font>
|
||||
<family>Arial</family>
|
||||
</font>
|
||||
</property>
|
||||
<property name="styleSheet">
|
||||
<string notr="true">color: rgb(51, 51, 51)</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Reticle Movement Speed</string>
|
||||
</property>
|
||||
<property name="indent">
|
||||
<number>15</number>
|
||||
</property>
|
||||
<property name="buddy">
|
||||
<cstring>maxVoxelsSpin</cstring>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="horizontalSpacer_16">
|
||||
<property name="font">
|
||||
<font>
|
||||
<family>Arial</family>
|
||||
</font>
|
||||
</property>
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>40</width>
|
||||
<height>20</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QSpinBox" name="sixenseReticleMoveSpeedSpin">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Minimum" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>125</width>
|
||||
<height>36</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="font">
|
||||
<font>
|
||||
<family>Arial</family>
|
||||
</font>
|
||||
</property>
|
||||
<property name="maximum">
|
||||
<number>100</number>
|
||||
</property>
|
||||
<property name="singleStep">
|
||||
<number>1</number>
|
||||
</property>
|
||||
<property name="value">
|
||||
<number>50</number>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</widget>
|
||||
|
|
|
@ -258,7 +258,7 @@ void Sound::interpretAsWav(const QByteArray& inputAudioByteArray, QByteArray& ou
|
|||
// Now pull out the data
|
||||
quint32 outputAudioByteArraySize = qFromLittleEndian<quint32>(dataHeader.descriptor.size);
|
||||
outputAudioByteArray.resize(outputAudioByteArraySize);
|
||||
if (waveStream.readRawData(outputAudioByteArray.data(), outputAudioByteArraySize) != outputAudioByteArraySize) {
|
||||
if (waveStream.readRawData(outputAudioByteArray.data(), outputAudioByteArraySize) != (int)outputAudioByteArraySize) {
|
||||
qDebug() << "Error reading WAV file";
|
||||
}
|
||||
|
||||
|
|
|
@ -55,13 +55,13 @@ AccountManager::AccountManager() :
|
|||
{
|
||||
qRegisterMetaType<OAuthAccessToken>("OAuthAccessToken");
|
||||
qRegisterMetaTypeStreamOperators<OAuthAccessToken>("OAuthAccessToken");
|
||||
|
||||
|
||||
qRegisterMetaType<DataServerAccountInfo>("DataServerAccountInfo");
|
||||
qRegisterMetaTypeStreamOperators<DataServerAccountInfo>("DataServerAccountInfo");
|
||||
|
||||
|
||||
qRegisterMetaType<QNetworkAccessManager::Operation>("QNetworkAccessManager::Operation");
|
||||
qRegisterMetaType<JSONCallbackParameters>("JSONCallbackParameters");
|
||||
|
||||
|
||||
connect(&_accountInfo, &DataServerAccountInfo::balanceChanged, this, &AccountManager::accountInfoBalanceChanged);
|
||||
}
|
||||
|
||||
|
@ -70,18 +70,18 @@ const QString DOUBLE_SLASH_SUBSTITUTE = "slashslash";
|
|||
void AccountManager::logout() {
|
||||
// a logout means we want to delete the DataServerAccountInfo we currently have for this URL, in-memory and in file
|
||||
_accountInfo = DataServerAccountInfo();
|
||||
|
||||
|
||||
emit balanceChanged(0);
|
||||
connect(&_accountInfo, &DataServerAccountInfo::balanceChanged, this, &AccountManager::accountInfoBalanceChanged);
|
||||
|
||||
|
||||
QSettings settings;
|
||||
settings.beginGroup(ACCOUNTS_GROUP);
|
||||
|
||||
|
||||
QString keyURLString(_authURL.toString().replace("//", DOUBLE_SLASH_SUBSTITUTE));
|
||||
settings.remove(keyURLString);
|
||||
|
||||
|
||||
qDebug() << "Removed account info for" << _authURL << "from in-memory accounts and .ini file";
|
||||
|
||||
|
||||
emit logoutComplete();
|
||||
// the username has changed to blank
|
||||
emit usernameChanged(QString());
|
||||
|
@ -93,7 +93,7 @@ void AccountManager::updateBalance() {
|
|||
JSONCallbackParameters callbackParameters;
|
||||
callbackParameters.jsonCallbackReceiver = &_accountInfo;
|
||||
callbackParameters.jsonCallbackMethod = "setBalanceFromJSON";
|
||||
|
||||
|
||||
authenticatedRequest("/api/v1/wallets/mine", QNetworkAccessManager::GetOperation, callbackParameters);
|
||||
}
|
||||
}
|
||||
|
@ -105,28 +105,33 @@ void AccountManager::accountInfoBalanceChanged(qint64 newBalance) {
|
|||
void AccountManager::setAuthURL(const QUrl& authURL) {
|
||||
if (_authURL != authURL) {
|
||||
_authURL = authURL;
|
||||
|
||||
|
||||
qDebug() << "URL for node authentication has been changed to" << qPrintable(_authURL.toString());
|
||||
qDebug() << "Re-setting authentication flow.";
|
||||
|
||||
|
||||
// check if there are existing access tokens to load from settings
|
||||
QSettings settings;
|
||||
settings.beginGroup(ACCOUNTS_GROUP);
|
||||
|
||||
|
||||
foreach(const QString& key, settings.allKeys()) {
|
||||
// take a key copy to perform the double slash replacement
|
||||
QString keyCopy(key);
|
||||
QUrl keyURL(keyCopy.replace("slashslash", "//"));
|
||||
|
||||
|
||||
if (keyURL == _authURL) {
|
||||
// pull out the stored access token and store it in memory
|
||||
_accountInfo = settings.value(key).value<DataServerAccountInfo>();
|
||||
qDebug() << "Found a data-server access token for" << qPrintable(keyURL.toString());
|
||||
|
||||
emit accessTokenChanged();
|
||||
|
||||
// profile info isn't guaranteed to be saved too
|
||||
if (_accountInfo.hasProfile()) {
|
||||
emit profileChanged();
|
||||
} else {
|
||||
requestProfile();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// tell listeners that the auth endpoint has changed
|
||||
emit authEndpointChanged();
|
||||
}
|
||||
|
@ -147,36 +152,36 @@ void AccountManager::authenticatedRequest(const QString& path, QNetworkAccessMan
|
|||
void AccountManager::invokedRequest(const QString& path, QNetworkAccessManager::Operation operation,
|
||||
const JSONCallbackParameters& callbackParams,
|
||||
const QByteArray& dataByteArray, QHttpMultiPart* dataMultiPart) {
|
||||
|
||||
|
||||
if (!_networkAccessManager) {
|
||||
_networkAccessManager = new QNetworkAccessManager(this);
|
||||
}
|
||||
|
||||
|
||||
if (hasValidAccessToken()) {
|
||||
QNetworkRequest authenticatedRequest;
|
||||
|
||||
|
||||
QUrl requestURL = _authURL;
|
||||
|
||||
|
||||
if (path.startsWith("/")) {
|
||||
requestURL.setPath(path);
|
||||
} else {
|
||||
requestURL.setPath("/" + path);
|
||||
}
|
||||
|
||||
|
||||
requestURL.setQuery("access_token=" + _accountInfo.getAccessToken().token);
|
||||
|
||||
|
||||
authenticatedRequest.setUrl(requestURL);
|
||||
|
||||
|
||||
if (VERBOSE_HTTP_REQUEST_DEBUGGING) {
|
||||
qDebug() << "Making an authenticated request to" << qPrintable(requestURL.toString());
|
||||
|
||||
|
||||
if (!dataByteArray.isEmpty()) {
|
||||
qDebug() << "The POST/PUT body -" << QString(dataByteArray);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
QNetworkReply* networkReply = NULL;
|
||||
|
||||
|
||||
switch (operation) {
|
||||
case QNetworkAccessManager::GetOperation:
|
||||
networkReply = _networkAccessManager->get(authenticatedRequest);
|
||||
|
@ -198,24 +203,24 @@ void AccountManager::invokedRequest(const QString& path, QNetworkAccessManager::
|
|||
networkReply = _networkAccessManager->put(authenticatedRequest, dataByteArray);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
break;
|
||||
default:
|
||||
// other methods not yet handled
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
if (networkReply) {
|
||||
if (!callbackParams.isEmpty()) {
|
||||
// if we have information for a callback, insert the callbackParams into our local map
|
||||
_pendingCallbackMap.insert(networkReply, callbackParams);
|
||||
|
||||
|
||||
if (callbackParams.updateReciever && !callbackParams.updateSlot.isEmpty()) {
|
||||
callbackParams.updateReciever->connect(networkReply, SIGNAL(uploadProgress(qint64, qint64)),
|
||||
callbackParams.updateSlot.toStdString().c_str());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// if we ended up firing of a request, hook up to it now
|
||||
connect(networkReply, SIGNAL(finished()), SLOT(processReply()));
|
||||
}
|
||||
|
@ -224,7 +229,7 @@ void AccountManager::invokedRequest(const QString& path, QNetworkAccessManager::
|
|||
|
||||
void AccountManager::processReply() {
|
||||
QNetworkReply* requestReply = reinterpret_cast<QNetworkReply*>(sender());
|
||||
|
||||
|
||||
if (requestReply->error() == QNetworkReply::NoError) {
|
||||
passSuccessToCallback(requestReply);
|
||||
} else {
|
||||
|
@ -235,17 +240,17 @@ void AccountManager::processReply() {
|
|||
|
||||
void AccountManager::passSuccessToCallback(QNetworkReply* requestReply) {
|
||||
QJsonDocument jsonResponse = QJsonDocument::fromJson(requestReply->readAll());
|
||||
|
||||
|
||||
JSONCallbackParameters callbackParams = _pendingCallbackMap.value(requestReply);
|
||||
|
||||
|
||||
if (callbackParams.jsonCallbackReceiver) {
|
||||
// invoke the right method on the callback receiver
|
||||
QMetaObject::invokeMethod(callbackParams.jsonCallbackReceiver, qPrintable(callbackParams.jsonCallbackMethod),
|
||||
Q_ARG(const QJsonObject&, jsonResponse.object()));
|
||||
|
||||
|
||||
// remove the related reply-callback group from the map
|
||||
_pendingCallbackMap.remove(requestReply);
|
||||
|
||||
|
||||
} else {
|
||||
if (VERBOSE_HTTP_REQUEST_DEBUGGING) {
|
||||
qDebug() << "Received JSON response from data-server that has no matching callback.";
|
||||
|
@ -256,13 +261,13 @@ void AccountManager::passSuccessToCallback(QNetworkReply* requestReply) {
|
|||
|
||||
void AccountManager::passErrorToCallback(QNetworkReply* requestReply) {
|
||||
JSONCallbackParameters callbackParams = _pendingCallbackMap.value(requestReply);
|
||||
|
||||
|
||||
if (callbackParams.errorCallbackReceiver) {
|
||||
// invoke the right method on the callback receiver
|
||||
QMetaObject::invokeMethod(callbackParams.errorCallbackReceiver, qPrintable(callbackParams.errorCallbackMethod),
|
||||
Q_ARG(QNetworkReply::NetworkError, requestReply->error()),
|
||||
Q_ARG(const QString&, requestReply->errorString()));
|
||||
|
||||
|
||||
// remove the related reply-callback group from the map
|
||||
_pendingCallbackMap.remove(requestReply);
|
||||
} else {
|
||||
|
@ -274,12 +279,12 @@ void AccountManager::passErrorToCallback(QNetworkReply* requestReply) {
|
|||
}
|
||||
|
||||
bool AccountManager::hasValidAccessToken() {
|
||||
|
||||
|
||||
if (_accountInfo.getAccessToken().token.isEmpty() || _accountInfo.getAccessToken().isExpired()) {
|
||||
if (VERBOSE_HTTP_REQUEST_DEBUGGING) {
|
||||
qDebug() << "An access token is required for requests to" << qPrintable(_authURL.toString());
|
||||
}
|
||||
|
||||
|
||||
return false;
|
||||
} else {
|
||||
return true;
|
||||
|
@ -288,12 +293,12 @@ bool AccountManager::hasValidAccessToken() {
|
|||
|
||||
bool AccountManager::checkAndSignalForAccessToken() {
|
||||
bool hasToken = hasValidAccessToken();
|
||||
|
||||
|
||||
if (!hasToken) {
|
||||
// emit a signal so somebody can call back to us and request an access token given a username and password
|
||||
emit authRequired();
|
||||
}
|
||||
|
||||
|
||||
return hasToken;
|
||||
}
|
||||
|
||||
|
@ -304,36 +309,36 @@ void AccountManager::requestAccessToken(const QString& login, const QString& pas
|
|||
}
|
||||
|
||||
QNetworkRequest request;
|
||||
|
||||
|
||||
QUrl grantURL = _authURL;
|
||||
grantURL.setPath("/oauth/token");
|
||||
|
||||
|
||||
const QString ACCOUNT_MANAGER_REQUESTED_SCOPE = "owner";
|
||||
|
||||
|
||||
QByteArray postData;
|
||||
postData.append("grant_type=password&");
|
||||
postData.append("username=" + login + "&");
|
||||
postData.append("password=" + QUrl::toPercentEncoding(password) + "&");
|
||||
postData.append("scope=" + ACCOUNT_MANAGER_REQUESTED_SCOPE);
|
||||
|
||||
|
||||
request.setUrl(grantURL);
|
||||
request.setHeader(QNetworkRequest::ContentTypeHeader, "application/x-www-form-urlencoded");
|
||||
|
||||
|
||||
QNetworkReply* requestReply = _networkAccessManager->post(request, postData);
|
||||
connect(requestReply, &QNetworkReply::finished, this, &AccountManager::requestFinished);
|
||||
connect(requestReply, SIGNAL(error(QNetworkReply::NetworkError)), this, SLOT(requestError(QNetworkReply::NetworkError)));
|
||||
connect(requestReply, &QNetworkReply::finished, this, &AccountManager::requestAccessTokenFinished);
|
||||
connect(requestReply, SIGNAL(error(QNetworkReply::NetworkError)), this, SLOT(requestAccessTokenError(QNetworkReply::NetworkError)));
|
||||
}
|
||||
|
||||
|
||||
void AccountManager::requestFinished() {
|
||||
void AccountManager::requestAccessTokenFinished() {
|
||||
QNetworkReply* requestReply = reinterpret_cast<QNetworkReply*>(sender());
|
||||
|
||||
|
||||
QJsonDocument jsonResponse = QJsonDocument::fromJson(requestReply->readAll());
|
||||
const QJsonObject& rootObject = jsonResponse.object();
|
||||
|
||||
|
||||
if (!rootObject.contains("error")) {
|
||||
// construct an OAuthAccessToken from the json object
|
||||
|
||||
|
||||
if (!rootObject.contains("access_token") || !rootObject.contains("expires_in")
|
||||
|| !rootObject.contains("token_type")) {
|
||||
// TODO: error handling - malformed token response
|
||||
|
@ -342,23 +347,21 @@ void AccountManager::requestFinished() {
|
|||
// clear the path from the response URL so we have the right root URL for this access token
|
||||
QUrl rootURL = requestReply->url();
|
||||
rootURL.setPath("");
|
||||
|
||||
|
||||
qDebug() << "Storing an account with access-token for" << qPrintable(rootURL.toString());
|
||||
|
||||
_accountInfo = DataServerAccountInfo(rootObject);
|
||||
|
||||
|
||||
_accountInfo = DataServerAccountInfo();
|
||||
_accountInfo.setAccessTokenFromJSON(rootObject);
|
||||
|
||||
emit loginComplete(rootURL);
|
||||
// the username has changed to whatever came back
|
||||
emit usernameChanged(_accountInfo.getUsername());
|
||||
|
||||
// we have found or requested an access token
|
||||
emit accessTokenChanged();
|
||||
|
||||
|
||||
// store this access token into the local settings
|
||||
QSettings localSettings;
|
||||
localSettings.beginGroup(ACCOUNTS_GROUP);
|
||||
localSettings.setValue(rootURL.toString().replace("//", DOUBLE_SLASH_SUBSTITUTE),
|
||||
QVariant::fromValue(_accountInfo));
|
||||
|
||||
requestProfile();
|
||||
}
|
||||
} else {
|
||||
// TODO: error handling
|
||||
|
@ -367,7 +370,53 @@ void AccountManager::requestFinished() {
|
|||
}
|
||||
}
|
||||
|
||||
void AccountManager::requestError(QNetworkReply::NetworkError error) {
|
||||
void AccountManager::requestAccessTokenError(QNetworkReply::NetworkError error) {
|
||||
// TODO: error handling
|
||||
qDebug() << "AccountManager requestError - " << error;
|
||||
}
|
||||
|
||||
void AccountManager::requestProfile() {
|
||||
if (!_networkAccessManager) {
|
||||
_networkAccessManager = new QNetworkAccessManager(this);
|
||||
}
|
||||
|
||||
QUrl profileURL = _authURL;
|
||||
profileURL.setPath("/api/v1/users/profile");
|
||||
profileURL.setQuery("access_token=" + _accountInfo.getAccessToken().token);
|
||||
|
||||
QNetworkReply* profileReply = _networkAccessManager->get(QNetworkRequest(profileURL));
|
||||
connect(profileReply, &QNetworkReply::finished, this, &AccountManager::requestProfileFinished);
|
||||
connect(profileReply, SIGNAL(error(QNetworkReply::NetworkError)), this, SLOT(requestProfileError(QNetworkReply::NetworkError)));
|
||||
}
|
||||
|
||||
void AccountManager::requestProfileFinished() {
|
||||
QNetworkReply* profileReply = reinterpret_cast<QNetworkReply*>(sender());
|
||||
|
||||
QJsonDocument jsonResponse = QJsonDocument::fromJson(profileReply->readAll());
|
||||
const QJsonObject& rootObject = jsonResponse.object();
|
||||
|
||||
if (rootObject.contains("status") && rootObject["status"].toString() == "success") {
|
||||
_accountInfo.setProfileInfoFromJSON(rootObject);
|
||||
|
||||
emit profileChanged();
|
||||
|
||||
// the username has changed to whatever came back
|
||||
emit usernameChanged(_accountInfo.getUsername());
|
||||
|
||||
// store the whole profile into the local settings
|
||||
QUrl rootURL = profileReply->url();
|
||||
rootURL.setPath("");
|
||||
QSettings localSettings;
|
||||
localSettings.beginGroup(ACCOUNTS_GROUP);
|
||||
localSettings.setValue(rootURL.toString().replace("//", DOUBLE_SLASH_SUBSTITUTE),
|
||||
QVariant::fromValue(_accountInfo));
|
||||
} else {
|
||||
// TODO: error handling
|
||||
qDebug() << "Error in response for profile";
|
||||
}
|
||||
}
|
||||
|
||||
void AccountManager::requestProfileError(QNetworkReply::NetworkError error) {
|
||||
// TODO: error handling
|
||||
qDebug() << "AccountManager requestProfileError - " << error;
|
||||
}
|
||||
|
|
|
@ -23,9 +23,9 @@
|
|||
class JSONCallbackParameters {
|
||||
public:
|
||||
JSONCallbackParameters();
|
||||
|
||||
|
||||
bool isEmpty() const { return !jsonCallbackReceiver && !errorCallbackReceiver; }
|
||||
|
||||
|
||||
QObject* jsonCallbackReceiver;
|
||||
QString jsonCallbackMethod;
|
||||
QObject* errorCallbackReceiver;
|
||||
|
@ -38,30 +38,33 @@ class AccountManager : public QObject {
|
|||
Q_OBJECT
|
||||
public:
|
||||
static AccountManager& getInstance();
|
||||
|
||||
|
||||
void authenticatedRequest(const QString& path,
|
||||
QNetworkAccessManager::Operation operation = QNetworkAccessManager::GetOperation,
|
||||
const JSONCallbackParameters& callbackParams = JSONCallbackParameters(),
|
||||
const QByteArray& dataByteArray = QByteArray(),
|
||||
QHttpMultiPart* dataMultiPart = NULL);
|
||||
|
||||
|
||||
const QUrl& getAuthURL() const { return _authURL; }
|
||||
void setAuthURL(const QUrl& authURL);
|
||||
bool hasAuthEndpoint() { return !_authURL.isEmpty(); }
|
||||
|
||||
|
||||
bool isLoggedIn() { return !_authURL.isEmpty() && hasValidAccessToken(); }
|
||||
bool hasValidAccessToken();
|
||||
Q_INVOKABLE bool checkAndSignalForAccessToken();
|
||||
|
||||
|
||||
void requestAccessToken(const QString& login, const QString& password);
|
||||
|
||||
void requestProfile();
|
||||
|
||||
const DataServerAccountInfo& getAccountInfo() const { return _accountInfo; }
|
||||
|
||||
|
||||
void destroy() { delete _networkAccessManager; }
|
||||
|
||||
|
||||
public slots:
|
||||
void requestFinished();
|
||||
void requestError(QNetworkReply::NetworkError error);
|
||||
void requestAccessTokenFinished();
|
||||
void requestProfileFinished();
|
||||
void requestAccessTokenError(QNetworkReply::NetworkError error);
|
||||
void requestProfileError(QNetworkReply::NetworkError error);
|
||||
void logout();
|
||||
void updateBalance();
|
||||
void accountInfoBalanceChanged(qint64 newBalance);
|
||||
|
@ -69,7 +72,7 @@ signals:
|
|||
void authRequired();
|
||||
void authEndpointChanged();
|
||||
void usernameChanged(const QString& username);
|
||||
void accessTokenChanged();
|
||||
void profileChanged();
|
||||
void loginComplete(const QUrl& authURL);
|
||||
void loginFailed();
|
||||
void logoutComplete();
|
||||
|
@ -80,19 +83,19 @@ private:
|
|||
AccountManager();
|
||||
AccountManager(AccountManager const& other); // not implemented
|
||||
void operator=(AccountManager const& other); // not implemented
|
||||
|
||||
|
||||
void passSuccessToCallback(QNetworkReply* reply);
|
||||
void passErrorToCallback(QNetworkReply* reply);
|
||||
|
||||
|
||||
Q_INVOKABLE void invokedRequest(const QString& path, QNetworkAccessManager::Operation operation,
|
||||
const JSONCallbackParameters& callbackParams,
|
||||
const QByteArray& dataByteArray,
|
||||
QHttpMultiPart* dataMultiPart);
|
||||
|
||||
|
||||
QUrl _authURL;
|
||||
QNetworkAccessManager* _networkAccessManager;
|
||||
QMap<QNetworkReply*, JSONCallbackParameters> _pendingCallbackMap;
|
||||
|
||||
|
||||
DataServerAccountInfo _accountInfo;
|
||||
};
|
||||
|
||||
|
|
|
@ -21,20 +21,7 @@ DataServerAccountInfo::DataServerAccountInfo() :
|
|||
_balance(0),
|
||||
_hasBalance(false)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
DataServerAccountInfo::DataServerAccountInfo(const QJsonObject& jsonObject) :
|
||||
_accessToken(jsonObject),
|
||||
_username(),
|
||||
_xmppPassword(),
|
||||
_balance(0),
|
||||
_hasBalance(false)
|
||||
{
|
||||
QJsonObject userJSONObject = jsonObject["user"].toObject();
|
||||
setUsername(userJSONObject["username"].toString());
|
||||
setXMPPPassword(userJSONObject["xmpp_password"].toString());
|
||||
setDiscourseApiKey(userJSONObject["discourse_api_key"].toString());
|
||||
}
|
||||
|
||||
DataServerAccountInfo::DataServerAccountInfo(const DataServerAccountInfo& otherInfo) {
|
||||
|
@ -54,7 +41,7 @@ DataServerAccountInfo& DataServerAccountInfo::operator=(const DataServerAccountI
|
|||
|
||||
void DataServerAccountInfo::swap(DataServerAccountInfo& otherInfo) {
|
||||
using std::swap;
|
||||
|
||||
|
||||
swap(_accessToken, otherInfo._accessToken);
|
||||
swap(_username, otherInfo._username);
|
||||
swap(_xmppPassword, otherInfo._xmppPassword);
|
||||
|
@ -63,10 +50,14 @@ void DataServerAccountInfo::swap(DataServerAccountInfo& otherInfo) {
|
|||
swap(_hasBalance, otherInfo._hasBalance);
|
||||
}
|
||||
|
||||
void DataServerAccountInfo::setAccessTokenFromJSON(const QJsonObject& jsonObject) {
|
||||
_accessToken = OAuthAccessToken(jsonObject);
|
||||
}
|
||||
|
||||
void DataServerAccountInfo::setUsername(const QString& username) {
|
||||
if (_username != username) {
|
||||
_username = username;
|
||||
|
||||
|
||||
qDebug() << "Username changed to" << username;
|
||||
}
|
||||
}
|
||||
|
@ -87,18 +78,29 @@ void DataServerAccountInfo::setBalance(qint64 balance) {
|
|||
if (!_hasBalance || _balance != balance) {
|
||||
_balance = balance;
|
||||
_hasBalance = true;
|
||||
|
||||
|
||||
emit balanceChanged(_balance);
|
||||
}
|
||||
}
|
||||
|
||||
void DataServerAccountInfo::setBalanceFromJSON(const QJsonObject& jsonObject) {
|
||||
if (jsonObject["status"].toString() == "success") {
|
||||
qint64 balanceInSatoshis = jsonObject["data"].toObject()["wallet"].toObject()["balance"].toInt();
|
||||
qint64 balanceInSatoshis = jsonObject["data"].toObject()["wallet"].toObject()["balance"].toDouble();
|
||||
setBalance(balanceInSatoshis);
|
||||
}
|
||||
}
|
||||
|
||||
bool DataServerAccountInfo::hasProfile() const {
|
||||
return _username.length() > 0;
|
||||
}
|
||||
|
||||
void DataServerAccountInfo::setProfileInfoFromJSON(const QJsonObject& jsonObject) {
|
||||
QJsonObject user = jsonObject["data"].toObject()["user"].toObject();
|
||||
setUsername(user["username"].toString());
|
||||
setXMPPPassword(user["xmpp_password"].toString());
|
||||
setDiscourseApiKey(user["discourse_api_key"].toString());
|
||||
}
|
||||
|
||||
QDataStream& operator<<(QDataStream &out, const DataServerAccountInfo& info) {
|
||||
out << info._accessToken << info._username << info._xmppPassword << info._discourseApiKey;
|
||||
return out;
|
||||
|
|
|
@ -22,12 +22,12 @@ class DataServerAccountInfo : public QObject {
|
|||
Q_OBJECT
|
||||
public:
|
||||
DataServerAccountInfo();
|
||||
DataServerAccountInfo(const QJsonObject& jsonObject);
|
||||
DataServerAccountInfo(const DataServerAccountInfo& otherInfo);
|
||||
DataServerAccountInfo& operator=(const DataServerAccountInfo& otherInfo);
|
||||
|
||||
|
||||
const OAuthAccessToken& getAccessToken() const { return _accessToken; }
|
||||
|
||||
void setAccessTokenFromJSON(const QJsonObject& jsonObject);
|
||||
|
||||
const QString& getUsername() const { return _username; }
|
||||
void setUsername(const QString& username);
|
||||
|
||||
|
@ -36,20 +36,24 @@ public:
|
|||
|
||||
const QString& getDiscourseApiKey() const { return _discourseApiKey; }
|
||||
void setDiscourseApiKey(const QString& discourseApiKey);
|
||||
|
||||
|
||||
qint64 getBalance() const { return _balance; }
|
||||
void setBalance(qint64 balance);
|
||||
bool hasBalance() const { return _hasBalance; }
|
||||
void setHasBalance(bool hasBalance) { _hasBalance = hasBalance; }
|
||||
Q_INVOKABLE void setBalanceFromJSON(const QJsonObject& jsonObject);
|
||||
|
||||
bool hasProfile() const;
|
||||
|
||||
void setProfileInfoFromJSON(const QJsonObject& jsonObject);
|
||||
|
||||
friend QDataStream& operator<<(QDataStream &out, const DataServerAccountInfo& info);
|
||||
friend QDataStream& operator>>(QDataStream &in, DataServerAccountInfo& info);
|
||||
signals:
|
||||
qint64 balanceChanged(qint64 newBalance);
|
||||
private:
|
||||
void swap(DataServerAccountInfo& otherInfo);
|
||||
|
||||
|
||||
OAuthAccessToken _accessToken;
|
||||
QString _username;
|
||||
QString _xmppPassword;
|
||||
|
|
|
@ -180,6 +180,18 @@ static bool findIntersection(float origin, float direction, float corner, float
|
|||
return false;
|
||||
}
|
||||
|
||||
// finds the intersection between a ray and the inside facing plane on one axis
|
||||
static bool findInsideOutIntersection(float origin, float direction, float corner, float size, float& distance) {
|
||||
if (direction > EPSILON) {
|
||||
distance = -1.0f * (origin - (corner + size)) / direction;
|
||||
return true;
|
||||
} else if (direction < -EPSILON) {
|
||||
distance = -1.0f * (origin - corner) / direction;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool AABox::expandedIntersectsSegment(const glm::vec3& start, const glm::vec3& end, float expansion) const {
|
||||
// handle the trivial cases where the expanded box contains the start or end
|
||||
if (expandedContains(start, expansion) || expandedContains(end, expansion)) {
|
||||
|
@ -207,9 +219,34 @@ bool AABox::expandedIntersectsSegment(const glm::vec3& start, const glm::vec3& e
|
|||
bool AABox::findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, float& distance, BoxFace& face) const {
|
||||
// handle the trivial case where the box contains the origin
|
||||
if (contains(origin)) {
|
||||
// We still want to calculate the distance from the origin to the inside out plane
|
||||
float axisDistance;
|
||||
if ((findInsideOutIntersection(origin.x, direction.x, _corner.x, _scale.x, axisDistance) && axisDistance >= 0 &&
|
||||
isWithin(origin.y + axisDistance*direction.y, _corner.y, _scale.y) &&
|
||||
isWithin(origin.z + axisDistance*direction.z, _corner.z, _scale.z))) {
|
||||
distance = axisDistance;
|
||||
face = direction.x > 0 ? MAX_X_FACE : MIN_X_FACE;
|
||||
return true;
|
||||
}
|
||||
if ((findInsideOutIntersection(origin.y, direction.y, _corner.y, _scale.y, axisDistance) && axisDistance >= 0 &&
|
||||
isWithin(origin.x + axisDistance*direction.x, _corner.x, _scale.x) &&
|
||||
isWithin(origin.z + axisDistance*direction.z, _corner.z, _scale.z))) {
|
||||
distance = axisDistance;
|
||||
face = direction.y > 0 ? MAX_Y_FACE : MIN_Y_FACE;
|
||||
return true;
|
||||
}
|
||||
if ((findInsideOutIntersection(origin.z, direction.z, _corner.z, _scale.z, axisDistance) && axisDistance >= 0 &&
|
||||
isWithin(origin.y + axisDistance*direction.y, _corner.y, _scale.y) &&
|
||||
isWithin(origin.x + axisDistance*direction.x, _corner.x, _scale.x))) {
|
||||
distance = axisDistance;
|
||||
face = direction.z > 0 ? MAX_Z_FACE : MIN_Z_FACE;
|
||||
return true;
|
||||
}
|
||||
// This case is unexpected, but mimics the previous behavior for inside out intersections
|
||||
distance = 0;
|
||||
return true;
|
||||
}
|
||||
|
||||
// check each axis
|
||||
float axisDistance;
|
||||
if ((findIntersection(origin.x, direction.x, _corner.x, _scale.x, axisDistance) && axisDistance >= 0 &&
|
||||
|
|
|
@ -169,6 +169,18 @@ static bool findIntersection(float origin, float direction, float corner, float
|
|||
return false;
|
||||
}
|
||||
|
||||
// finds the intersection between a ray and the inside facing plane on one axis
|
||||
static bool findInsideOutIntersection(float origin, float direction, float corner, float size, float& distance) {
|
||||
if (direction > EPSILON) {
|
||||
distance = -1.0f * (origin - (corner + size)) / direction;
|
||||
return true;
|
||||
} else if (direction < -EPSILON) {
|
||||
distance = -1.0f * (origin - corner) / direction;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool AACube::expandedIntersectsSegment(const glm::vec3& start, const glm::vec3& end, float expansion) const {
|
||||
// handle the trivial cases where the expanded box contains the start or end
|
||||
if (expandedContains(start, expansion) || expandedContains(end, expansion)) {
|
||||
|
@ -196,9 +208,35 @@ bool AACube::expandedIntersectsSegment(const glm::vec3& start, const glm::vec3&
|
|||
bool AACube::findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, float& distance, BoxFace& face) const {
|
||||
// handle the trivial case where the box contains the origin
|
||||
if (contains(origin)) {
|
||||
|
||||
// We still want to calculate the distance from the origin to the inside out plane
|
||||
float axisDistance;
|
||||
if ((findInsideOutIntersection(origin.x, direction.x, _corner.x, _scale, axisDistance) && axisDistance >= 0 &&
|
||||
isWithin(origin.y + axisDistance*direction.y, _corner.y, _scale) &&
|
||||
isWithin(origin.z + axisDistance*direction.z, _corner.z, _scale))) {
|
||||
distance = axisDistance;
|
||||
face = direction.x > 0 ? MAX_X_FACE : MIN_X_FACE;
|
||||
return true;
|
||||
}
|
||||
if ((findInsideOutIntersection(origin.y, direction.y, _corner.y, _scale, axisDistance) && axisDistance >= 0 &&
|
||||
isWithin(origin.x + axisDistance*direction.x, _corner.x, _scale) &&
|
||||
isWithin(origin.z + axisDistance*direction.z, _corner.z, _scale))) {
|
||||
distance = axisDistance;
|
||||
face = direction.y > 0 ? MAX_Y_FACE : MIN_Y_FACE;
|
||||
return true;
|
||||
}
|
||||
if ((findInsideOutIntersection(origin.z, direction.z, _corner.z, _scale, axisDistance) && axisDistance >= 0 &&
|
||||
isWithin(origin.y + axisDistance*direction.y, _corner.y, _scale) &&
|
||||
isWithin(origin.x + axisDistance*direction.x, _corner.x, _scale))) {
|
||||
distance = axisDistance;
|
||||
face = direction.z > 0 ? MAX_Z_FACE : MIN_Z_FACE;
|
||||
return true;
|
||||
}
|
||||
// This case is unexpected, but mimics the previous behavior for inside out intersections
|
||||
distance = 0;
|
||||
return true;
|
||||
}
|
||||
|
||||
// check each axis
|
||||
float axisDistance;
|
||||
if ((findIntersection(origin.x, direction.x, _corner.x, _scale, axisDistance) && axisDistance >= 0 &&
|
||||
|
|
|
@ -147,7 +147,12 @@ ScriptEngine::ScriptEngine(const QUrl& scriptURL,
|
|||
QEventLoop loop;
|
||||
QObject::connect(reply, SIGNAL(finished()), &loop, SLOT(quit()));
|
||||
loop.exec();
|
||||
_scriptContents = reply->readAll();
|
||||
if (reply->error() == QNetworkReply::NoError && reply->attribute(QNetworkRequest::HttpStatusCodeAttribute) == 200) {
|
||||
_scriptContents = reply->readAll();
|
||||
} else {
|
||||
qDebug() << "ERROR Loading file:" << url.toString();
|
||||
emit errorMessage("ERROR Loading file:" + url.toString());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -13,6 +13,8 @@
|
|||
#include <glm/gtx/vector_angle.hpp>
|
||||
|
||||
#include "CapsuleShape.h"
|
||||
|
||||
#include "GeometryUtil.h"
|
||||
#include "SharedUtil.h"
|
||||
|
||||
|
||||
|
@ -84,3 +86,11 @@ void CapsuleShape::setEndPoints(const glm::vec3& startPoint, const glm::vec3& en
|
|||
updateBoundingRadius();
|
||||
}
|
||||
|
||||
bool CapsuleShape::findRayIntersection(const glm::vec3& rayStart, const glm::vec3& rayDirection, float& distance) const {
|
||||
glm::vec3 capsuleStart, capsuleEnd;
|
||||
getStartPoint(capsuleStart);
|
||||
getEndPoint(capsuleEnd);
|
||||
// NOTE: findRayCapsuleIntersection returns 'true' with distance = 0 when rayStart is inside capsule.
|
||||
// TODO: implement the raycast to return inside surface intersection for the internal rayStart.
|
||||
return findRayCapsuleIntersection(rayStart, rayDirection, capsuleStart, capsuleEnd, _radius, distance);
|
||||
}
|
||||
|
|
|
@ -39,6 +39,8 @@ public:
|
|||
void setRadiusAndHalfHeight(float radius, float height);
|
||||
void setEndPoints(const glm::vec3& startPoint, const glm::vec3& endPoint);
|
||||
|
||||
bool findRayIntersection(const glm::vec3& rayStart, const glm::vec3& rayDirection, float& distance) const;
|
||||
|
||||
protected:
|
||||
void updateBoundingRadius() { _boundingRadius = _radius + _halfHeight; }
|
||||
|
||||
|
|
|
@ -55,6 +55,9 @@ public:
|
|||
|
||||
void setShapes(QVector<ListShapeEntry>& shapes);
|
||||
|
||||
// TODO: either implement this or remove ListShape altogether
|
||||
bool findRayIntersection(const glm::vec3& rayStart, const glm::vec3& rayDirection, float& distance) const { return false; }
|
||||
|
||||
protected:
|
||||
void clear();
|
||||
void computeBoundingRadius();
|
||||
|
|
|
@ -30,7 +30,28 @@ PlaneShape::PlaneShape(const glm::vec4& coefficients) :
|
|||
}
|
||||
}
|
||||
|
||||
glm::vec3 PlaneShape::getNormal() const {
|
||||
return _rotation * UNROTATED_NORMAL;
|
||||
}
|
||||
|
||||
glm::vec4 PlaneShape::getCoefficients() const {
|
||||
glm::vec3 normal = _rotation * UNROTATED_NORMAL;
|
||||
return glm::vec4(normal.x, normal.y, normal.z, -glm::dot(normal, _position));
|
||||
}
|
||||
|
||||
bool PlaneShape::findRayIntersection(const glm::vec3& rayStart, const glm::vec3& rayDirection, float& distance) const {
|
||||
glm::vec3 n = getNormal();
|
||||
float denominator = glm::dot(n, rayDirection);
|
||||
if (fabsf(denominator) < EPSILON) {
|
||||
// line is parallel to plane
|
||||
return glm::dot(_position - rayStart, n) < EPSILON;
|
||||
} else {
|
||||
float d = glm::dot(_position - rayStart, n) / denominator;
|
||||
if (d > 0.0f) {
|
||||
// ray points toward plane
|
||||
distance = d;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
|
|
@ -18,7 +18,10 @@ class PlaneShape : public Shape {
|
|||
public:
|
||||
PlaneShape(const glm::vec4& coefficients = glm::vec4(0.0f, 1.0f, 0.0f, 0.0f));
|
||||
|
||||
glm::vec3 getNormal() const;
|
||||
glm::vec4 getCoefficients() const;
|
||||
|
||||
bool findRayIntersection(const glm::vec3& rayStart, const glm::vec3& rayDirection, float& distance) const;
|
||||
};
|
||||
|
||||
#endif // hifi_PlaneShape_h
|
||||
|
|
|
@ -38,6 +38,8 @@ public:
|
|||
virtual void setPosition(const glm::vec3& position) { _position = position; }
|
||||
virtual void setRotation(const glm::quat& rotation) { _rotation = rotation; }
|
||||
|
||||
virtual bool findRayIntersection(const glm::vec3& rayStart, const glm::vec3& rayDirection, float& distance) const = 0;
|
||||
|
||||
protected:
|
||||
// these ctors are protected (used by derived classes only)
|
||||
Shape(Type type) : _type(type), _boundingRadius(0.f), _position(0.f), _rotation() {}
|
||||
|
|
|
@ -765,5 +765,24 @@ bool capsuleAACube(const CapsuleShape* capsuleA, const glm::vec3& cubeCenter, fl
|
|||
return sphereAACube(nearestApproach, capsuleA->getRadius(), cubeCenter, cubeSide, collisions);
|
||||
}
|
||||
|
||||
bool findRayIntersectionWithShapes(const QVector<Shape*> shapes, const glm::vec3& rayStart, const glm::vec3& rayDirection, float& minDistance) {
|
||||
float hitDistance = FLT_MAX;
|
||||
int numShapes = shapes.size();
|
||||
for (int i = 0; i < numShapes; ++i) {
|
||||
Shape* shape = shapes.at(i);
|
||||
if (shape) {
|
||||
float distance;
|
||||
if (shape->findRayIntersection(rayStart, rayDirection, distance)) {
|
||||
if (distance < hitDistance) {
|
||||
hitDistance = distance;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (hitDistance < FLT_MAX) {
|
||||
minDistance = hitDistance;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
} // namespace ShapeCollider
|
||||
|
|
|
@ -21,8 +21,8 @@
|
|||
|
||||
namespace ShapeCollider {
|
||||
|
||||
/// \param shapeA pointer to first shape
|
||||
/// \param shapeB pointer to second shape
|
||||
/// \param shapeA pointer to first shape (cannot be NULL)
|
||||
/// \param shapeB pointer to second shape (cannot be NULL)
|
||||
/// \param collisions[out] collision details
|
||||
/// \return true if shapes collide
|
||||
bool collideShapes(const Shape* shapeA, const Shape* shapeB, CollisionList& collisions);
|
||||
|
@ -33,123 +33,130 @@ namespace ShapeCollider {
|
|||
/// \return true if any shapes collide
|
||||
bool collideShapesCoarse(const QVector<const Shape*>& shapesA, const QVector<const Shape*>& shapesB, CollisionInfo& collision);
|
||||
|
||||
/// \param shapeA a pointer to a shape
|
||||
/// \param shapeA a pointer to a shape (cannot be NULL)
|
||||
/// \param cubeCenter center of cube
|
||||
/// \param cubeSide lenght of side of cube
|
||||
/// \param collisions[out] average collision details
|
||||
/// \return true if shapeA collides with axis aligned cube
|
||||
bool collideShapeWithAACube(const Shape* shapeA, const glm::vec3& cubeCenter, float cubeSide, CollisionList& collisions);
|
||||
|
||||
/// \param sphereA pointer to first shape
|
||||
/// \param sphereB pointer to second shape
|
||||
/// \param sphereA pointer to first shape (cannot be NULL)
|
||||
/// \param sphereB pointer to second shape (cannot be NULL)
|
||||
/// \param[out] collisions where to append collision details
|
||||
/// \return true if shapes collide
|
||||
bool sphereSphere(const SphereShape* sphereA, const SphereShape* sphereB, CollisionList& collisions);
|
||||
|
||||
/// \param sphereA pointer to first shape
|
||||
/// \param capsuleB pointer to second shape
|
||||
/// \param sphereA pointer to first shape (cannot be NULL)
|
||||
/// \param capsuleB pointer to second shape (cannot be NULL)
|
||||
/// \param[out] collisions where to append collision details
|
||||
/// \return true if shapes collide
|
||||
bool sphereCapsule(const SphereShape* sphereA, const CapsuleShape* capsuleB, CollisionList& collisions);
|
||||
|
||||
/// \param sphereA pointer to first shape
|
||||
/// \param planeB pointer to second shape
|
||||
/// \param sphereA pointer to first shape (cannot be NULL)
|
||||
/// \param planeB pointer to second shape (cannot be NULL)
|
||||
/// \param[out] collisions where to append collision details
|
||||
/// \return true if shapes collide
|
||||
bool spherePlane(const SphereShape* sphereA, const PlaneShape* planeB, CollisionList& collisions);
|
||||
|
||||
/// \param capsuleA pointer to first shape
|
||||
/// \param sphereB pointer to second shape
|
||||
/// \param capsuleA pointer to first shape (cannot be NULL)
|
||||
/// \param sphereB pointer to second shape (cannot be NULL)
|
||||
/// \param[out] collisions where to append collision details
|
||||
/// \return true if shapes collide
|
||||
bool capsuleSphere(const CapsuleShape* capsuleA, const SphereShape* sphereB, CollisionList& collisions);
|
||||
|
||||
/// \param capsuleA pointer to first shape
|
||||
/// \param capsuleB pointer to second shape
|
||||
/// \param capsuleA pointer to first shape (cannot be NULL)
|
||||
/// \param capsuleB pointer to second shape (cannot be NULL)
|
||||
/// \param[out] collisions where to append collision details
|
||||
/// \return true if shapes collide
|
||||
bool capsuleCapsule(const CapsuleShape* capsuleA, const CapsuleShape* capsuleB, CollisionList& collisions);
|
||||
|
||||
/// \param capsuleA pointer to first shape
|
||||
/// \param planeB pointer to second shape
|
||||
/// \param capsuleA pointer to first shape (cannot be NULL)
|
||||
/// \param planeB pointer to second shape (cannot be NULL)
|
||||
/// \param[out] collisions where to append collision details
|
||||
/// \return true if shapes collide
|
||||
bool capsulePlane(const CapsuleShape* capsuleA, const PlaneShape* planeB, CollisionList& collisions);
|
||||
|
||||
/// \param planeA pointer to first shape
|
||||
/// \param sphereB pointer to second shape
|
||||
/// \param planeA pointer to first shape (cannot be NULL)
|
||||
/// \param sphereB pointer to second shape (cannot be NULL)
|
||||
/// \param[out] collisions where to append collision details
|
||||
/// \return true if shapes collide
|
||||
bool planeSphere(const PlaneShape* planeA, const SphereShape* sphereB, CollisionList& collisions);
|
||||
|
||||
/// \param planeA pointer to first shape
|
||||
/// \param capsuleB pointer to second shape
|
||||
/// \param planeA pointer to first shape (cannot be NULL)
|
||||
/// \param capsuleB pointer to second shape (cannot be NULL)
|
||||
/// \param[out] collisions where to append collision details
|
||||
/// \return true if shapes collide
|
||||
bool planeCapsule(const PlaneShape* planeA, const CapsuleShape* capsuleB, CollisionList& collisions);
|
||||
|
||||
/// \param planeA pointer to first shape
|
||||
/// \param planeB pointer to second shape
|
||||
/// \param planeA pointer to first shape (cannot be NULL)
|
||||
/// \param planeB pointer to second shape (cannot be NULL)
|
||||
/// \param[out] collisions where to append collision details
|
||||
/// \return true if shapes collide
|
||||
bool planePlane(const PlaneShape* planeA, const PlaneShape* planeB, CollisionList& collisions);
|
||||
|
||||
/// \param sphereA pointer to first shape
|
||||
/// \param listB pointer to second shape
|
||||
/// \param sphereA pointer to first shape (cannot be NULL)
|
||||
/// \param listB pointer to second shape (cannot be NULL)
|
||||
/// \param[out] collisions where to append collision details
|
||||
/// \return true if shapes collide
|
||||
bool sphereList(const SphereShape* sphereA, const ListShape* listB, CollisionList& collisions);
|
||||
|
||||
/// \param capuleA pointer to first shape
|
||||
/// \param listB pointer to second shape
|
||||
/// \param capuleA pointer to first shape (cannot be NULL)
|
||||
/// \param listB pointer to second shape (cannot be NULL)
|
||||
/// \param[out] collisions where to append collision details
|
||||
/// \return true if shapes collide
|
||||
bool capsuleList(const CapsuleShape* capsuleA, const ListShape* listB, CollisionList& collisions);
|
||||
|
||||
/// \param planeA pointer to first shape
|
||||
/// \param listB pointer to second shape
|
||||
/// \param planeA pointer to first shape (cannot be NULL)
|
||||
/// \param listB pointer to second shape (cannot be NULL)
|
||||
/// \param[out] collisions where to append collision details
|
||||
/// \return true if shapes collide
|
||||
bool planeList(const PlaneShape* planeA, const ListShape* listB, CollisionList& collisions);
|
||||
|
||||
/// \param listA pointer to first shape
|
||||
/// \param sphereB pointer to second shape
|
||||
/// \param listA pointer to first shape (cannot be NULL)
|
||||
/// \param sphereB pointer to second shape (cannot be NULL)
|
||||
/// \param[out] collisions where to append collision details
|
||||
/// \return true if shapes collide
|
||||
bool listSphere(const ListShape* listA, const SphereShape* sphereB, CollisionList& collisions);
|
||||
|
||||
/// \param listA pointer to first shape
|
||||
/// \param capsuleB pointer to second shape
|
||||
/// \param listA pointer to first shape (cannot be NULL)
|
||||
/// \param capsuleB pointer to second shape (cannot be NULL)
|
||||
/// \param[out] collisions where to append collision details
|
||||
/// \return true if shapes collide
|
||||
bool listCapsule(const ListShape* listA, const CapsuleShape* capsuleB, CollisionList& collisions);
|
||||
|
||||
/// \param listA pointer to first shape
|
||||
/// \param planeB pointer to second shape
|
||||
/// \param listA pointer to first shape (cannot be NULL)
|
||||
/// \param planeB pointer to second shape (cannot be NULL)
|
||||
/// \param[out] collisions where to append collision details
|
||||
/// \return true if shapes collide
|
||||
bool listPlane(const ListShape* listA, const PlaneShape* planeB, CollisionList& collisions);
|
||||
|
||||
/// \param listA pointer to first shape
|
||||
/// \param capsuleB pointer to second shape
|
||||
/// \param listA pointer to first shape (cannot be NULL)
|
||||
/// \param capsuleB pointer to second shape (cannot be NULL)
|
||||
/// \param[out] collisions where to append collision details
|
||||
/// \return true if shapes collide
|
||||
bool listList(const ListShape* listA, const ListShape* listB, CollisionList& collisions);
|
||||
|
||||
/// \param sphereA pointer to sphere
|
||||
/// \param sphereA pointer to sphere (cannot be NULL)
|
||||
/// \param cubeCenter center of cube
|
||||
/// \param cubeSide lenght of side of cube
|
||||
/// \param[out] collisions where to append collision details
|
||||
/// \return true if sphereA collides with axis aligned cube
|
||||
bool sphereAACube(const SphereShape* sphereA, const glm::vec3& cubeCenter, float cubeSide, CollisionList& collisions);
|
||||
|
||||
/// \param capsuleA pointer to capsule
|
||||
/// \param capsuleA pointer to capsule (cannot be NULL)
|
||||
/// \param cubeCenter center of cube
|
||||
/// \param cubeSide lenght of side of cube
|
||||
/// \param[out] collisions where to append collision details
|
||||
/// \return true if capsuleA collides with axis aligned cube
|
||||
bool capsuleAACube(const CapsuleShape* capsuleA, const glm::vec3& cubeCenter, float cubeSide, CollisionList& collisions);
|
||||
|
||||
/// \param shapes list of pointers to shapes (shape pointers may be NULL)
|
||||
/// \param startPoint beginning of ray
|
||||
/// \param direction direction of ray
|
||||
/// \param minDistance[out] shortest distance to intersection of ray with a shapes
|
||||
/// \return true if ray hits any shape in shapes
|
||||
bool findRayIntersectionWithShapes(const QVector<Shape*> shapes, const glm::vec3& startPoint, const glm::vec3& direction, float& minDistance);
|
||||
|
||||
} // namespace ShapeCollider
|
||||
|
||||
#endif // hifi_ShapeCollider_h
|
||||
|
|
|
@ -26,6 +26,7 @@
|
|||
#include <QtCore/QDebug>
|
||||
#include <QDateTime>
|
||||
#include <QElapsedTimer>
|
||||
#include <QThread>
|
||||
|
||||
#include "OctalCode.h"
|
||||
#include "SharedUtil.h"
|
||||
|
@ -415,13 +416,17 @@ void printVoxelCode(unsigned char* voxelCode) {
|
|||
|
||||
#ifdef _WIN32
|
||||
void usleep(int waitTime) {
|
||||
__int64 time1 = 0, time2 = 0, sysFreq = 0;
|
||||
|
||||
QueryPerformanceCounter((LARGE_INTEGER *)&time1);
|
||||
QueryPerformanceFrequency((LARGE_INTEGER *)&sysFreq);
|
||||
do {
|
||||
QueryPerformanceCounter((LARGE_INTEGER *)&time2);
|
||||
} while( (time2 - time1) < waitTime);
|
||||
const quint64 BUSY_LOOP_USECS = 2000;
|
||||
quint64 compTime = waitTime + usecTimestampNow();
|
||||
quint64 compTimeSleep = compTime - BUSY_LOOP_USECS;
|
||||
while (true) {
|
||||
if (usecTimestampNow() < compTimeSleep) {
|
||||
QThread::msleep(1);
|
||||
}
|
||||
if (usecTimestampNow() >= compTime) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
|
|
44
libraries/shared/src/SphereShape.cpp
Normal file
44
libraries/shared/src/SphereShape.cpp
Normal file
|
@ -0,0 +1,44 @@
|
|||
//
|
||||
// SphereShape.cpp
|
||||
// libraries/shared/src
|
||||
//
|
||||
// Created by Andrew Meadows on 2014.06.17
|
||||
// Copyright 2014 High Fidelity, Inc.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
#include <glm/gtx/norm.hpp>
|
||||
|
||||
#include "SphereShape.h"
|
||||
|
||||
bool SphereShape::findRayIntersection(const glm::vec3& rayStart, const glm::vec3& rayDirection, float& distance) const {
|
||||
float r2 = _boundingRadius * _boundingRadius;
|
||||
|
||||
// compute closest approach (CA)
|
||||
float a = glm::dot(_position - rayStart, rayDirection); // a = distance from ray-start to CA
|
||||
float b2 = glm::distance2(_position, rayStart + a * rayDirection); // b2 = squared distance from sphere-center to CA
|
||||
if (b2 > r2) {
|
||||
// ray does not hit sphere
|
||||
return false;
|
||||
}
|
||||
float c = sqrtf(r2 - b2); // c = distance from CA to sphere surface along rayDirection
|
||||
float d2 = glm::distance2(rayStart, _position); // d2 = squared distance from sphere-center to ray-start
|
||||
if (a < 0.0f) {
|
||||
// ray points away from sphere-center
|
||||
if (d2 > r2) {
|
||||
// ray starts outside sphere
|
||||
return false;
|
||||
}
|
||||
// ray starts inside sphere
|
||||
distance = c + a;
|
||||
} else if (d2 > r2) {
|
||||
// ray starts outside sphere
|
||||
distance = a - c;
|
||||
} else {
|
||||
// ray starts inside sphere
|
||||
distance = a + c;
|
||||
}
|
||||
return true;
|
||||
}
|
|
@ -29,6 +29,8 @@ public:
|
|||
float getRadius() const { return _boundingRadius; }
|
||||
|
||||
void setRadius(float radius) { _boundingRadius = radius; }
|
||||
|
||||
bool findRayIntersection(const glm::vec3& rayStart, const glm::vec3& rayDirection, float& distance) const;
|
||||
};
|
||||
|
||||
#endif // hifi_SphereShape_h
|
||||
|
|
100
tests/octree/src/AABoxCubeTests.cpp
Normal file
100
tests/octree/src/AABoxCubeTests.cpp
Normal file
|
@ -0,0 +1,100 @@
|
|||
//
|
||||
// AABoxCubeTests.h
|
||||
// tests/octree/src
|
||||
//
|
||||
// Created by Brad Hefta-Gaub on 06/04/2014.
|
||||
// Copyright 2014 High Fidelity, Inc.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
#include <QDebug>
|
||||
|
||||
#include <AABox.h>
|
||||
#include <AACube.h>
|
||||
|
||||
#include "AABoxCubeTests.h"
|
||||
|
||||
void AABoxCubeTests::AABoxCubeTests() {
|
||||
qDebug() << "******************************************************************************************";
|
||||
qDebug() << "AABoxCubeTests::AABoxCubeTests()";
|
||||
|
||||
{
|
||||
qDebug() << "Test 1: AABox.findRayIntersection() inside out MIN_X_FACE";
|
||||
|
||||
glm::vec3 corner(0.0f, 0.0f, 0.0f);
|
||||
float size = 1.0f;
|
||||
|
||||
AABox box(corner, size);
|
||||
glm::vec3 origin(0.5f, 0.5f, 0.5f);
|
||||
glm::vec3 direction(-1.0f, 0.0f, 0.0f);
|
||||
float distance;
|
||||
BoxFace face;
|
||||
|
||||
bool intersects = box.findRayIntersection(origin, direction, distance, face);
|
||||
|
||||
if (intersects && distance == 0.5f && face == MIN_X_FACE) {
|
||||
qDebug() << "Test 1: PASSED";
|
||||
} else {
|
||||
qDebug() << "intersects=" << intersects << "expected=" << true;
|
||||
qDebug() << "distance=" << distance << "expected=" << 0.5f;
|
||||
qDebug() << "face=" << face << "expected=" << MIN_X_FACE;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
qDebug() << "Test 2: AABox.findRayIntersection() inside out MAX_X_FACE";
|
||||
|
||||
glm::vec3 corner(0.0f, 0.0f, 0.0f);
|
||||
float size = 1.0f;
|
||||
|
||||
AABox box(corner, size);
|
||||
glm::vec3 origin(0.5f, 0.5f, 0.5f);
|
||||
glm::vec3 direction(1.0f, 0.0f, 0.0f);
|
||||
float distance;
|
||||
BoxFace face;
|
||||
|
||||
bool intersects = box.findRayIntersection(origin, direction, distance, face);
|
||||
|
||||
if (intersects && distance == 0.5f && face == MAX_X_FACE) {
|
||||
qDebug() << "Test 2: PASSED";
|
||||
} else {
|
||||
qDebug() << "intersects=" << intersects << "expected=" << true;
|
||||
qDebug() << "distance=" << distance << "expected=" << 0.5f;
|
||||
qDebug() << "face=" << face << "expected=" << MAX_X_FACE;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
qDebug() << "Test 3: AABox.findRayIntersection() outside in";
|
||||
|
||||
glm::vec3 corner(0.5f, 0.0f, 0.0f);
|
||||
float size = 0.5f;
|
||||
|
||||
AABox box(corner, size);
|
||||
glm::vec3 origin(0.25f, 0.25f, 0.25f);
|
||||
glm::vec3 direction(1.0f, 0.0f, 0.0f);
|
||||
float distance;
|
||||
BoxFace face;
|
||||
|
||||
bool intersects = box.findRayIntersection(origin, direction, distance, face);
|
||||
|
||||
if (intersects && distance == 0.25f && face == MIN_X_FACE) {
|
||||
qDebug() << "Test 3: PASSED";
|
||||
} else {
|
||||
qDebug() << "intersects=" << intersects << "expected=" << true;
|
||||
qDebug() << "distance=" << distance << "expected=" << 0.5f;
|
||||
qDebug() << "face=" << face << "expected=" << MIN_X_FACE;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
qDebug() << "******************************************************************************************";
|
||||
}
|
||||
|
||||
void AABoxCubeTests::runAllTests() {
|
||||
AABoxCubeTests();
|
||||
}
|
20
tests/octree/src/AABoxCubeTests.h
Normal file
20
tests/octree/src/AABoxCubeTests.h
Normal file
|
@ -0,0 +1,20 @@
|
|||
//
|
||||
// AABoxCubeTests.h
|
||||
// tests/octree/src
|
||||
//
|
||||
// Created by Brad Hefta-Gaub on 06/04/2014.
|
||||
// Copyright 2014 High Fidelity, Inc.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
#ifndef hifi_AABoxCubeTests_h
|
||||
#define hifi_AABoxCubeTests_h
|
||||
|
||||
namespace AABoxCubeTests {
|
||||
void AABoxCubeTests();
|
||||
void runAllTests();
|
||||
}
|
||||
|
||||
#endif // hifi_AABoxCubeTests_h
|
|
@ -1,6 +1,6 @@
|
|||
//
|
||||
// OctreeTests.h
|
||||
// tests/physics/src
|
||||
// tests/octree/src
|
||||
//
|
||||
// Created by Brad Hefta-Gaub on 06/04/2014.
|
||||
// Copyright 2014 High Fidelity, Inc.
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
//
|
||||
// OctreeTests.h
|
||||
// tests/physics/src
|
||||
// tests/octree/src
|
||||
//
|
||||
// Created by Brad Hefta-Gaub on 06/04/2014.
|
||||
// Copyright 2014 High Fidelity, Inc.
|
||||
|
|
|
@ -9,8 +9,10 @@
|
|||
//
|
||||
|
||||
#include "OctreeTests.h"
|
||||
#include "AABoxCubeTests.h"
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
OctreeTests::runAllTests();
|
||||
AABoxCubeTests::runAllTests();
|
||||
return 0;
|
||||
}
|
||||
|
|
|
@ -11,6 +11,7 @@
|
|||
|
||||
//#include <stdio.h>
|
||||
#include <iostream>
|
||||
#include <math.h>
|
||||
|
||||
#include <glm/glm.hpp>
|
||||
#include <glm/gtx/quaternion.hpp>
|
||||
|
@ -897,6 +898,405 @@ void ShapeColliderTests::sphereMissesAACube() {
|
|||
}
|
||||
}
|
||||
|
||||
void ShapeColliderTests::rayHitsSphere() {
|
||||
float startDistance = 3.0f;
|
||||
glm::vec3 rayStart(-startDistance, 0.0f, 0.0f);
|
||||
glm::vec3 rayDirection(1.0f, 0.0f, 0.0f);
|
||||
|
||||
float radius = 1.0f;
|
||||
glm::vec3 center(0.0f);
|
||||
|
||||
SphereShape sphere(radius, center);
|
||||
|
||||
// very simple ray along xAxis
|
||||
{
|
||||
float distance = FLT_MAX;
|
||||
if (!sphere.findRayIntersection(rayStart, rayDirection, distance)) {
|
||||
std::cout << __FILE__ << ":" << __LINE__ << " ERROR: ray should intersect sphere" << std::endl;
|
||||
}
|
||||
|
||||
float expectedDistance = startDistance - radius;
|
||||
float relativeError = fabsf(distance - expectedDistance) / startDistance;
|
||||
if (relativeError > EPSILON) {
|
||||
std::cout << __FILE__ << ":" << __LINE__ << " ERROR: ray sphere intersection distance error = " << relativeError << std::endl;
|
||||
}
|
||||
}
|
||||
|
||||
// ray along a diagonal axis
|
||||
{
|
||||
rayStart = glm::vec3(startDistance, startDistance, 0.0f);
|
||||
rayDirection = - glm::normalize(rayStart);
|
||||
|
||||
float distance = FLT_MAX;
|
||||
if (!sphere.findRayIntersection(rayStart, rayDirection, distance)) {
|
||||
std::cout << __FILE__ << ":" << __LINE__ << " ERROR: ray should intersect sphere" << std::endl;
|
||||
}
|
||||
|
||||
float expectedDistance = SQUARE_ROOT_OF_2 * startDistance - radius;
|
||||
float relativeError = fabsf(distance - expectedDistance) / startDistance;
|
||||
if (relativeError > EPSILON) {
|
||||
std::cout << __FILE__ << ":" << __LINE__ << " ERROR: ray sphere intersection distance error = " << relativeError << std::endl;
|
||||
}
|
||||
}
|
||||
|
||||
// rotated and displaced ray and sphere
|
||||
{
|
||||
startDistance = 7.41f;
|
||||
radius = 3.917f;
|
||||
|
||||
glm::vec3 axis = glm::normalize(glm::vec3(1.0f, 2.0f, 3.0f));
|
||||
glm::quat rotation = glm::angleAxis(0.987654321f, axis);
|
||||
glm::vec3 translation(35.7f, 2.46f, -1.97f);
|
||||
|
||||
glm::vec3 unrotatedRayDirection(-1.0f, 0.0f, 0.0f);
|
||||
glm::vec3 untransformedRayStart(startDistance, 0.0f, 0.0f);
|
||||
|
||||
rayStart = rotation * (untransformedRayStart + translation);
|
||||
rayDirection = rotation * unrotatedRayDirection;
|
||||
|
||||
sphere.setRadius(radius);
|
||||
sphere.setPosition(rotation * translation);
|
||||
|
||||
float distance = FLT_MAX;
|
||||
if (!sphere.findRayIntersection(rayStart, rayDirection, distance)) {
|
||||
std::cout << __FILE__ << ":" << __LINE__ << " ERROR: ray should intersect sphere" << std::endl;
|
||||
}
|
||||
|
||||
float expectedDistance = startDistance - radius;
|
||||
float relativeError = fabsf(distance - expectedDistance) / startDistance;
|
||||
if (relativeError > EPSILON) {
|
||||
std::cout << __FILE__ << ":" << __LINE__ << " ERROR: ray sphere intersection distance error = " << relativeError << std::endl;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ShapeColliderTests::rayBarelyHitsSphere() {
|
||||
float radius = 1.0f;
|
||||
glm::vec3 center(0.0f);
|
||||
float delta = 2.0f * EPSILON;
|
||||
|
||||
float startDistance = 3.0f;
|
||||
glm::vec3 rayStart(-startDistance, radius - delta, 0.0f);
|
||||
glm::vec3 rayDirection(1.0f, 0.0f, 0.0f);
|
||||
|
||||
SphereShape sphere(radius, center);
|
||||
|
||||
// very simple ray along xAxis
|
||||
float distance = FLT_MAX;
|
||||
if (!sphere.findRayIntersection(rayStart, rayDirection, distance)) {
|
||||
std::cout << __FILE__ << ":" << __LINE__ << " ERROR: ray should just barely hit sphere" << std::endl;
|
||||
}
|
||||
|
||||
// translate and rotate the whole system...
|
||||
glm::vec3 axis = glm::normalize(glm::vec3(1.0f, 2.0f, 3.0f));
|
||||
glm::quat rotation = glm::angleAxis(0.987654321f, axis);
|
||||
glm::vec3 translation(35.7f, 2.46f, -1.97f);
|
||||
|
||||
rayStart = rotation * (rayStart + translation);
|
||||
rayDirection = rotation * rayDirection;
|
||||
sphere.setPosition(rotation * translation);
|
||||
|
||||
// ...and test again
|
||||
distance = FLT_MAX;
|
||||
if (!sphere.findRayIntersection(rayStart, rayDirection, distance)) {
|
||||
std::cout << __FILE__ << ":" << __LINE__ << " ERROR: ray should just barely hit sphere" << std::endl;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void ShapeColliderTests::rayBarelyMissesSphere() {
|
||||
// same as the barely-hits case, but this time we move the ray away from sphere
|
||||
float radius = 1.0f;
|
||||
glm::vec3 center(0.0f);
|
||||
float delta = 2.0f * EPSILON;
|
||||
|
||||
float startDistance = 3.0f;
|
||||
glm::vec3 rayStart(-startDistance, radius + delta, 0.0f);
|
||||
glm::vec3 rayDirection(1.0f, 0.0f, 0.0f);
|
||||
|
||||
SphereShape sphere(radius, center);
|
||||
|
||||
// very simple ray along xAxis
|
||||
float distance = FLT_MAX;
|
||||
if (sphere.findRayIntersection(rayStart, rayDirection, distance)) {
|
||||
std::cout << __FILE__ << ":" << __LINE__ << " ERROR: ray should just barely miss sphere" << std::endl;
|
||||
}
|
||||
if (distance != FLT_MAX) {
|
||||
std::cout << __FILE__ << ":" << __LINE__ << " ERROR: distance should be unchanged after intersection miss" << std::endl;
|
||||
}
|
||||
|
||||
// translate and rotate the whole system...
|
||||
glm::vec3 axis = glm::normalize(glm::vec3(1.0f, 2.0f, 3.0f));
|
||||
glm::quat rotation = glm::angleAxis(0.987654321f, axis);
|
||||
glm::vec3 translation(35.7f, 2.46f, -1.97f);
|
||||
|
||||
rayStart = rotation * (rayStart + translation);
|
||||
rayDirection = rotation * rayDirection;
|
||||
sphere.setPosition(rotation * translation);
|
||||
|
||||
// ...and test again
|
||||
distance = FLT_MAX;
|
||||
if (sphere.findRayIntersection(rayStart, rayDirection, distance)) {
|
||||
std::cout << __FILE__ << ":" << __LINE__ << " ERROR: ray should just barely miss sphere" << std::endl;
|
||||
}
|
||||
if (distance != FLT_MAX) {
|
||||
std::cout << __FILE__ << ":" << __LINE__ << " ERROR: distance should be unchanged after intersection miss" << std::endl;
|
||||
}
|
||||
}
|
||||
|
||||
void ShapeColliderTests::rayHitsCapsule() {
|
||||
float startDistance = 3.0f;
|
||||
float radius = 1.0f;
|
||||
float halfHeight = 2.0f;
|
||||
glm::vec3 center(0.0f);
|
||||
CapsuleShape capsule(radius, halfHeight);
|
||||
|
||||
{ // simple test along xAxis
|
||||
// toward capsule center
|
||||
glm::vec3 rayStart(startDistance, 0.0f, 0.0f);
|
||||
glm::vec3 rayDirection(-1.0f, 0.0f, 0.0f);
|
||||
float distance = FLT_MAX;
|
||||
if (!capsule.findRayIntersection(rayStart, rayDirection, distance)) {
|
||||
std::cout << __FILE__ << ":" << __LINE__ << " ERROR: ray should hit capsule" << std::endl;
|
||||
}
|
||||
float expectedDistance = startDistance - radius;
|
||||
float relativeError = fabsf(distance - expectedDistance) / startDistance;
|
||||
if (relativeError > EPSILON) {
|
||||
std::cout << __FILE__ << ":" << __LINE__ << " ERROR: ray capsule intersection distance error = " << relativeError << std::endl;
|
||||
}
|
||||
|
||||
// toward top of cylindrical wall
|
||||
rayStart.y = halfHeight;
|
||||
distance = FLT_MAX;
|
||||
if (!capsule.findRayIntersection(rayStart, rayDirection, distance)) {
|
||||
std::cout << __FILE__ << ":" << __LINE__ << " ERROR: ray should hit capsule" << std::endl;
|
||||
}
|
||||
relativeError = fabsf(distance - expectedDistance) / startDistance;
|
||||
if (relativeError > EPSILON) {
|
||||
std::cout << __FILE__ << ":" << __LINE__ << " ERROR: ray capsule intersection distance error = " << relativeError << std::endl;
|
||||
}
|
||||
|
||||
// toward top cap
|
||||
float delta = 2.0f * EPSILON;
|
||||
rayStart.y = halfHeight + delta;
|
||||
distance = FLT_MAX;
|
||||
if (!capsule.findRayIntersection(rayStart, rayDirection, distance)) {
|
||||
std::cout << __FILE__ << ":" << __LINE__ << " ERROR: ray should hit capsule" << std::endl;
|
||||
}
|
||||
relativeError = fabsf(distance - expectedDistance) / startDistance;
|
||||
if (relativeError > EPSILON) {
|
||||
std::cout << __FILE__ << ":" << __LINE__ << " ERROR: ray capsule intersection distance error = " << relativeError << std::endl;
|
||||
}
|
||||
|
||||
const float EDGE_CASE_SLOP_FACTOR = 20.0f;
|
||||
|
||||
// toward tip of top cap
|
||||
rayStart.y = halfHeight + radius - delta;
|
||||
distance = FLT_MAX;
|
||||
if (!capsule.findRayIntersection(rayStart, rayDirection, distance)) {
|
||||
std::cout << __FILE__ << ":" << __LINE__ << " ERROR: ray should hit capsule" << std::endl;
|
||||
}
|
||||
expectedDistance = startDistance - radius * sqrtf(2.0f * delta); // using small angle approximation of cosine
|
||||
relativeError = fabsf(distance - expectedDistance) / startDistance;
|
||||
// for edge cases we allow a LOT of error
|
||||
if (relativeError > EDGE_CASE_SLOP_FACTOR * EPSILON) {
|
||||
std::cout << __FILE__ << ":" << __LINE__ << " ERROR: ray capsule intersection distance error = " << relativeError << std::endl;
|
||||
}
|
||||
|
||||
// toward tip of bottom cap
|
||||
rayStart.y = - halfHeight - radius + delta;
|
||||
distance = FLT_MAX;
|
||||
if (!capsule.findRayIntersection(rayStart, rayDirection, distance)) {
|
||||
std::cout << __FILE__ << ":" << __LINE__ << " ERROR: ray should hit capsule" << std::endl;
|
||||
}
|
||||
expectedDistance = startDistance - radius * sqrtf(2.0f * delta); // using small angle approximation of cosine
|
||||
relativeError = fabsf(distance - expectedDistance) / startDistance;
|
||||
// for edge cases we allow a LOT of error
|
||||
if (relativeError > EDGE_CASE_SLOP_FACTOR * EPSILON) {
|
||||
std::cout << __FILE__ << ":" << __LINE__ << " ERROR: ray capsule intersection distance error = " << relativeError << std::endl;
|
||||
}
|
||||
|
||||
// toward edge of capsule cylindrical face
|
||||
rayStart.y = 0.0f;
|
||||
rayStart.z = radius - delta;
|
||||
distance = FLT_MAX;
|
||||
if (!capsule.findRayIntersection(rayStart, rayDirection, distance)) {
|
||||
std::cout << __FILE__ << ":" << __LINE__ << " ERROR: ray should hit capsule" << std::endl;
|
||||
}
|
||||
expectedDistance = startDistance - radius * sqrtf(2.0f * delta); // using small angle approximation of cosine
|
||||
relativeError = fabsf(distance - expectedDistance) / startDistance;
|
||||
// for edge cases we allow a LOT of error
|
||||
if (relativeError > EDGE_CASE_SLOP_FACTOR * EPSILON) {
|
||||
std::cout << __FILE__ << ":" << __LINE__ << " ERROR: ray capsule intersection distance error = " << relativeError << std::endl;
|
||||
}
|
||||
}
|
||||
// TODO: test at steep angles near cylinder/cap junction
|
||||
}
|
||||
|
||||
void ShapeColliderTests::rayMissesCapsule() {
|
||||
// same as edge case hit tests, but shifted in the opposite direction
|
||||
float startDistance = 3.0f;
|
||||
float radius = 1.0f;
|
||||
float halfHeight = 2.0f;
|
||||
glm::vec3 center(0.0f);
|
||||
CapsuleShape capsule(radius, halfHeight);
|
||||
|
||||
{ // simple test along xAxis
|
||||
// toward capsule center
|
||||
glm::vec3 rayStart(startDistance, 0.0f, 0.0f);
|
||||
glm::vec3 rayDirection(-1.0f, 0.0f, 0.0f);
|
||||
float delta = 2.0f * EPSILON;
|
||||
|
||||
// over top cap
|
||||
rayStart.y = halfHeight + radius + delta;
|
||||
float distance = FLT_MAX;
|
||||
if (capsule.findRayIntersection(rayStart, rayDirection, distance)) {
|
||||
std::cout << __FILE__ << ":" << __LINE__ << " ERROR: ray should miss capsule" << std::endl;
|
||||
}
|
||||
if (distance != FLT_MAX) {
|
||||
std::cout << __FILE__ << ":" << __LINE__ << " ERROR: distance should be unchanged after intersection miss" << std::endl;
|
||||
}
|
||||
|
||||
// below bottom cap
|
||||
rayStart.y = - halfHeight - radius - delta;
|
||||
distance = FLT_MAX;
|
||||
if (capsule.findRayIntersection(rayStart, rayDirection, distance)) {
|
||||
std::cout << __FILE__ << ":" << __LINE__ << " ERROR: ray should miss capsule" << std::endl;
|
||||
}
|
||||
if (distance != FLT_MAX) {
|
||||
std::cout << __FILE__ << ":" << __LINE__ << " ERROR: distance should be unchanged after intersection miss" << std::endl;
|
||||
}
|
||||
|
||||
// past edge of capsule cylindrical face
|
||||
rayStart.y = 0.0f;
|
||||
rayStart.z = radius + delta;
|
||||
distance = FLT_MAX;
|
||||
if (capsule.findRayIntersection(rayStart, rayDirection, distance)) {
|
||||
std::cout << __FILE__ << ":" << __LINE__ << " ERROR: ray should miss capsule" << std::endl;
|
||||
}
|
||||
if (distance != FLT_MAX) {
|
||||
std::cout << __FILE__ << ":" << __LINE__ << " ERROR: distance should be unchanged after intersection miss" << std::endl;
|
||||
}
|
||||
}
|
||||
// TODO: test at steep angles near edge
|
||||
}
|
||||
|
||||
void ShapeColliderTests::rayHitsPlane() {
|
||||
// make a simple plane
|
||||
float planeDistanceFromOrigin = 3.579;
|
||||
glm::vec3 planePosition(0.0f, planeDistanceFromOrigin, 0.0f);
|
||||
PlaneShape plane;
|
||||
plane.setPosition(planePosition);
|
||||
|
||||
// make a simple ray
|
||||
float startDistance = 1.234f;
|
||||
glm::vec3 rayStart(-startDistance, 0.0f, 0.0f);
|
||||
glm::vec3 rayDirection = glm::normalize(glm::vec3(1.0f, 1.0f, 1.0f));
|
||||
|
||||
float distance = FLT_MAX;
|
||||
if (!plane.findRayIntersection(rayStart, rayDirection, distance)) {
|
||||
std::cout << __FILE__ << ":" << __LINE__ << " ERROR: ray should hit plane" << std::endl;
|
||||
}
|
||||
|
||||
float expectedDistance = SQUARE_ROOT_OF_3 * planeDistanceFromOrigin;
|
||||
float relativeError = fabsf(distance - expectedDistance) / planeDistanceFromOrigin;
|
||||
if (relativeError > EPSILON) {
|
||||
std::cout << __FILE__ << ":" << __LINE__ << " ERROR: ray plane intersection distance error = " << relativeError << std::endl;
|
||||
}
|
||||
|
||||
// rotate the whole system and try again
|
||||
float angle = 37.8f;
|
||||
glm::vec3 axis = glm::normalize( glm::vec3(-7.0f, 2.8f, 9.3f) );
|
||||
glm::quat rotation = glm::angleAxis(angle, axis);
|
||||
|
||||
plane.setPosition(rotation * planePosition);
|
||||
plane.setRotation(rotation);
|
||||
rayStart = rotation * rayStart;
|
||||
rayDirection = rotation * rayDirection;
|
||||
|
||||
distance = FLT_MAX;
|
||||
if (!plane.findRayIntersection(rayStart, rayDirection, distance)) {
|
||||
std::cout << __FILE__ << ":" << __LINE__ << " ERROR: ray should hit plane" << std::endl;
|
||||
}
|
||||
|
||||
expectedDistance = SQUARE_ROOT_OF_3 * planeDistanceFromOrigin;
|
||||
relativeError = fabsf(distance - expectedDistance) / planeDistanceFromOrigin;
|
||||
if (relativeError > EPSILON) {
|
||||
std::cout << __FILE__ << ":" << __LINE__ << " ERROR: ray plane intersection distance error = " << relativeError << std::endl;
|
||||
}
|
||||
}
|
||||
|
||||
void ShapeColliderTests::rayMissesPlane() {
|
||||
// make a simple plane
|
||||
float planeDistanceFromOrigin = 3.579;
|
||||
glm::vec3 planePosition(0.0f, planeDistanceFromOrigin, 0.0f);
|
||||
PlaneShape plane;
|
||||
plane.setPosition(planePosition);
|
||||
|
||||
{ // parallel rays should miss
|
||||
float startDistance = 1.234f;
|
||||
glm::vec3 rayStart(-startDistance, 0.0f, 0.0f);
|
||||
glm::vec3 rayDirection = glm::normalize(glm::vec3(-1.0f, 0.0f, -1.0f));
|
||||
|
||||
float distance = FLT_MAX;
|
||||
if (plane.findRayIntersection(rayStart, rayDirection, distance)) {
|
||||
std::cout << __FILE__ << ":" << __LINE__ << " ERROR: ray should miss plane" << std::endl;
|
||||
}
|
||||
if (distance != FLT_MAX) {
|
||||
std::cout << __FILE__ << ":" << __LINE__ << " ERROR: distance should be unchanged after intersection miss" << std::endl;
|
||||
}
|
||||
|
||||
// rotate the whole system and try again
|
||||
float angle = 37.8f;
|
||||
glm::vec3 axis = glm::normalize( glm::vec3(-7.0f, 2.8f, 9.3f) );
|
||||
glm::quat rotation = glm::angleAxis(angle, axis);
|
||||
|
||||
plane.setPosition(rotation * planePosition);
|
||||
plane.setRotation(rotation);
|
||||
rayStart = rotation * rayStart;
|
||||
rayDirection = rotation * rayDirection;
|
||||
|
||||
distance = FLT_MAX;
|
||||
if (plane.findRayIntersection(rayStart, rayDirection, distance)) {
|
||||
std::cout << __FILE__ << ":" << __LINE__ << " ERROR: ray should miss plane" << std::endl;
|
||||
}
|
||||
if (distance != FLT_MAX) {
|
||||
std::cout << __FILE__ << ":" << __LINE__ << " ERROR: distance should be unchanged after intersection miss" << std::endl;
|
||||
}
|
||||
}
|
||||
|
||||
{ // make a simple ray that points away from plane
|
||||
float startDistance = 1.234f;
|
||||
glm::vec3 rayStart(-startDistance, 0.0f, 0.0f);
|
||||
glm::vec3 rayDirection = glm::normalize(glm::vec3(-1.0f, -1.0f, -1.0f));
|
||||
|
||||
float distance = FLT_MAX;
|
||||
if (plane.findRayIntersection(rayStart, rayDirection, distance)) {
|
||||
std::cout << __FILE__ << ":" << __LINE__ << " ERROR: ray should miss plane" << std::endl;
|
||||
}
|
||||
if (distance != FLT_MAX) {
|
||||
std::cout << __FILE__ << ":" << __LINE__ << " ERROR: distance should be unchanged after intersection miss" << std::endl;
|
||||
}
|
||||
|
||||
// rotate the whole system and try again
|
||||
float angle = 37.8f;
|
||||
glm::vec3 axis = glm::normalize( glm::vec3(-7.0f, 2.8f, 9.3f) );
|
||||
glm::quat rotation = glm::angleAxis(angle, axis);
|
||||
|
||||
plane.setPosition(rotation * planePosition);
|
||||
plane.setRotation(rotation);
|
||||
rayStart = rotation * rayStart;
|
||||
rayDirection = rotation * rayDirection;
|
||||
|
||||
distance = FLT_MAX;
|
||||
if (plane.findRayIntersection(rayStart, rayDirection, distance)) {
|
||||
std::cout << __FILE__ << ":" << __LINE__ << " ERROR: ray should miss plane" << std::endl;
|
||||
}
|
||||
if (distance != FLT_MAX) {
|
||||
std::cout << __FILE__ << ":" << __LINE__ << " ERROR: distance should be unchanged after intersection miss" << std::endl;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ShapeColliderTests::runAllTests() {
|
||||
sphereMissesSphere();
|
||||
|
@ -911,4 +1311,12 @@ void ShapeColliderTests::runAllTests() {
|
|||
sphereTouchesAACubeFaces();
|
||||
sphereTouchesAACubeEdges();
|
||||
sphereMissesAACube();
|
||||
|
||||
rayHitsSphere();
|
||||
rayBarelyHitsSphere();
|
||||
rayBarelyMissesSphere();
|
||||
rayHitsCapsule();
|
||||
rayMissesCapsule();
|
||||
rayHitsPlane();
|
||||
rayMissesPlane();
|
||||
}
|
||||
|
|
|
@ -27,6 +27,14 @@ namespace ShapeColliderTests {
|
|||
void sphereTouchesAACubeEdges();
|
||||
void sphereMissesAACube();
|
||||
|
||||
void rayHitsSphere();
|
||||
void rayBarelyHitsSphere();
|
||||
void rayBarelyMissesSphere();
|
||||
void rayHitsCapsule();
|
||||
void rayMissesCapsule();
|
||||
void rayHitsPlane();
|
||||
void rayMissesPlane();
|
||||
|
||||
void runAllTests();
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in a new issue