diff --git a/examples/avatarCollision.js b/examples/avatarCollision.js
new file mode 100644
index 0000000000..5ade894365
--- /dev/null
+++ b/examples/avatarCollision.js
@@ -0,0 +1,68 @@
+//
+// avatarCollision.js
+// examples
+//
+// Created by Andrew Meadows on 2014-04-09
+// Copyright 2014 High Fidelity, Inc.
+//
+// Play a sound on collisions with your avatar
+//
+// Distributed under the Apache License, Version 2.0.
+// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
+//
+
+var SOUND_TRIGGER_CLEAR = 1000; // milliseconds
+var SOUND_TRIGGER_DELAY = 200; // milliseconds
+var soundExpiry = 0;
+var DateObj = new Date();
+var audioOptions = new AudioInjectionOptions();
+audioOptions.volume = 0.5;
+audioOptions.position = { x: 0, y: 0, z: 0 };
+
+var hitSounds = new Array();
+hitSounds[0] = new Sound("http://highfidelity-public.s3-us-west-1.amazonaws.com/sounds/Collisions-hitsandslaps/Hit1.raw");
+hitSounds[1] = new Sound("http://highfidelity-public.s3-us-west-1.amazonaws.com/sounds/Collisions-hitsandslaps/Hit2.raw");
+hitSounds[2] = new Sound("http://highfidelity-public.s3-us-west-1.amazonaws.com/sounds/Collisions-hitsandslaps/Hit3.raw");
+hitSounds[3] = new Sound("http://highfidelity-public.s3-us-west-1.amazonaws.com/sounds/Collisions-hitsandslaps/Hit4.raw");
+hitSounds[4] = new Sound("http://highfidelity-public.s3-us-west-1.amazonaws.com/sounds/Collisions-hitsandslaps/Hit5.raw");
+hitSounds[5] = new Sound("http://highfidelity-public.s3-us-west-1.amazonaws.com/sounds/Collisions-hitsandslaps/Hit6.raw");
+hitSounds[6] = new Sound("http://highfidelity-public.s3-us-west-1.amazonaws.com/sounds/Collisions-hitsandslaps/Hit7.raw");
+hitSounds[7] = new Sound("http://highfidelity-public.s3-us-west-1.amazonaws.com/sounds/Collisions-hitsandslaps/Hit8.raw");
+hitSounds[8] = new Sound("http://highfidelity-public.s3-us-west-1.amazonaws.com/sounds/Collisions-hitsandslaps/Hit9.raw");
+hitSounds[9] = new Sound("http://highfidelity-public.s3-us-west-1.amazonaws.com/sounds/Collisions-hitsandslaps/Hit10.raw");
+hitSounds[10] = new Sound("http://highfidelity-public.s3-us-west-1.amazonaws.com/sounds/Collisions-hitsandslaps/Hit11.raw");
+hitSounds[11] = new Sound("http://highfidelity-public.s3-us-west-1.amazonaws.com/sounds/Collisions-hitsandslaps/Hit12.raw");
+hitSounds[12] = new Sound("http://highfidelity-public.s3-us-west-1.amazonaws.com/sounds/Collisions-hitsandslaps/Hit13.raw");
+hitSounds[13] = new Sound("http://highfidelity-public.s3-us-west-1.amazonaws.com/sounds/Collisions-hitsandslaps/Hit14.raw");
+hitSounds[14] = new Sound("http://highfidelity-public.s3-us-west-1.amazonaws.com/sounds/Collisions-hitsandslaps/Hit15.raw");
+hitSounds[15] = new Sound("http://highfidelity-public.s3-us-west-1.amazonaws.com/sounds/Collisions-hitsandslaps/Hit16.raw");
+hitSounds[16] = new Sound("http://highfidelity-public.s3-us-west-1.amazonaws.com/sounds/Collisions-hitsandslaps/Hit17.raw");
+hitSounds[17] = new Sound("http://highfidelity-public.s3-us-west-1.amazonaws.com/sounds/Collisions-hitsandslaps/Hit18.raw");
+hitSounds[18] = new Sound("http://highfidelity-public.s3-us-west-1.amazonaws.com/sounds/Collisions-hitsandslaps/Hit19.raw");
+hitSounds[19] = new Sound("http://highfidelity-public.s3-us-west-1.amazonaws.com/sounds/Collisions-hitsandslaps/Hit20.raw");
+hitSounds[20] = new Sound("http://highfidelity-public.s3-us-west-1.amazonaws.com/sounds/Collisions-hitsandslaps/Hit21.raw");
+hitSounds[21] = new Sound("http://highfidelity-public.s3-us-west-1.amazonaws.com/sounds/Collisions-hitsandslaps/Hit22.raw");
+
+function playHitSound(mySessionID, theirSessionID, collision) {
+ var now = new Date();
+ var msec = now.getTime();
+ if (msec > soundExpiry) {
+ // this is a new contact --> play a new sound
+ var soundIndex = Math.floor((Math.random() * hitSounds.length) % hitSounds.length);
+ audioOptions.position = collision.contactPoint;
+ Audio.playSound(hitSounds[soundIndex], audioOptions);
+
+ // bump the expiry
+ soundExpiry = msec + SOUND_TRIGGER_CLEAR;
+
+ // log the collision info
+ Uuid.print("my sessionID = ", mySessionID);
+ Uuid.print(" their sessionID = ", theirSessionID);
+ Vec3.print(" penetration = ", collision.penetration);
+ Vec3.print(" contactPoint = ", collision.contactPoint);
+ } else {
+ // this is a recurring contact --> continue to delay sound trigger
+ soundExpiry = msec + SOUND_TRIGGER_DELAY;
+ }
+}
+MyAvatar.collisionWithAvatar.connect(playHitSound);
diff --git a/interface/interface_en.ts b/interface/interface_en.ts
new file mode 100644
index 0000000000..48b395339e
--- /dev/null
+++ b/interface/interface_en.ts
@@ -0,0 +1,274 @@
+
+
+
+
+ Application
+
+
+ Export Voxels
+
+
+
+
+ Sparse Voxel Octree Files (*.svo)
+
+
+
+
+ Open Script
+
+
+
+
+ JavaScript Files (*.js)
+
+
+
+
+ ChatWindow
+
+
+
+ Chat
+
+
+
+
+
+ Connecting to XMPP...
+
+
+
+
+
+ online now:
+
+
+
+
+ day
+
+ %n day
+ %n days
+
+
+
+
+ hour
+
+ %n hour
+ %n hours
+
+
+
+
+ minute
+
+ %n minute
+ %n minutes
+
+
+
+ second
+
+ %n second
+ %n seconds
+
+
+
+
+ %1 online now:
+
+
+
+
+ Dialog
+
+
+
+
+
+ Update Required
+
+
+
+
+
+ Download
+
+
+
+
+
+ Skip Version
+
+
+
+
+
+ Close
+
+
+
+
+ Menu
+
+
+ Open .ini config file
+
+
+
+
+
+ Text files (*.ini)
+
+
+
+
+ Save .ini config file
+
+
+
+
+ PreferencesDialog
+
+
+
+ Cancel
+
+
+
+
+
+ Save all changes
+
+
+
+
+
+
+
+ Avatar
+
+
+
+
+
+ <html><head/><body><p>Avatar display name <span style=" color:#909090;">(optional)</span></p></body></html>
+
+
+
+
+
+ Not showing a name
+
+
+
+
+
+ Head
+
+
+
+
+
+ Body
+
+
+
+
+
+ Advanced Tuning
+
+
+
+
+
+ It's not recomended that you play with these settings unless you've looked into exactly what they do.
+
+
+
+
+
+ Vertical field of view
+
+
+
+
+
+ Lean scale (applies to Faceshift users)
+
+
+
+
+
+ Avatar scale <span style=" color:#909090;">(default is 1.0)</span>
+
+
+
+
+
+ Pupil dillation
+
+
+
+
+
+ Audio Jitter Buffer Samples (0 for automatic)
+
+
+
+
+
+ Faceshift eye detection
+
+
+
+
+
+ Voxels
+
+
+
+
+
+ Maximum voxels
+
+
+
+
+
+ Max voxels sent each second
+
+
+
+
+ QObject
+
+
+
+ Import Voxels
+
+
+
+
+ Loading ...
+
+
+
+
+ Place voxels
+
+
+
+
+ <b>Import</b> %1 as voxels
+
+
+
+
+ Cancel
+
+
+
+
diff --git a/interface/resources/images/pin.svg b/interface/resources/images/pin.svg
new file mode 100644
index 0000000000..ec968a1ec1
--- /dev/null
+++ b/interface/resources/images/pin.svg
@@ -0,0 +1,98 @@
+
+
diff --git a/interface/resources/images/pinned.svg b/interface/resources/images/pinned.svg
new file mode 100644
index 0000000000..bda6f0e747
--- /dev/null
+++ b/interface/resources/images/pinned.svg
@@ -0,0 +1,106 @@
+
+
diff --git a/interface/resources/resources.qrc b/interface/resources/resources.qrc
index 35c0e40270..196c71b743 100644
--- a/interface/resources/resources.qrc
+++ b/interface/resources/resources.qrc
@@ -1,8 +1,11 @@
-
+
+ styles/search.svg
images/close.svg
images/kill-script.svg
images/reload.svg
images/stop.svg
+ images/pin.svg
+ images/pinned.svg
diff --git a/interface/resources/styles/avatar.svg b/interface/resources/styles/avatar.svg
new file mode 100644
index 0000000000..f9382edee4
--- /dev/null
+++ b/interface/resources/styles/avatar.svg
@@ -0,0 +1,27 @@
+
+
+
+
diff --git a/interface/resources/styles/close.svg b/interface/resources/styles/close.svg
new file mode 100644
index 0000000000..8fe4bf4bdb
--- /dev/null
+++ b/interface/resources/styles/close.svg
@@ -0,0 +1,14 @@
+
+
+
+
diff --git a/interface/resources/styles/down.svg b/interface/resources/styles/down.svg
new file mode 100644
index 0000000000..983ccd9597
--- /dev/null
+++ b/interface/resources/styles/down.svg
@@ -0,0 +1,9 @@
+
+
+
+
diff --git a/interface/resources/styles/global.qss b/interface/resources/styles/global.qss
new file mode 100644
index 0000000000..2554f3b2c9
--- /dev/null
+++ b/interface/resources/styles/global.qss
@@ -0,0 +1,113 @@
+* {
+ padding: 0;
+ margin: 0;
+}
+
+FramelessDialog {
+ font-family: Helvetica, Arial, sans-serif;
+ font-size: 16px;
+}
+
+QLineEdit {
+ background-color: rgba(255, 255, 255, 1);
+ border-style: solid;
+ border-width: 1px;
+ border-color: #ccc;
+ padding: 8px;
+ font-size: 16px;
+ color: rgb(51, 51, 51);
+}
+
+QLabel p {
+ color: #0e7077;
+ font-size: 23px;
+}
+
+QPushButton {
+ border-width: 0;
+ border-radius: 9px;
+ font-family: Arial;
+ font-size: 18px;
+ color: #ffffff;
+ padding: 10px 0px;
+}
+
+QSpinBox, QDoubleSpinBox {
+ padding: 5px;
+ border-width: 1;
+ font-size: 16px;
+ color: rgb(51, 51, 51);
+}
+
+QDoubleSpinBox::up-arrow,
+QSpinBox::up-arrow {
+ background-image: url(styles/up.svg);
+ background-repeat: no-repeat;
+ background-position: center center;
+}
+
+QDoubleSpinBox::down-arrow,
+QSpinBox::down-arrow {
+ background-image: url(styles/down.svg);
+ background-repeat: no-repeat;
+ background-position: center center;
+}
+
+QDoubleSpinBox::up-button,
+QSpinBox::up-button,
+QDoubleSpinBox::down-button,
+QSpinBox::down-button {
+ width: 26px;
+ height: 13px;
+
+ background-color: #f2f2f2;
+ border-color: #ccc;
+ border-style: solid;
+ border-width: 1px;
+}
+
+QDoubleSpinBox::up-button,
+QSpinBox::up-button {
+
+ margin-top:2px;
+ border-top-left-radius: 4px;
+ border-top-right-radius: 4px;
+}
+
+QDoubleSpinBox::down-button,
+QSpinBox::down-button {
+ margin-bottom:3px;
+ border-bottom-left-radius: 4px;
+ border-bottom-right-radius: 4px;
+}
+
+QSlider {
+ width: 125px;
+ height: 18px;
+}
+
+QSlider::groove:horizontal {
+ border: none;
+ background-image: url(styles/slider-bg.svg);
+ background-repeat: no-repeat;
+ background-position: center center;
+}
+
+QSlider::handle:horizontal {
+ width: 18px;
+ height: 18px;
+ background-image: url(styles/slider-handle.svg);
+ background-repeat: no-repeat;
+ background-position: center center;
+}
+
+QPushButton#closeButton {
+ border-color: #ccc;
+ border-style: solid;
+ border-width: 1px;
+ border-radius: 0;
+ background-color: #fff;
+ background-image: url(styles/close.svg);
+ background-repeat: no-repeat;
+ background-position: center center;
+}
diff --git a/interface/resources/styles/preferences.qss b/interface/resources/styles/preferences.qss
new file mode 100644
index 0000000000..643fd13a77
--- /dev/null
+++ b/interface/resources/styles/preferences.qss
@@ -0,0 +1,21 @@
+QLabel#avatarLabel {
+ background-image: url(styles/avatar.svg);
+ background-repeat: no-repeat;
+ background-position: left center;
+}
+
+QLabel#advancedTuningLabel {
+ background-image: url(styles/wrench.svg);
+ background-repeat: no-repeat;
+ background-position: left center;
+}
+
+QPushButton#buttonBrowseHead,
+QPushButton#buttonBrowseBody {
+ background-image: url(styles/search.svg);
+ background-repeat: no-repeat;
+ background-position: center center;
+ background-color: #fff;
+ border-radius: 0;
+ padding: 0;
+}
diff --git a/interface/resources/styles/slider-bg.svg b/interface/resources/styles/slider-bg.svg
new file mode 100644
index 0000000000..36c6478026
--- /dev/null
+++ b/interface/resources/styles/slider-bg.svg
@@ -0,0 +1,10 @@
+
+
+
+
diff --git a/interface/resources/styles/slider-handle.svg b/interface/resources/styles/slider-handle.svg
new file mode 100644
index 0000000000..5d87e8599c
--- /dev/null
+++ b/interface/resources/styles/slider-handle.svg
@@ -0,0 +1,10 @@
+
+
+
+
diff --git a/interface/resources/styles/up.svg b/interface/resources/styles/up.svg
new file mode 100644
index 0000000000..d122c4801e
--- /dev/null
+++ b/interface/resources/styles/up.svg
@@ -0,0 +1,9 @@
+
+
+
+
diff --git a/interface/resources/styles/wrench.svg b/interface/resources/styles/wrench.svg
new file mode 100644
index 0000000000..5019f92b18
--- /dev/null
+++ b/interface/resources/styles/wrench.svg
@@ -0,0 +1,9 @@
+
+
+
+
diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp
index 768009183e..9e812ac954 100644
--- a/interface/src/Application.cpp
+++ b/interface/src/Application.cpp
@@ -151,6 +151,7 @@ Application::Application(int& argc, char** argv, timeval &startup_time) :
_lastQueriedViewFrustum(),
_lastQueriedTime(usecTimestampNow()),
_mirrorViewRect(QRect(MIRROR_VIEW_LEFT_PADDING, MIRROR_VIEW_TOP_PADDING, MIRROR_VIEW_WIDTH, MIRROR_VIEW_HEIGHT)),
+ _cameraPushback(0.0f),
_mouseX(0),
_mouseY(0),
_lastMouseMove(usecTimestampNow()),
@@ -521,6 +522,8 @@ void Application::paintGL() {
glEnable(GL_LINE_SMOOTH);
+ float pushback = 0.0f;
+ float pushbackFocalLength = 0.0f;
if (OculusManager::isConnected()) {
_myCamera.setUpShift(0.0f);
_myCamera.setDistance(0.0f);
@@ -533,6 +536,41 @@ void Application::paintGL() {
_myCamera.setTargetPosition(_myAvatar->getHead()->calculateAverageEyePosition());
_myCamera.setTargetRotation(_myAvatar->getHead()->getCameraOrientation());
+ glm::vec3 planeNormal = _myCamera.getTargetRotation() * IDENTITY_FRONT;
+ const float BASE_PUSHBACK_RADIUS = 0.25f;
+ float pushbackRadius = _myCamera.getNearClip() + _myAvatar->getScale() * BASE_PUSHBACK_RADIUS;
+ glm::vec4 plane(planeNormal, -glm::dot(planeNormal, _myCamera.getTargetPosition()) - pushbackRadius);
+
+ // push camera out of any intersecting avatars
+ foreach (const AvatarSharedPointer& avatarData, _avatarManager.getAvatarHash()) {
+ Avatar* avatar = static_cast(avatarData.data());
+ if (avatar->isMyAvatar()) {
+ continue;
+ }
+ if (glm::distance(avatar->getPosition(), _myCamera.getTargetPosition()) >
+ avatar->getBoundingRadius() + pushbackRadius) {
+ continue;
+ }
+ float angle = angleBetween(avatar->getPosition() - _myCamera.getTargetPosition(), planeNormal);
+ if (angle > PI_OVER_TWO) {
+ continue;
+ }
+ float scale = 1.0f - angle / PI_OVER_TWO;
+ scale = qMin(1.0f, scale * 2.5f);
+ static CollisionList collisions(64);
+ collisions.clear();
+ if (!avatar->findPlaneCollisions(plane, collisions)) {
+ continue;
+ }
+ for (int i = 0; i < collisions.size(); i++) {
+ pushback = qMax(pushback, glm::length(collisions.getCollision(i)->_penetration) * scale);
+ }
+ }
+ const float MAX_PUSHBACK = 0.35f;
+ pushback = qMin(pushback, MAX_PUSHBACK * _myAvatar->getScale());
+ const float BASE_PUSHBACK_FOCAL_LENGTH = 0.5f;
+ pushbackFocalLength = BASE_PUSHBACK_FOCAL_LENGTH * _myAvatar->getScale();
+
} else if (_myCamera.getMode() == CAMERA_MODE_THIRD_PERSON) {
_myCamera.setTightness(0.0f); // Camera is directly connected to head without smoothing
_myCamera.setTargetPosition(_myAvatar->getUprightHeadPosition());
@@ -549,13 +587,26 @@ void Application::paintGL() {
// if the head would intersect the near clip plane, we must push the camera out
glm::vec3 relativePosition = glm::inverse(_myCamera.getTargetRotation()) *
(eyePosition - _myCamera.getTargetPosition());
- const float PUSHBACK_RADIUS = 0.2f;
- float pushback = relativePosition.z + _myCamera.getNearClip() +
- _myAvatar->getScale() * PUSHBACK_RADIUS - _myCamera.getDistance();
- if (pushback > 0.0f) {
+ const float BASE_PUSHBACK_RADIUS = 0.2f;
+ float pushbackRadius = _myCamera.getNearClip() + _myAvatar->getScale() * BASE_PUSHBACK_RADIUS;
+ pushback = relativePosition.z + pushbackRadius - _myCamera.getDistance();
+ pushbackFocalLength = _myCamera.getDistance();
+ }
+
+ // handle pushback, if any
+ if (pushbackFocalLength > 0.0f) {
+ const float PUSHBACK_DECAY = 0.5f;
+ _cameraPushback = qMax(pushback, _cameraPushback * PUSHBACK_DECAY);
+ if (_cameraPushback > EPSILON) {
_myCamera.setTargetPosition(_myCamera.getTargetPosition() +
- _myCamera.getTargetRotation() * glm::vec3(0.0f, 0.0f, pushback));
+ _myCamera.getTargetRotation() * glm::vec3(0.0f, 0.0f, _cameraPushback));
+ float enlargement = pushbackFocalLength / (pushbackFocalLength + _cameraPushback);
+ _myCamera.setFieldOfView(glm::degrees(2.0f * atanf(enlargement * tanf(
+ glm::radians(Menu::getInstance()->getFieldOfView() * 0.5f)))));
+ } else {
+ _myCamera.setFieldOfView(Menu::getInstance()->getFieldOfView());
}
+ updateProjectionMatrix(_myCamera, true);
}
// Update camera position
@@ -2563,6 +2614,12 @@ void Application::displayOverlay() {
}
bool isClipping = ((_audio.getTimeSinceLastClip() > 0.f) && (_audio.getTimeSinceLastClip() < CLIPPING_INDICATOR_TIME));
+
+ if ((_audio.getTimeSinceLastClip() > 0.f) && (_audio.getTimeSinceLastClip() < CLIPPING_INDICATOR_TIME)) {
+ const float MAX_MAGNITUDE = 0.7f;
+ float magnitude = MAX_MAGNITUDE * (1 - _audio.getTimeSinceLastClip() / CLIPPING_INDICATOR_TIME);
+ renderCollisionOverlay(_glWidget->width(), _glWidget->height(), magnitude, 1.0f);
+ }
_audio.renderToolBox(MIRROR_VIEW_LEFT_PADDING + AUDIO_METER_GAP,
audioMeterY,
diff --git a/interface/src/Application.h b/interface/src/Application.h
index 278283a7e2..6a14788caa 100644
--- a/interface/src/Application.h
+++ b/interface/src/Application.h
@@ -435,6 +435,7 @@ private:
QRect _mirrorViewRect;
RearMirrorTools* _rearMirrorTools;
+ float _cameraPushback;
glm::mat4 _untranslatedViewMatrix;
glm::vec3 _viewMatrixTranslation;
glm::mat4 _projectionMatrix;
diff --git a/interface/src/Menu.cpp b/interface/src/Menu.cpp
index ece747221e..8ad55dec5b 100644
--- a/interface/src/Menu.cpp
+++ b/interface/src/Menu.cpp
@@ -86,7 +86,8 @@ Menu::Menu() :
_lastAvatarDetailDrop(usecTimestampNow()),
_fpsAverage(FIVE_SECONDS_OF_FRAMES),
_fastFPSAverage(ONE_SECOND_OF_FRAMES),
- _loginAction(NULL)
+ _loginAction(NULL),
+ _preferencesDialog(NULL)
{
Application *appInstance = Application::getInstance();
@@ -708,7 +709,12 @@ bool Menu::isOptionChecked(const QString& menuOption) {
}
void Menu::triggerOption(const QString& menuOption) {
- _actionHash.value(menuOption)->trigger();
+ QAction* action = _actionHash.value(menuOption);
+ if (action) {
+ action->trigger();
+ } else {
+ qDebug() << "NULL Action for menuOption '" << menuOption << "'";
+ }
}
QAction* Menu::getActionForOption(const QString& menuOption) {
@@ -767,165 +773,12 @@ void Menu::loginForCurrentDomain() {
}
void Menu::editPreferences() {
- Application* applicationInstance = Application::getInstance();
- ModelsBrowser headBrowser(Head);
- ModelsBrowser skeletonBrowser(Skeleton);
-
- const QString BROWSE_BUTTON_TEXT = "Browse";
-
- QDialog dialog(applicationInstance->getWindow());
- dialog.setWindowTitle("Interface Preferences");
-
- QBoxLayout* layout = new QBoxLayout(QBoxLayout::TopToBottom);
- dialog.setLayout(layout);
-
- QFormLayout* form = new QFormLayout();
- layout->addLayout(form, 1);
-
-
- QHBoxLayout headModelLayout;
- QString faceURLString = applicationInstance->getAvatar()->getHead()->getFaceModel().getURL().toString();
- QLineEdit headURLEdit(faceURLString);
- QPushButton headBrowseButton(BROWSE_BUTTON_TEXT);
- connect(&headBrowseButton, SIGNAL(clicked()), &headBrowser, SLOT(browse()));
- connect(&headBrowser, SIGNAL(selected(QString)), &headURLEdit, SLOT(setText(QString)));
- headURLEdit.setMinimumWidth(QLINE_MINIMUM_WIDTH);
- headURLEdit.setPlaceholderText(DEFAULT_HEAD_MODEL_URL.toString());
- headModelLayout.addWidget(&headURLEdit);
- headModelLayout.addWidget(&headBrowseButton);
- form->addRow("Head URL:", &headModelLayout);
-
- QHBoxLayout skeletonModelLayout;
- QString skeletonURLString = applicationInstance->getAvatar()->getSkeletonModel().getURL().toString();
- QLineEdit skeletonURLEdit(skeletonURLString);
- QPushButton SkeletonBrowseButton(BROWSE_BUTTON_TEXT);
- connect(&SkeletonBrowseButton, SIGNAL(clicked()), &skeletonBrowser, SLOT(browse()));
- connect(&skeletonBrowser, SIGNAL(selected(QString)), &skeletonURLEdit, SLOT(setText(QString)));
- skeletonURLEdit.setMinimumWidth(QLINE_MINIMUM_WIDTH);
- skeletonURLEdit.setPlaceholderText(DEFAULT_BODY_MODEL_URL.toString());
- skeletonModelLayout.addWidget(&skeletonURLEdit);
- skeletonModelLayout.addWidget(&SkeletonBrowseButton);
- form->addRow("Skeleton URL:", &skeletonModelLayout);
-
-
- QString displayNameString = applicationInstance->getAvatar()->getDisplayName();
- QLineEdit* displayNameEdit = new QLineEdit(displayNameString);
- displayNameEdit->setMinimumWidth(QLINE_MINIMUM_WIDTH);
- form->addRow("Display name:", displayNameEdit);
-
- QSlider* pupilDilation = new QSlider(Qt::Horizontal);
- pupilDilation->setValue(applicationInstance->getAvatar()->getHead()->getPupilDilation() * pupilDilation->maximum());
- form->addRow("Pupil Dilation:", pupilDilation);
-
- QSlider* faceshiftEyeDeflection = new QSlider(Qt::Horizontal);
- faceshiftEyeDeflection->setValue(_faceshiftEyeDeflection * faceshiftEyeDeflection->maximum());
- form->addRow("Faceshift Eye Deflection:", faceshiftEyeDeflection);
-
- QSpinBox* fieldOfView = new QSpinBox();
- fieldOfView->setMaximum(180.f);
- fieldOfView->setMinimum(1.f);
- fieldOfView->setValue(_fieldOfView);
- form->addRow("Vertical Field of View (Degrees):", fieldOfView);
-
- QDoubleSpinBox* leanScale = new QDoubleSpinBox();
- leanScale->setValue(applicationInstance->getAvatar()->getLeanScale());
- form->addRow("Lean Scale:", leanScale);
-
- QDoubleSpinBox* avatarScale = new QDoubleSpinBox();
- avatarScale->setValue(applicationInstance->getAvatar()->getScale());
- form->addRow("Avatar Scale:", avatarScale);
-
- QSpinBox* audioJitterBufferSamples = new QSpinBox();
- audioJitterBufferSamples->setMaximum(10000);
- audioJitterBufferSamples->setMinimum(-10000);
- audioJitterBufferSamples->setValue(_audioJitterBufferSamples);
- form->addRow("Audio Jitter Buffer Samples (0 for automatic):", audioJitterBufferSamples);
-
- QSpinBox* maxVoxels = new QSpinBox();
- const int MAX_MAX_VOXELS = 5000000;
- const int MIN_MAX_VOXELS = 0;
- const int STEP_MAX_VOXELS = 50000;
- maxVoxels->setMaximum(MAX_MAX_VOXELS);
- maxVoxels->setMinimum(MIN_MAX_VOXELS);
- maxVoxels->setSingleStep(STEP_MAX_VOXELS);
- maxVoxels->setValue(_maxVoxels);
- form->addRow("Maximum Voxels:", maxVoxels);
-
- QSpinBox* maxVoxelsPPS = new QSpinBox();
- const int MAX_MAX_VOXELS_PPS = 6000;
- const int MIN_MAX_VOXELS_PPS = 60;
- const int STEP_MAX_VOXELS_PPS = 10;
- maxVoxelsPPS->setMaximum(MAX_MAX_VOXELS_PPS);
- maxVoxelsPPS->setMinimum(MIN_MAX_VOXELS_PPS);
- maxVoxelsPPS->setSingleStep(STEP_MAX_VOXELS_PPS);
- maxVoxelsPPS->setValue(_maxVoxelPacketsPerSecond);
- form->addRow("Maximum Voxels Packets Per Second:", maxVoxelsPPS);
-
- QDialogButtonBox* buttons = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel);
- dialog.connect(buttons, SIGNAL(accepted()), SLOT(accept()));
- dialog.connect(buttons, SIGNAL(rejected()), SLOT(reject()));
- layout->addWidget(buttons);
-
- int ret = dialog.exec();
- if (ret == QDialog::Accepted) {
- bool shouldDispatchIdentityPacket = false;
-
- if (headURLEdit.text() != faceURLString) {
- // change the faceModelURL in the profile, it will also update this user's BlendFace
- if (headURLEdit.text().isEmpty()) {
- applicationInstance->getAvatar()->setFaceModelURL(QUrl(headURLEdit.placeholderText()));
- } else {
- applicationInstance->getAvatar()->setFaceModelURL(QUrl(headURLEdit.text()));
- }
- shouldDispatchIdentityPacket = true;
- }
-
- if (skeletonURLEdit.text() != skeletonURLString) {
- // change the skeletonModelURL in the profile, it will also update this user's Body
- if (skeletonURLEdit.text().isEmpty()) {
- applicationInstance->getAvatar()->setSkeletonModelURL(QUrl(skeletonURLEdit.placeholderText()));
- } else {
- applicationInstance->getAvatar()->setSkeletonModelURL(QUrl(skeletonURLEdit.text()));
- }
- shouldDispatchIdentityPacket = true;
- }
-
- QString displayNameStr(displayNameEdit->text());
-
- if (displayNameStr != displayNameString) {
- applicationInstance->getAvatar()->setDisplayName(displayNameStr);
- shouldDispatchIdentityPacket = true;
- }
-
- if (shouldDispatchIdentityPacket) {
- applicationInstance->getAvatar()->sendIdentityPacket();
- applicationInstance->bumpSettings();
- }
-
- applicationInstance->getAvatar()->getHead()->setPupilDilation(pupilDilation->value() / (float)pupilDilation->maximum());
-
- _maxVoxels = maxVoxels->value();
- applicationInstance->getVoxels()->setMaxVoxels(_maxVoxels);
-
- _maxVoxelPacketsPerSecond = maxVoxelsPPS->value();
-
- applicationInstance->getAvatar()->setLeanScale(leanScale->value());
- applicationInstance->getAvatar()->setClampedTargetScale(avatarScale->value());
-
- _audioJitterBufferSamples = audioJitterBufferSamples->value();
-
- if (_audioJitterBufferSamples != 0) {
- applicationInstance->getAudio()->setJitterBufferSamples(_audioJitterBufferSamples);
- }
-
- _fieldOfView = fieldOfView->value();
- applicationInstance->resizeGL(applicationInstance->getGLWidget()->width(), applicationInstance->getGLWidget()->height());
-
- _faceshiftEyeDeflection = faceshiftEyeDeflection->value() / (float)faceshiftEyeDeflection->maximum();
+ if (!_preferencesDialog) {
+ _preferencesDialog = new PreferencesDialog(Application::getInstance()->getWindow());
+ _preferencesDialog->show();
+ } else {
+ _preferencesDialog->close();
}
- QMetaObject::invokeMethod(applicationInstance->getAudio(), "reset", Qt::QueuedConnection);
-
- sendFakeEnterEvent();
}
void Menu::goToDomain(const QString newDomain) {
@@ -1208,7 +1061,7 @@ void Menu::showMetavoxelEditor() {
void Menu::showChat() {
QMainWindow* mainWindow = Application::getInstance()->getWindow();
if (!_chatWindow) {
- mainWindow->addDockWidget(Qt::NoDockWidgetArea, _chatWindow = new ChatWindow());
+ mainWindow->addDockWidget(Qt::RightDockWidgetArea, _chatWindow = new ChatWindow());
}
if (!_chatWindow->toggleViewAction()->isChecked()) {
int width = _chatWindow->width();
diff --git a/interface/src/Menu.h b/interface/src/Menu.h
index fb71efa5e5..e827e43014 100644
--- a/interface/src/Menu.h
+++ b/interface/src/Menu.h
@@ -22,6 +22,7 @@
#include
#include "location/LocationManager.h"
+#include "ui/PreferencesDialog.h"
#include "ui/ChatWindow.h"
const float ADJUST_LOD_DOWN_FPS = 40.0;
@@ -71,10 +72,13 @@ public:
void triggerOption(const QString& menuOption);
QAction* getActionForOption(const QString& menuOption);
-
+
float getAudioJitterBufferSamples() const { return _audioJitterBufferSamples; }
+ void setAudioJitterBufferSamples(float audioJitterBufferSamples) { _audioJitterBufferSamples = audioJitterBufferSamples; }
float getFieldOfView() const { return _fieldOfView; }
+ void setFieldOfView(float fieldOfView) { _fieldOfView = fieldOfView; }
float getFaceshiftEyeDeflection() const { return _faceshiftEyeDeflection; }
+ void setFaceshiftEyeDeflection(float faceshiftEyeDeflection) { _faceshiftEyeDeflection = faceshiftEyeDeflection; }
BandwidthDialog* getBandwidthDialog() const { return _bandwidthDialog; }
FrustumDrawMode getFrustumDrawMode() const { return _frustumDrawMode; }
ViewFrustumOffset getViewFrustumOffset() const { return _viewFrustumOffset; }
@@ -97,6 +101,7 @@ public:
// User Tweakable PPS from Voxel Server
int getMaxVoxelPacketsPerSecond() const { return _maxVoxelPacketsPerSecond; }
+ void setMaxVoxelPacketsPerSecond(int maxVoxelPacketsPerSecond) { _maxVoxelPacketsPerSecond = maxVoxelPacketsPerSecond; }
QAction* addActionToQMenuAndActionHash(QMenu* destinationMenu,
const QString& actionName,
@@ -222,6 +227,7 @@ private:
SimpleMovingAverage _fpsAverage;
SimpleMovingAverage _fastFPSAverage;
QAction* _loginAction;
+ QPointer _preferencesDialog;
QAction* _chatAction;
};
diff --git a/interface/src/ModelUploader.cpp b/interface/src/ModelUploader.cpp
index d22097a00c..edbc6c0ad9 100644
--- a/interface/src/ModelUploader.cpp
+++ b/interface/src/ModelUploader.cpp
@@ -307,13 +307,13 @@ bool ModelUploader::addTextures(const QString& texdir, const QString fbxFile) {
foreach (FBXMesh mesh, geometry.meshes) {
foreach (FBXMeshPart part, mesh.parts) {
if (!part.diffuseFilename.isEmpty()) {
- if (!addPart(QFileInfo(fbxFile).path() + "/" + part.diffuseFilename,
+ if (!addPart(texdir + "/" + part.diffuseFilename,
QString("texture%1").arg(++_texturesCount))) {
return false;
}
}
if (!part.normalFilename.isEmpty()) {
- if (!addPart(QFileInfo(fbxFile).path() + "/" + part.normalFilename,
+ if (!addPart(texdir + "/" + part.normalFilename,
QString("texture%1").arg(++_texturesCount))) {
return false;
}
diff --git a/interface/src/Util.cpp b/interface/src/Util.cpp
index 36e39a46a5..cecc363daa 100644
--- a/interface/src/Util.cpp
+++ b/interface/src/Util.cpp
@@ -351,10 +351,10 @@ void drawvec3(int x, int y, float scale, float radians, float thick, int mono, g
glPopMatrix();
}
-void renderCollisionOverlay(int width, int height, float magnitude) {
+void renderCollisionOverlay(int width, int height, float magnitude, float red, float blue, float green) {
const float MIN_VISIBLE_COLLISION = 0.01f;
if (magnitude > MIN_VISIBLE_COLLISION) {
- glColor4f(0, 0, 0, magnitude);
+ glColor4f(red, blue, green, magnitude);
glBegin(GL_QUADS);
glVertex2f(0, 0);
glVertex2d(width, 0);
diff --git a/interface/src/Util.h b/interface/src/Util.h
index 4f0e76adf8..ac680645a9 100644
--- a/interface/src/Util.h
+++ b/interface/src/Util.h
@@ -62,7 +62,7 @@ float extractUniformScale(const glm::vec3& scale);
double diffclock(timeval *clock1,timeval *clock2);
-void renderCollisionOverlay(int width, int height, float magnitude);
+void renderCollisionOverlay(int width, int height, float magnitude, float red = 0, float blue = 0, float green = 0);
void renderOrientationDirections( glm::vec3 position, const glm::quat& orientation, float size );
diff --git a/interface/src/avatar/Avatar.cpp b/interface/src/avatar/Avatar.cpp
index 47ced025aa..475e7a1abc 100644
--- a/interface/src/avatar/Avatar.cpp
+++ b/interface/src/avatar/Avatar.cpp
@@ -210,7 +210,14 @@ void Avatar::render(const glm::vec3& cameraPosition, RenderMode renderMode) {
{
// glow when moving far away
const float GLOW_DISTANCE = 20.0f;
- Glower glower(_moving && distanceToTarget > GLOW_DISTANCE && renderMode == NORMAL_RENDER_MODE ? 1.0f : 0.0f);
+ const float GLOW_MAX_LOUDNESS = 2500.0f;
+ const float MAX_GLOW = 0.5f;
+ const float GLOW_FROM_AVERAGE_LOUDNESS = ((this == Application::getInstance()->getAvatar())
+ ? 0.0f
+ : MAX_GLOW * getHeadData()->getAudioLoudness() / GLOW_MAX_LOUDNESS);
+ Glower glower(_moving && distanceToTarget > GLOW_DISTANCE && renderMode == NORMAL_RENDER_MODE
+ ? 1.0f
+ : GLOW_FROM_AVERAGE_LOUDNESS);
// render body
if (Menu::getInstance()->isOptionChecked(MenuOption::Avatars)) {
@@ -233,22 +240,22 @@ void Avatar::render(const glm::vec3& cameraPosition, RenderMode renderMode) {
// quick check before falling into the code below:
// (a 10 degree breadth of an almost 2 meter avatar kicks in at about 12m)
- const float MIN_VOICE_SPHERE_DISTANCE = 12.f;
+ const float MIN_VOICE_SPHERE_DISTANCE = 12.0f;
if (distanceToTarget > MIN_VOICE_SPHERE_DISTANCE) {
// render voice intensity sphere for avatars that are farther away
- const float MAX_SPHERE_ANGLE = 10.f * RADIANS_PER_DEGREE;
- const float MIN_SPHERE_ANGLE = 1.f * RADIANS_PER_DEGREE;
+ const float MAX_SPHERE_ANGLE = 10.0f * RADIANS_PER_DEGREE;
+ const float MIN_SPHERE_ANGLE = 1.0f * RADIANS_PER_DEGREE;
const float MIN_SPHERE_SIZE = 0.01f;
const float SPHERE_LOUDNESS_SCALING = 0.0005f;
const float SPHERE_COLOR[] = { 0.5f, 0.8f, 0.8f };
float height = getSkeletonHeight();
- glm::vec3 delta = height * (getHead()->getCameraOrientation() * IDENTITY_UP) / 2.f;
+ glm::vec3 delta = height * (getHead()->getCameraOrientation() * IDENTITY_UP) / 2.0f;
float angle = abs(angleBetween(toTarget + delta, toTarget - delta));
float sphereRadius = getHead()->getAverageLoudness() * SPHERE_LOUDNESS_SCALING;
if (renderMode == NORMAL_RENDER_MODE && (sphereRadius > MIN_SPHERE_SIZE) &&
(angle < MAX_SPHERE_ANGLE) && (angle > MIN_SPHERE_ANGLE)) {
- glColor4f(SPHERE_COLOR[0], SPHERE_COLOR[1], SPHERE_COLOR[2], 1.f - angle / MAX_SPHERE_ANGLE);
+ glColor4f(SPHERE_COLOR[0], SPHERE_COLOR[1], SPHERE_COLOR[2], 1.0f - angle / MAX_SPHERE_ANGLE);
glPushMatrix();
glTranslatef(_position.x, _position.y, _position.z);
glScalef(height, height, height);
@@ -280,9 +287,9 @@ void Avatar::render(const glm::vec3& cameraPosition, RenderMode renderMode) {
glm::vec3 chatAxis = glm::axis(chatRotation);
glRotatef(glm::degrees(glm::angle(chatRotation)), chatAxis.x, chatAxis.y, chatAxis.z);
- glColor3f(0.f, 0.8f, 0.f);
- glRotatef(180.f, 0.f, 1.f, 0.f);
- glRotatef(180.f, 0.f, 0.f, 1.f);
+ glColor3f(0.0f, 0.8f, 0.0f);
+ glRotatef(180.0f, 0.0f, 1.0f, 0.0f);
+ glRotatef(180.0f, 0.0f, 0.0f, 1.0f);
glScalef(_scale * CHAT_MESSAGE_SCALE, _scale * CHAT_MESSAGE_SCALE, 1.0f);
glDisable(GL_LIGHTING);
@@ -298,7 +305,7 @@ void Avatar::render(const glm::vec3& cameraPosition, RenderMode renderMode) {
_chatMessage[lastIndex] = '\0';
textRenderer(CHAT)->draw(-width / 2.0f, 0, _chatMessage.c_str());
_chatMessage[lastIndex] = lastChar;
- glColor3f(0.f, 1.f, 0.f);
+ glColor3f(0.0f, 1.0f, 0.0f);
textRenderer(CHAT)->draw(width / 2.0f - lastWidth, 0, _chatMessage.c_str() + lastIndex);
}
glEnable(GL_LIGHTING);
@@ -522,6 +529,11 @@ bool Avatar::findSphereCollisions(const glm::vec3& penetratorCenter, float penet
//return getHead()->getFaceModel().findSphereCollisions(penetratorCenter, penetratorRadius, collisions);
}
+bool Avatar::findPlaneCollisions(const glm::vec4& plane, CollisionList& collisions) {
+ return _skeletonModel.findPlaneCollisions(plane, collisions) ||
+ getHead()->getFaceModel().findPlaneCollisions(plane, collisions);
+}
+
void Avatar::updateShapePositions() {
_skeletonModel.updateShapePositions();
Model& headModel = getHead()->getFaceModel();
@@ -550,7 +562,7 @@ bool Avatar::findParticleCollisions(const glm::vec3& particleCenter, float parti
const PalmData* palm = handData->getPalm(i);
if (palm && palm->hasPaddle()) {
// create a disk collision proxy where the hand is
- glm::vec3 fingerAxis(0.f);
+ glm::vec3 fingerAxis(0.0f);
for (size_t f = 0; f < palm->getNumFingers(); ++f) {
const FingerData& finger = (palm->getFingers())[f];
if (finger.isActive()) {
@@ -692,8 +704,8 @@ void Avatar::renderJointConnectingCone(glm::vec3 position1, glm::vec3 position2,
glm::vec3 perpCos = glm::normalize(glm::cross(axis, perpSin));
perpSin = glm::cross(perpCos, axis);
- float anglea = 0.f;
- float angleb = 0.f;
+ float anglea = 0.0f;
+ float angleb = 0.0f;
for (int i = 0; i < NUM_BODY_CONE_SIDES; i ++) {
@@ -743,8 +755,8 @@ void Avatar::updateCollisionFlags() {
void Avatar::setScale(float scale) {
_scale = scale;
- if (_targetScale * (1.f - RESCALING_TOLERANCE) < _scale &&
- _scale < _targetScale * (1.f + RESCALING_TOLERANCE)) {
+ if (_targetScale * (1.0f - RESCALING_TOLERANCE) < _scale &&
+ _scale < _targetScale * (1.0f + RESCALING_TOLERANCE)) {
_scale = _targetScale;
}
}
diff --git a/interface/src/avatar/Avatar.h b/interface/src/avatar/Avatar.h
index 4e24c00c7e..ca05e5dbbf 100755
--- a/interface/src/avatar/Avatar.h
+++ b/interface/src/avatar/Avatar.h
@@ -64,7 +64,7 @@ enum ScreenTintLayer {
// Where one's own Avatar begins in the world (will be overwritten if avatar data file is found)
// this is basically in the center of the ground plane. Slightly adjusted. This was asked for by
// Grayson as he's building a street around here for demo dinner 2
-const glm::vec3 START_LOCATION(0.485f * TREE_SCALE, 0.f, 0.5f * TREE_SCALE);
+const glm::vec3 START_LOCATION(0.485f * TREE_SCALE, 0.0f, 0.5f * TREE_SCALE);
class Texture;
@@ -119,6 +119,12 @@ public:
bool findSphereCollisions(const glm::vec3& penetratorCenter, float penetratorRadius,
CollisionList& collisions, int skeletonSkipIndex = -1);
+ /// Checks for penetration between the described plane and the avatar.
+ /// \param plane the penetration plane
+ /// \param collisions[out] a list to which collisions get appended
+ /// \return whether or not the plane penetrated
+ bool findPlaneCollisions(const glm::vec4& plane, CollisionList& collisions);
+
/// Checks for collision between the a spherical particle and the avatar (including paddle hands)
/// \param collisionCenter the center of particle's bounding sphere
/// \param collisionRadius the radius of particle's bounding sphere
@@ -143,8 +149,6 @@ public:
static void renderJointConnectingCone(glm::vec3 position1, glm::vec3 position2, float radius1, float radius2);
-
-
/// \return true if we expect the avatar would move as a result of the collision
bool collisionWouldMoveAvatar(CollisionInfo& collision) const;
@@ -157,6 +161,9 @@ public:
public slots:
void updateCollisionFlags();
+signals:
+ void collisionWithAvatar(const QUuid& myUUID, const QUuid& theirUUID, const CollisionInfo& collision);
+
protected:
SkeletonModel _skeletonModel;
float _bodyYawDelta;
diff --git a/interface/src/avatar/Hand.cpp b/interface/src/avatar/Hand.cpp
index 832a3da6ca..0f0df8a484 100644
--- a/interface/src/avatar/Hand.cpp
+++ b/interface/src/avatar/Hand.cpp
@@ -163,6 +163,11 @@ void Hand::collideAgainstAvatar(Avatar* avatar, bool isMyHand) {
// TODO: submit collision info to MyAvatar which should lean accordingly
averageContactPoint /= (float)handCollisions.size();
avatar->applyCollision(averageContactPoint, totalPenetration);
+
+ CollisionInfo collision;
+ collision._penetration = totalPenetration;
+ collision._contactPoint = averageContactPoint;
+ emit avatar->collisionWithAvatar(avatar->getSessionUUID(), _owningAvatar->getSessionUUID(), collision);
}
}
}
diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp
index e587f38ed0..a258cd341b 100644
--- a/interface/src/avatar/MyAvatar.cpp
+++ b/interface/src/avatar/MyAvatar.cpp
@@ -647,7 +647,7 @@ void MyAvatar::renderBody(RenderMode renderMode) {
_skeletonModel.render(1.0f, modelRenderMode);
// Render head so long as the camera isn't inside it
- const float RENDER_HEAD_CUTOFF_DISTANCE = 0.40f;
+ const float RENDER_HEAD_CUTOFF_DISTANCE = 0.50f;
Camera* myCamera = Application::getInstance()->getCamera();
if (renderMode != NORMAL_RENDER_MODE || (glm::length(myCamera->getPosition() - getHead()->calculateAverageEyePosition()) >
RENDER_HEAD_CUTOFF_DISTANCE * _scale)) {
@@ -896,8 +896,7 @@ bool findAvatarAvatarPenetration(const glm::vec3 positionA, float radiusA, float
return false;
}
-static CollisionList bodyCollisions(16);
-const float BODY_COLLISION_RESOLVE_TIMESCALE = 0.5f; // seconds
+const float BODY_COLLISION_RESOLUTION_TIMESCALE = 0.5f; // seconds
void MyAvatar::updateCollisionWithAvatars(float deltaTime) {
// Reset detector for nearest avatar
@@ -910,7 +909,7 @@ void MyAvatar::updateCollisionWithAvatars(float deltaTime) {
updateShapePositions();
float myBoundingRadius = getBoundingRadius();
- const float BODY_COLLISION_RESOLVE_FACTOR = deltaTime / BODY_COLLISION_RESOLVE_TIMESCALE;
+ const float BODY_COLLISION_RESOLUTION_FACTOR = deltaTime / BODY_COLLISION_RESOLUTION_TIMESCALE;
foreach (const AvatarSharedPointer& avatarPointer, avatars) {
Avatar* avatar = static_cast(avatarPointer.data());
@@ -930,26 +929,19 @@ void MyAvatar::updateCollisionWithAvatars(float deltaTime) {
_skeletonModel.getBodyShapes(myShapes);
QVector theirShapes;
avatar->getSkeletonModel().getBodyShapes(theirShapes);
- bodyCollisions.clear();
- // TODO: add method to ShapeCollider for colliding lists of shapes
- foreach (const Shape* myShape, myShapes) {
- foreach (const Shape* theirShape, theirShapes) {
- ShapeCollider::shapeShape(myShape, theirShape, bodyCollisions);
+
+ CollisionInfo collision;
+ if (ShapeCollider::collideShapesCoarse(myShapes, theirShapes, collision)) {
+ if (glm::length2(collision._penetration) > EPSILON) {
+ setPosition(getPosition() - BODY_COLLISION_RESOLUTION_FACTOR * collision._penetration);
+ _lastBodyPenetration += collision._penetration;
+ emit collisionWithAvatar(getSessionUUID(), avatar->getSessionUUID(), collision);
}
}
- glm::vec3 totalPenetration(0.0f);
- for (int j = 0; j < bodyCollisions.size(); ++j) {
- CollisionInfo* collision = bodyCollisions.getCollision(j);
- totalPenetration = addPenetrations(totalPenetration, collision->_penetration);
- }
- if (glm::length2(totalPenetration) > EPSILON) {
- setPosition(getPosition() - BODY_COLLISION_RESOLVE_FACTOR * totalPenetration);
- }
- _lastBodyPenetration += totalPenetration;
// collide our hands against them
// TODO: make this work when we can figure out when the other avatar won't yeild
- // (for example, we're colling against their chest or leg)
+ // (for example, we're colliding against their chest or leg)
//getHand()->collideAgainstAvatar(avatar, true);
// collide their hands against us
diff --git a/interface/src/renderer/Model.cpp b/interface/src/renderer/Model.cpp
index 819d1164c0..2d47a077b7 100644
--- a/interface/src/renderer/Model.cpp
+++ b/interface/src/renderer/Model.cpp
@@ -595,7 +595,7 @@ bool Model::findCollisions(const QVector shapes, CollisionList& co
const Shape* theirShape = shapes[i];
for (int j = 0; j < _jointShapes.size(); ++j) {
const Shape* ourShape = _jointShapes[j];
- if (ShapeCollider::shapeShape(theirShape, ourShape, collisions)) {
+ if (ShapeCollider::collideShapes(theirShape, ourShape, collisions)) {
collided = true;
}
}
@@ -622,7 +622,7 @@ bool Model::findSphereCollisions(const glm::vec3& sphereCenter, float sphereRadi
} while (ancestorIndex != -1);
}
}
- if (ShapeCollider::shapeShape(&sphere, _jointShapes[i], collisions)) {
+ if (ShapeCollider::collideShapes(&sphere, _jointShapes[i], collisions)) {
CollisionInfo* collision = collisions.getLastCollision();
collision->_type = MODEL_COLLISION;
collision->_data = (void*)(this);
@@ -634,6 +634,21 @@ bool Model::findSphereCollisions(const glm::vec3& sphereCenter, float sphereRadi
return collided;
}
+bool Model::findPlaneCollisions(const glm::vec4& plane, CollisionList& collisions) {
+ bool collided = false;
+ PlaneShape planeShape(plane);
+ for (int i = 0; i < _jointShapes.size(); i++) {
+ if (ShapeCollider::collideShapes(&planeShape, _jointShapes[i], collisions)) {
+ CollisionInfo* collision = collisions.getLastCollision();
+ collision->_type = MODEL_COLLISION;
+ collision->_data = (void*)(this);
+ collision->_flags = i;
+ collided = true;
+ }
+ }
+ return collided;
+}
+
class Blender : public QRunnable {
public:
diff --git a/interface/src/renderer/Model.h b/interface/src/renderer/Model.h
index 30625cc16f..65b79fffdd 100644
--- a/interface/src/renderer/Model.h
+++ b/interface/src/renderer/Model.h
@@ -176,6 +176,8 @@ public:
bool findSphereCollisions(const glm::vec3& penetratorCenter, float penetratorRadius,
CollisionList& collisions, int skipIndex = -1);
+
+ bool findPlaneCollisions(const glm::vec4& plane, CollisionList& collisions);
/// \param collision details about the collisions
/// \return true if the collision is against a moveable joint
diff --git a/interface/src/ui/ChatWindow.cpp b/interface/src/ui/ChatWindow.cpp
index 0060cb839c..8a83ba680b 100644
--- a/interface/src/ui/ChatWindow.cpp
+++ b/interface/src/ui/ChatWindow.cpp
@@ -12,10 +12,12 @@
#include
#include
#include
+#include
#include
#include
#include
#include
+#include
#include "Application.h"
#include "FlowLayout.h"
@@ -35,7 +37,9 @@ ChatWindow::ChatWindow() :
{
ui->setupUi(this);
- // remove the title bar (see the Qt docs on setTitleBarWidget)
+ // remove the title bar (see the Qt docs on setTitleBarWidget), but we keep it for undocking
+ //
+ titleBar = titleBarWidget();
setTitleBarWidget(new QWidget());
FlowLayout* flowLayout = new FlowLayout(0, 4, 4);
@@ -260,3 +264,16 @@ void ChatWindow::messageReceived(const QXmppMessage& message) {
}
#endif
+
+void ChatWindow::togglePinned() {
+ QMainWindow* mainWindow = Application::getInstance()->getWindow();
+ mainWindow->removeDockWidget(this);
+ if (ui->togglePinnedButton->isChecked()) {
+ mainWindow->addDockWidget(ui->togglePinnedButton->isChecked() ? Qt::RightDockWidgetArea : Qt::NoDockWidgetArea, this);
+ }
+ if (!this->toggleViewAction()->isChecked()) {
+ this->toggleViewAction()->trigger();
+ }
+ this->setFloating(!ui->togglePinnedButton->isChecked());
+ setTitleBarWidget(ui->togglePinnedButton->isChecked()?new QWidget():titleBar);
+}
\ No newline at end of file
diff --git a/interface/src/ui/ChatWindow.h b/interface/src/ui/ChatWindow.h
index cb9619cc42..614afc1ef1 100644
--- a/interface/src/ui/ChatWindow.h
+++ b/interface/src/ui/ChatWindow.h
@@ -50,12 +50,14 @@ private:
void addTimeStamp();
Ui::ChatWindow* ui;
+ QWidget* titleBar;
int numMessagesAfterLastTimeStamp;
QDateTime lastMessageStamp;
private slots:
void connected();
void timeout();
+ void togglePinned();
#ifdef HAVE_QXMPP
void error(QXmppClient::Error error);
void participantsChanged();
diff --git a/interface/src/ui/FramelessDialog.cpp b/interface/src/ui/FramelessDialog.cpp
new file mode 100644
index 0000000000..18e3bca89a
--- /dev/null
+++ b/interface/src/ui/FramelessDialog.cpp
@@ -0,0 +1,100 @@
+//
+// FramelessDialog.cpp
+// interface/src/ui
+//
+// Created by Stojce Slavkovski on 2/20/14.
+// 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 "Application.h"
+#include "FramelessDialog.h"
+
+const int RESIZE_HANDLE_WIDTH = 7;
+
+FramelessDialog::FramelessDialog(QWidget *parent, Qt::WindowFlags flags) :
+QDialog(parent, flags | Qt::FramelessWindowHint | Qt::WindowStaysOnTopHint) {
+ setAttribute(Qt::WA_DeleteOnClose);
+
+ // handle rezize and move events
+ parentWidget()->installEventFilter(this);
+
+ // handle minimize, restore and focus events
+ Application::getInstance()->installEventFilter(this);
+}
+
+bool FramelessDialog::eventFilter(QObject* sender, QEvent* event) {
+ switch (event->type()) {
+ case QEvent::Move:
+ if (sender == parentWidget()) {
+ // move to upper left corner on app move
+ move(parentWidget()->geometry().topLeft());
+ }
+ break;
+ case QEvent::Resize:
+ if (sender == parentWidget()) {
+ // keep full app height on resizing the app
+ setFixedHeight(parentWidget()->size().height());
+ }
+ break;
+ case QEvent::WindowStateChange:
+ if (parentWidget()->isMinimized()) {
+ setHidden(true);
+ } else {
+ setHidden(false);
+ }
+ break;
+ case QEvent::ApplicationDeactivate:
+ // hide on minimize and focus lost
+ setHidden(true);
+ break;
+ case QEvent::ApplicationActivate:
+ setHidden(false);
+ break;
+ default:
+ break;
+ }
+
+ return false;
+}
+
+void FramelessDialog::setStyleSheetFile(const QString& fileName) {
+ QFile globalStyleSheet(Application::resourcesPath() + "styles/global.qss");
+ QFile styleSheet(Application::resourcesPath() + fileName);
+ if (styleSheet.open(QIODevice::ReadOnly) && globalStyleSheet.open(QIODevice::ReadOnly) ) {
+ QDir::setCurrent(Application::resourcesPath());
+ setStyleSheet(globalStyleSheet.readAll() + styleSheet.readAll());
+ }
+}
+
+void FramelessDialog::showEvent(QShowEvent* event) {
+ // move to upper left corner
+ move(parentWidget()->geometry().topLeft());
+
+ // keep full app height
+ setFixedHeight(parentWidget()->size().height());
+
+ // resize parrent if width is smaller than this dialog
+ if (parentWidget()->size().width() < size().width()) {
+ parentWidget()->resize(size().width(), parentWidget()->size().height());
+ }
+}
+void FramelessDialog::mousePressEvent(QMouseEvent* mouseEvent) {
+ if (abs(mouseEvent->pos().x() - size().width()) < RESIZE_HANDLE_WIDTH && mouseEvent->button() == Qt::LeftButton) {
+ _isResizing = true;
+ QApplication::setOverrideCursor(Qt::SizeHorCursor);
+ }
+}
+
+void FramelessDialog::mouseReleaseEvent(QMouseEvent* mouseEvent) {
+ QApplication::restoreOverrideCursor();
+ _isResizing = false;
+}
+
+void FramelessDialog::mouseMoveEvent(QMouseEvent* mouseEvent) {
+ if (_isResizing) {
+ resize(mouseEvent->pos().x(), size().height());
+ }
+}
diff --git a/interface/src/ui/FramelessDialog.h b/interface/src/ui/FramelessDialog.h
new file mode 100644
index 0000000000..db9f6dfd6c
--- /dev/null
+++ b/interface/src/ui/FramelessDialog.h
@@ -0,0 +1,38 @@
+//
+// FramelessDialog.h
+// interface/src/ui
+//
+// Created by Stojce Slavkovski on 2/20/14.
+// 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_FramelessDialog_h
+#define hifi_FramelessDialog_h
+
+#include
+
+class FramelessDialog : public QDialog {
+ Q_OBJECT
+
+public:
+ FramelessDialog(QWidget* parent = 0, Qt::WindowFlags flags = 0);
+ void setStyleSheetFile(const QString& fileName);
+
+protected:
+ virtual void mouseMoveEvent(QMouseEvent* mouseEvent);
+ virtual void mousePressEvent(QMouseEvent* mouseEvent);
+ virtual void mouseReleaseEvent(QMouseEvent* mouseEvent);
+ virtual void showEvent(QShowEvent* event);
+
+ bool eventFilter(QObject* sender, QEvent* event);
+
+private:
+ bool _isResizing;
+
+};
+
+#endif // hifi_FramelessDialog_h
diff --git a/interface/src/ui/ModelsBrowser.cpp b/interface/src/ui/ModelsBrowser.cpp
index 6c421863c8..77e056bdd3 100644
--- a/interface/src/ui/ModelsBrowser.cpp
+++ b/interface/src/ui/ModelsBrowser.cpp
@@ -65,7 +65,7 @@ static const QString propertiesIds[MODEL_METADATA_COUNT] = {
};
ModelsBrowser::ModelsBrowser(ModelType modelsType, QWidget* parent) :
- QWidget(parent),
+ QWidget(parent, Qt::WindowStaysOnTopHint),
_handler(new ModelHandler(modelsType))
{
connect(_handler, SIGNAL(doneDownloading()), SLOT(resizeView()));
diff --git a/interface/src/ui/PreferencesDialog.cpp b/interface/src/ui/PreferencesDialog.cpp
new file mode 100644
index 0000000000..a14e80e62f
--- /dev/null
+++ b/interface/src/ui/PreferencesDialog.cpp
@@ -0,0 +1,164 @@
+//
+// PreferencesDialog.cpp
+// interface/src/ui
+//
+// Created by Stojce Slavkovski on 2/20/14.
+// 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 "Application.h"
+#include "Menu.h"
+#include "PreferencesDialog.h"
+#include "ModelsBrowser.h"
+
+const int SCROLL_PANEL_BOTTOM_MARGIN = 30;
+const int OK_BUTTON_RIGHT_MARGIN = 30;
+const int BUTTONS_TOP_MARGIN = 24;
+
+PreferencesDialog::PreferencesDialog(QWidget* parent, Qt::WindowFlags flags) : FramelessDialog(parent, flags) {
+
+ ui.setupUi(this);
+ setStyleSheetFile("styles/preferences.qss");
+ loadPreferences();
+ connect(ui.closeButton, &QPushButton::clicked, this, &QDialog::close);
+
+ connect(ui.buttonBrowseHead, &QPushButton::clicked, this, &PreferencesDialog::openHeadModelBrowser);
+ connect(ui.buttonBrowseBody, &QPushButton::clicked, this, &PreferencesDialog::openBodyModelBrowser);
+}
+
+void PreferencesDialog::accept() {
+ savePreferences();
+ close();
+}
+
+void PreferencesDialog::setHeadUrl(QString modelUrl) {
+ ui.faceURLEdit->setText(modelUrl);
+ setWindowFlags(windowFlags() | Qt::WindowStaysOnTopHint);
+}
+
+void PreferencesDialog::setSkeletonUrl(QString modelUrl) {
+ ui.skeletonURLEdit->setText(modelUrl);
+ setWindowFlags(windowFlags() | Qt::WindowStaysOnTopHint);
+}
+
+void PreferencesDialog::openHeadModelBrowser() {
+ setWindowFlags(windowFlags() & ~Qt::WindowStaysOnTopHint);
+ ModelsBrowser modelBrowser(Head);
+ connect(&modelBrowser, &ModelsBrowser::selected, this, &PreferencesDialog::setHeadUrl);
+ modelBrowser.browse();
+}
+
+void PreferencesDialog::openBodyModelBrowser() {
+ setWindowFlags(windowFlags() & ~Qt::WindowStaysOnTopHint);
+ ModelsBrowser modelBrowser(Skeleton);
+ connect(&modelBrowser, &ModelsBrowser::selected, this, &PreferencesDialog::setSkeletonUrl);
+ modelBrowser.browse();
+}
+
+void PreferencesDialog::resizeEvent(QResizeEvent *resizeEvent) {
+
+ // keep buttons panel at the bottom
+ ui.buttonsPanel->setGeometry(0, size().height() - ui.buttonsPanel->height(), size().width(), ui.buttonsPanel->height());
+
+ // set width and height of srcollarea to match bottom panel and width
+ ui.scrollArea->setGeometry(ui.scrollArea->geometry().x(), ui.scrollArea->geometry().y(),
+ size().width(),
+ size().height() - ui.buttonsPanel->height() -
+ SCROLL_PANEL_BOTTOM_MARGIN - ui.scrollArea->geometry().y());
+
+ // move Save button to left position
+ ui.defaultButton->move(size().width() - OK_BUTTON_RIGHT_MARGIN - ui.defaultButton->size().width(), BUTTONS_TOP_MARGIN);
+
+ // move Save button to left position
+ ui.cancelButton->move(ui.defaultButton->pos().x() - ui.cancelButton->size().width(), BUTTONS_TOP_MARGIN);
+
+ // move close button
+ ui.closeButton->move(size().width() - OK_BUTTON_RIGHT_MARGIN - ui.closeButton->size().width(), ui.closeButton->pos().y());
+}
+
+void PreferencesDialog::loadPreferences() {
+
+ MyAvatar* myAvatar = Application::getInstance()->getAvatar();
+ Menu* menuInstance = Menu::getInstance();
+
+ _displayNameString = myAvatar->getDisplayName();
+ ui.displayNameEdit->setText(_displayNameString);
+
+ _faceURLString = myAvatar->getHead()->getFaceModel().getURL().toString();
+ ui.faceURLEdit->setText(_faceURLString);
+
+ _skeletonURLString = myAvatar->getSkeletonModel().getURL().toString();
+ ui.skeletonURLEdit->setText(_skeletonURLString);
+
+ ui.pupilDilationSlider->setValue(myAvatar->getHead()->getPupilDilation() *
+ ui.pupilDilationSlider->maximum());
+
+ ui.faceshiftEyeDeflectionSider->setValue(menuInstance->getFaceshiftEyeDeflection() *
+ ui.faceshiftEyeDeflectionSider->maximum());
+
+ ui.audioJitterSpin->setValue(menuInstance->getAudioJitterBufferSamples());
+
+ ui.fieldOfViewSpin->setValue(menuInstance->getFieldOfView());
+
+ ui.leanScaleSpin->setValue(myAvatar->getLeanScale());
+
+ ui.avatarScaleSpin->setValue(myAvatar->getScale());
+
+ ui.maxVoxelsSpin->setValue(menuInstance->getMaxVoxels());
+
+ ui.maxVoxelsPPSSpin->setValue(menuInstance->getMaxVoxelPacketsPerSecond());
+}
+
+void PreferencesDialog::savePreferences() {
+
+ MyAvatar* myAvatar = Application::getInstance()->getAvatar();
+ bool shouldDispatchIdentityPacket = false;
+
+ QString displayNameStr(ui.displayNameEdit->text());
+ if (displayNameStr != _displayNameString) {
+ myAvatar->setDisplayName(displayNameStr);
+ shouldDispatchIdentityPacket = true;
+ }
+
+ QUrl faceModelURL(ui.faceURLEdit->text());
+ if (faceModelURL.toString() != _faceURLString) {
+ // change the faceModelURL in the profile, it will also update this user's BlendFace
+ myAvatar->setFaceModelURL(faceModelURL);
+ shouldDispatchIdentityPacket = true;
+ }
+
+ QUrl skeletonModelURL(ui.skeletonURLEdit->text());
+ if (skeletonModelURL.toString() != _skeletonURLString) {
+ // change the skeletonModelURL in the profile, it will also update this user's Body
+ myAvatar->setSkeletonModelURL(skeletonModelURL);
+ shouldDispatchIdentityPacket = true;
+ }
+
+ if (shouldDispatchIdentityPacket) {
+ myAvatar->sendIdentityPacket();
+ Application::getInstance()->bumpSettings();
+ }
+
+ myAvatar->getHead()->setPupilDilation(ui.pupilDilationSlider->value() / (float)ui.pupilDilationSlider->maximum());
+ myAvatar->setLeanScale(ui.leanScaleSpin->value());
+ myAvatar->setClampedTargetScale(ui.avatarScaleSpin->value());
+
+ Application::getInstance()->getVoxels()->setMaxVoxels(ui.maxVoxelsSpin->value());
+ Application::getInstance()->resizeGL(Application::getInstance()->getGLWidget()->width(),
+ Application::getInstance()->getGLWidget()->height());
+
+ Menu::getInstance()->setFieldOfView(ui.fieldOfViewSpin->value());
+
+ Menu::getInstance()->setFaceshiftEyeDeflection(ui.faceshiftEyeDeflectionSider->value() /
+ (float)ui.faceshiftEyeDeflectionSider->maximum());
+ Menu::getInstance()->setMaxVoxelPacketsPerSecond(ui.maxVoxelsPPSSpin->value());
+
+ Menu::getInstance()->setAudioJitterBufferSamples(ui.audioJitterSpin->value());
+
+ Application::getInstance()->resizeGL(Application::getInstance()->getGLWidget()->width(),
+ Application::getInstance()->getGLWidget()->height());
+}
diff --git a/interface/src/ui/PreferencesDialog.h b/interface/src/ui/PreferencesDialog.h
new file mode 100644
index 0000000000..c9514e584a
--- /dev/null
+++ b/interface/src/ui/PreferencesDialog.h
@@ -0,0 +1,47 @@
+//
+// PreferencesDialog.h
+// interface/src/ui
+//
+// Created by Stojce Slavkovski on 2/20/14.
+// 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_PreferencesDialog_h
+#define hifi_PreferencesDialog_h
+
+#include "FramelessDialog.h"
+#include "ui_preferencesDialog.h"
+
+#include
+
+class PreferencesDialog : public FramelessDialog {
+ Q_OBJECT
+
+public:
+ PreferencesDialog(QWidget* parent = 0, Qt::WindowFlags flags = 0);
+
+protected:
+ void resizeEvent(QResizeEvent* resizeEvent);
+
+private:
+ void loadPreferences();
+ void savePreferences();
+ void openHeadModelBrowser();
+ void openBodyModelBrowser();
+
+ Ui_PreferencesDialog ui;
+ QString _faceURLString;
+ QString _skeletonURLString;
+ QString _displayNameString;
+
+private slots:
+ void accept();
+ void setHeadUrl(QString modelUrl);
+ void setSkeletonUrl(QString modelUrl);
+
+};
+
+#endif // hifi_PreferencesDialog_h
diff --git a/interface/ui/chatWindow.ui b/interface/ui/chatWindow.ui
index 60a0c6badd..c46e692fc6 100644
--- a/interface/ui/chatWindow.ui
+++ b/interface/ui/chatWindow.ui
@@ -20,7 +20,7 @@
font-family: Helvetica, Arial, sans-serif;
- QDockWidget::NoDockWidgetFeatures
+ QDockWidget::DockWidgetFloatable|QDockWidget::DockWidgetMovable
Qt::NoDockWidgetArea
@@ -79,6 +79,45 @@
+ -
+
+
+
+ 0
+ 0
+
+
+
+
+ 16
+ 16
+
+
+
+ Qt::NoFocus
+
+
+
+
+
+
+ :/images/pin.svg
+ :/images/pinned.svg:/images/pin.svg
+
+
+ true
+
+
+ true
+
+
+ false
+
+
+ true
+
+
+
-
@@ -204,6 +243,22 @@
+
+ togglePinnedButton
+ clicked()
+ ChatWindow
+ togglePinned()
+
+
+ 390
+ 42
+
+
+ 550
+ 42
+
+
+
closeButton
clicked()
diff --git a/interface/ui/preferencesDialog.ui b/interface/ui/preferencesDialog.ui
new file mode 100644
index 0000000000..a151a499c6
--- /dev/null
+++ b/interface/ui/preferencesDialog.ui
@@ -0,0 +1,1375 @@
+
+
+ PreferencesDialog
+
+
+
+ 0
+ 0
+ 638
+ 652
+
+
+
+
+ 0
+ 0
+
+
+
+
+ 610
+ 0
+
+
+
+ 0.950000000000000
+
+
+
+
+ 0
+ 560
+ 611
+ 97
+
+
+
+
+ 0
+ 0
+
+
+
+
+ 0
+ 97
+
+
+
+
+ Arial
+
+
+
+ background-color: #0e7077
+
+
+
+
+ 310
+ 24
+ 91
+ 50
+
+
+
+
+ 0
+ 0
+
+
+
+
+ 0
+ 50
+
+
+
+
+ Arial
+ 18
+ 50
+ false
+
+
+
+
+
+
+ Cancel
+
+
+
+
+
+ 400
+ 24
+ 188
+ 50
+
+
+
+
+ 0
+ 0
+
+
+
+
+ 188
+ 49
+
+
+
+
+ Arial
+ 18
+
+
+
+ background-color: #fff;
+color: #0e7077
+
+
+ Save all changes
+
+
+ true
+
+
+
+
+
+
+ 0
+ 30
+ 615
+ 491
+
+
+
+ QFrame::NoFrame
+
+
+ QFrame::Plain
+
+
+ 0
+
+
+ true
+
+
+
+
+ 0
+ 0
+ 615
+ 833
+
+
+
+
+ 0
+
+
+ 30
+
+
+ 0
+
+
+ 30
+
+
+ 30
+
+
-
+
+
+
+ 0
+ 0
+
+
+
+
+ Arial
+ 24
+
+
+
+ color: #0e7077
+
+
+ Avatar
+
+
+ 25
+
+
+
+
+
+
+ -
+
+
+
+ 0
+ 0
+
+
+
+
+ 0
+ 30
+
+
+
+
+ Arial
+ 16
+
+
+
+ color: #0e7077
+
+
+ <html><head/><body><p>Avatar display name <span style=" color:#909090;">(optional)</span></p></body></html>
+
+
+ Qt::AlignBottom|Qt::AlignLeading|Qt::AlignLeft
+
+
+ displayNameEdit
+
+
+
+ -
+
+
+
+ 0
+ 0
+
+
+
+
+ 280
+ 0
+
+
+
+
+ Arial
+
+
+
+ Qt::LeftToRight
+
+
+
+
+
+ Not showing a name
+
+
+
+ -
+
+
+
+ 0
+ 0
+
+
+
+
+ 0
+ 30
+
+
+
+
+ Arial
+ 16
+
+
+
+ color: #0e7077
+
+
+ Head
+
+
+ Qt::AlignBottom|Qt::AlignLeading|Qt::AlignLeft
+
+
+ 0
+
+
+ faceURLEdit
+
+
+
+ -
+
+
-
+
+
+
+ 0
+ 0
+
+
+
+
+ Arial
+
+
+
+
+ -
+
+
+ Qt::Horizontal
+
+
+ QSizePolicy::Fixed
+
+
+
+ 20
+ 20
+
+
+
+
+ -
+
+
+
+ 0
+ 0
+
+
+
+
+ 30
+ 30
+
+
+
+
+ 30
+ 30
+
+
+
+
+
+
+
+
+
+
+ 30
+ 30
+
+
+
+
+
+
+ -
+
+
+
+ 0
+ 0
+
+
+
+
+ 0
+ 30
+
+
+
+
+ Arial
+ 16
+
+
+
+ color: #0e7077
+
+
+ Body
+
+
+ Qt::AlignBottom|Qt::AlignLeading|Qt::AlignLeft
+
+
+ skeletonURLEdit
+
+
+
+ -
+
+
-
+
+
+
+ 0
+ 0
+
+
+
+
+ Arial
+
+
+
+
+ -
+
+
+ Qt::Horizontal
+
+
+ QSizePolicy::Fixed
+
+
+
+ 20
+ 20
+
+
+
+
+ -
+
+
+
+ 0
+ 0
+
+
+
+
+ 30
+ 30
+
+
+
+
+ 30
+ 30
+
+
+
+
+
+
+
+
+
+
+ 30
+ 30
+
+
+
+
+
+
+ -
+
+
+ Qt::Vertical
+
+
+
+ 0
+ 35
+
+
+
+
+ -
+
+
+
+ 0
+ 0
+
+
+
+
+ 0
+ 0
+
+
+
+
+ Arial
+ 24
+
+
+
+ color: #0e7077
+
+
+ Advanced Tuning
+
+
+ Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter
+
+
+ 25
+
+
+
+ -
+
+
+
+ 0
+ 0
+
+
+
+
+ Arial
+ 16
+
+
+
+ true
+
+
+ color: rgb(51, 51, 51);
+
+
+ It's not recomended that you play with these settings unless you've looked into exactly what they do.
+
+
+ false
+
+
+ true
+
+
+
+ -
+
+
+
+ 0
+ 0
+
+
+
+
+ 0
+ 40
+
+
+
+
+ Arial
+ 20
+ 50
+ false
+
+
+
+ color: #0e7077
+
+
+ Avatar
+
+
+ Qt::AlignBottom|Qt::AlignLeading|Qt::AlignLeft
+
+
+
+ -
+
+
+ 0
+
+
+ 10
+
+
+ 0
+
+
+ 10
+
+
-
+
+
+
+ Arial
+
+
+
+
+
+
+ Vertical field of view
+
+
+ 15
+
+
+ fieldOfViewSpin
+
+
+
+ -
+
+
+
+ Arial
+
+
+
+ Qt::Horizontal
+
+
+
+ 0
+ 0
+
+
+
+
+ -
+
+
+
+ 0
+ 0
+
+
+
+
+ 95
+ 36
+
+
+
+
+ 95
+ 36
+
+
+
+
+ Arial
+
+
+
+ 1
+
+
+ 180
+
+
+
+
+
+ -
+
+
+ 0
+
+
+ 10
+
+
+ 10
+
+
-
+
+
+
+ 0
+ 0
+
+
+
+
+ 0
+ 0
+
+
+
+
+ 16777215
+ 25
+
+
+
+
+ Arial
+
+
+
+ color: rgb(51, 51, 51)
+
+
+ Lean scale (applies to Faceshift users)
+
+
+ 15
+
+
+ leanScaleSpin
+
+
+
+ -
+
+
+
+ Arial
+
+
+
+ Qt::Horizontal
+
+
+ QSizePolicy::Expanding
+
+
+
+ 40
+ 10
+
+
+
+
+ -
+
+
+
+ 0
+ 0
+
+
+
+
+ 95
+ 36
+
+
+
+
+ 70
+ 16777215
+
+
+
+
+ Arial
+
+
+
+
+
+
+ -
+
+
+ 0
+
+
+ 10
+
+
+ 0
+
+
+ 10
+
+
-
+
+
+
+ Arial
+
+
+
+ color: rgb(51, 51, 51)
+
+
+ Avatar scale <span style=" color:#909090;">(default is 1.0)</span>
+
+
+ 15
+
+
+ avatarScaleSpin
+
+
+
+ -
+
+
+
+ Arial
+
+
+
+ Qt::Horizontal
+
+
+
+ 40
+ 20
+
+
+
+
+ -
+
+
+
+ 0
+ 0
+
+
+
+
+ 95
+ 36
+
+
+
+
+ 70
+ 16777215
+
+
+
+
+ Arial
+
+
+
+
+
+
+ -
+
+
+ 0
+
+
+ 10
+
+
+ 0
+
+
+ 10
+
+
-
+
+
+
+ Arial
+
+
+
+ color: rgb(51, 51, 51)
+
+
+ Pupil dillation
+
+
+ 15
+
+
+ pupilDilationSlider
+
+
+
+ -
+
+
+
+ Arial
+
+
+
+ Qt::Horizontal
+
+
+
+ 40
+ 20
+
+
+
+
+ -
+
+
+
+ 0
+ 0
+
+
+
+
+ 125
+ 0
+
+
+
+
+ Arial
+
+
+
+ Qt::Horizontal
+
+
+
+
+
+ -
+
+
+ 0
+
+
+ 10
+
+
+ 0
+
+
+ 10
+
+
-
+
+
+
+ Arial
+
+
+
+ color: rgb(51, 51, 51)
+
+
+ Audio Jitter Buffer Samples (0 for automatic)
+
+
+ 15
+
+
+ audioJitterSpin
+
+
+
+ -
+
+
+
+ Arial
+
+
+
+ Qt::Horizontal
+
+
+
+ 40
+ 20
+
+
+
+
+ -
+
+
+
+ 0
+ 0
+
+
+
+
+ 95
+ 36
+
+
+
+
+ 70
+ 16777215
+
+
+
+
+ Arial
+
+
+
+ -10000
+
+
+ 10000
+
+
+ 1
+
+
+
+
+
+ -
+
+
+ 0
+
+
+ 10
+
+
+ 0
+
+
+ 10
+
+
-
+
+
+
+ Arial
+
+
+
+ color: rgb(51, 51, 51)
+
+
+ Faceshift eye detection
+
+
+ 15
+
+
+ faceshiftEyeDeflectionSider
+
+
+
+ -
+
+
+
+ Arial
+
+
+
+ Qt::Horizontal
+
+
+
+ 40
+ 20
+
+
+
+
+ -
+
+
+
+ 0
+ 0
+
+
+
+
+ 125
+ 0
+
+
+
+
+ Arial
+
+
+
+ Qt::Horizontal
+
+
+
+
+
+ -
+
+
+
+ 0
+ 0
+
+
+
+
+ 0
+ 40
+
+
+
+
+ Arial
+ 20
+ 50
+ false
+
+
+
+ color: #0e7077
+
+
+ Voxels
+
+
+ Qt::AlignBottom|Qt::AlignLeading|Qt::AlignLeft
+
+
+
+ -
+
+
+ 0
+
+
+ 10
+
+
+ 0
+
+
+ 10
+
+
-
+
+
+
+ Arial
+
+
+
+ color: rgb(51, 51, 51)
+
+
+ Maximum voxels
+
+
+ 15
+
+
+ maxVoxelsSpin
+
+
+
+ -
+
+
+
+ Arial
+
+
+
+ Qt::Horizontal
+
+
+
+ 40
+ 20
+
+
+
+
+ -
+
+
+
+ 0
+ 0
+
+
+
+
+ 125
+ 36
+
+
+
+
+ Arial
+
+
+
+ 5000000
+
+
+ 50000
+
+
+
+
+
+ -
+
+
+ 0
+
+
+ 10
+
+
+ 0
+
+
+ 10
+
+
-
+
+
+
+ Arial
+
+
+
+ Max voxels sent each second
+
+
+ 15
+
+
+ maxVoxelsPPSSpin
+
+
+
+ -
+
+
+
+ Arial
+
+
+
+ Qt::Horizontal
+
+
+
+ 40
+ 20
+
+
+
+
+ -
+
+
+
+ 0
+ 0
+
+
+
+
+ 95
+ 36
+
+
+
+
+ 70
+ 16777215
+
+
+
+
+ Arial
+
+
+
+ 60
+
+
+ 6000
+
+
+ 10
+
+
+
+
+
+
+
+
+
+
+
+ 540
+ 24
+ 31
+ 31
+
+
+
+
+ PreferAntialias
+
+
+
+
+
+
+
+
+
+ FramelessDialog
+ 1
+
+
+
+
+
+ cancelButton
+ clicked()
+ PreferencesDialog
+ close()
+
+
+ 495
+ 749
+
+
+ 528
+ 0
+
+
+
+
+ defaultButton
+ clicked()
+ PreferencesDialog
+ accept()
+
+
+ 504
+ 749
+
+
+ 20
+ 20
+
+
+
+
+
diff --git a/libraries/script-engine/src/ScriptEngine.cpp b/libraries/script-engine/src/ScriptEngine.cpp
index 3e63b26b0a..b5291a6a2f 100644
--- a/libraries/script-engine/src/ScriptEngine.cpp
+++ b/libraries/script-engine/src/ScriptEngine.cpp
@@ -95,8 +95,9 @@ ScriptEngine::ScriptEngine(const QUrl& scriptURL,
QUrl url(scriptURL);
- // if the scheme is empty, maybe they typed in a file, let's try
- if (url.scheme().isEmpty()) {
+ // if the scheme length is one or lower, maybe they typed in a file, let's try
+ const int WINDOWS_DRIVE_LETTER_SIZE = 1;
+ if (url.scheme().size() <= WINDOWS_DRIVE_LETTER_SIZE) {
url = QUrl::fromLocalFile(scriptURLString);
}
diff --git a/libraries/script-engine/src/ScriptEngine.h b/libraries/script-engine/src/ScriptEngine.h
index b90075e6a9..9719c83107 100644
--- a/libraries/script-engine/src/ScriptEngine.h
+++ b/libraries/script-engine/src/ScriptEngine.h
@@ -116,7 +116,6 @@ private:
static VoxelsScriptingInterface _voxelsScriptingInterface;
static ParticlesScriptingInterface _particlesScriptingInterface;
- static int _scriptNumber;
AbstractControllerScriptingInterface* _controllerScriptingInterface;
AudioScriptingInterface _audioScriptingInterface;
diff --git a/libraries/shared/src/PlaneShape.cpp b/libraries/shared/src/PlaneShape.cpp
new file mode 100644
index 0000000000..a8b4468c93
--- /dev/null
+++ b/libraries/shared/src/PlaneShape.cpp
@@ -0,0 +1,36 @@
+//
+// PlaneShape.cpp
+// libraries/shared/src
+//
+// Created by Andrzej Kapolka on 4/10/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 "PlaneShape.h"
+#include "SharedUtil.h"
+
+const glm::vec3 UNROTATED_NORMAL(0.0f, 1.0f, 0.0f);
+
+PlaneShape::PlaneShape(const glm::vec4& coefficients) :
+ Shape(Shape::PLANE_SHAPE) {
+
+ glm::vec3 normal = glm::vec3(coefficients);
+ _position = -normal * coefficients.w;
+
+ float angle = acosf(glm::dot(normal, UNROTATED_NORMAL));
+ if (angle > EPSILON) {
+ if (angle > PI - EPSILON) {
+ _rotation = glm::angleAxis(PI, glm::vec3(1.0f, 0.0f, 0.0f));
+ } else {
+ _rotation = glm::angleAxis(angle, glm::normalize(glm::cross(UNROTATED_NORMAL, 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));
+}
diff --git a/libraries/shared/src/PlaneShape.h b/libraries/shared/src/PlaneShape.h
new file mode 100644
index 0000000000..524d53ec73
--- /dev/null
+++ b/libraries/shared/src/PlaneShape.h
@@ -0,0 +1,24 @@
+//
+// PlaneShape.h
+// libraries/shared/src
+//
+// Created by Andrzej Kapolka on 4/9/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_PlaneShape_h
+#define hifi_PlaneShape_h
+
+#include "Shape.h"
+
+class PlaneShape : public Shape {
+public:
+ PlaneShape(const glm::vec4& coefficients = glm::vec4(0.0f, 1.0f, 0.0f, 0.0f));
+
+ glm::vec4 getCoefficients() const;
+};
+
+#endif // hifi_PlaneShape_h
diff --git a/libraries/shared/src/Shape.h b/libraries/shared/src/Shape.h
index fd16eafeae..87b84ea73b 100644
--- a/libraries/shared/src/Shape.h
+++ b/libraries/shared/src/Shape.h
@@ -22,6 +22,7 @@ public:
UNKNOWN_SHAPE = 0,
SPHERE_SHAPE,
CAPSULE_SHAPE,
+ PLANE_SHAPE,
BOX_SHAPE,
LIST_SHAPE
};
diff --git a/libraries/shared/src/ShapeCollider.cpp b/libraries/shared/src/ShapeCollider.cpp
index b13775dfa8..c53c7fab7d 100644
--- a/libraries/shared/src/ShapeCollider.cpp
+++ b/libraries/shared/src/ShapeCollider.cpp
@@ -13,6 +13,7 @@
#include
+#include "GeometryUtil.h"
#include "ShapeCollider.h"
// NOTE:
@@ -22,7 +23,7 @@
namespace ShapeCollider {
-bool shapeShape(const Shape* shapeA, const Shape* shapeB, CollisionList& collisions) {
+bool collideShapes(const Shape* shapeA, const Shape* shapeB, CollisionList& collisions) {
// ATM we only have two shape types so we just check every case.
// TODO: make a fast lookup for correct method
int typeA = shapeA->getType();
@@ -33,6 +34,8 @@ bool shapeShape(const Shape* shapeA, const Shape* shapeB, CollisionList& collisi
return sphereSphere(sphereA, static_cast(shapeB), collisions);
} else if (typeB == Shape::CAPSULE_SHAPE) {
return sphereCapsule(sphereA, static_cast(shapeB), collisions);
+ } else if (typeB == Shape::PLANE_SHAPE) {
+ return spherePlane(sphereA, static_cast(shapeB), collisions);
}
} else if (typeA == Shape::CAPSULE_SHAPE) {
const CapsuleShape* capsuleA = static_cast(shapeA);
@@ -40,6 +43,17 @@ bool shapeShape(const Shape* shapeA, const Shape* shapeB, CollisionList& collisi
return capsuleSphere(capsuleA, static_cast(shapeB), collisions);
} else if (typeB == Shape::CAPSULE_SHAPE) {
return capsuleCapsule(capsuleA, static_cast(shapeB), collisions);
+ } else if (typeB == Shape::PLANE_SHAPE) {
+ return capsulePlane(capsuleA, static_cast(shapeB), collisions);
+ }
+ } else if (typeA == Shape::PLANE_SHAPE) {
+ const PlaneShape* planeA = static_cast(shapeA);
+ if (typeB == Shape::SPHERE_SHAPE) {
+ return planeSphere(planeA, static_cast(shapeB), collisions);
+ } else if (typeB == Shape::CAPSULE_SHAPE) {
+ return planeCapsule(planeA, static_cast(shapeB), collisions);
+ } else if (typeB == Shape::PLANE_SHAPE) {
+ return planePlane(planeA, static_cast(shapeB), collisions);
}
} else if (typeA == Shape::LIST_SHAPE) {
const ListShape* listA = static_cast(shapeA);
@@ -47,11 +61,37 @@ bool shapeShape(const Shape* shapeA, const Shape* shapeB, CollisionList& collisi
return listSphere(listA, static_cast(shapeB), collisions);
} else if (typeB == Shape::CAPSULE_SHAPE) {
return listCapsule(listA, static_cast(shapeB), collisions);
+ } else if (typeB == Shape::PLANE_SHAPE) {
+ return listPlane(listA, static_cast(shapeB), collisions);
}
}
return false;
}
+static CollisionList tempCollisions(32);
+
+bool collideShapesCoarse(const QVector& shapesA, const QVector& shapesB, CollisionInfo& collision) {
+ tempCollisions.clear();
+ foreach (const Shape* shapeA, shapesA) {
+ foreach (const Shape* shapeB, shapesB) {
+ ShapeCollider::collideShapes(shapeA, shapeB, tempCollisions);
+ }
+ }
+ if (tempCollisions.size() > 0) {
+ glm::vec3 totalPenetration(0.0f);
+ glm::vec3 averageContactPoint(0.0f);
+ for (int j = 0; j < tempCollisions.size(); ++j) {
+ CollisionInfo* c = tempCollisions.getCollision(j);
+ totalPenetration = addPenetrations(totalPenetration, c->_penetration);
+ averageContactPoint += c->_contactPoint;
+ }
+ collision._penetration = totalPenetration;
+ collision._contactPoint = averageContactPoint / (float)(tempCollisions.size());
+ return true;
+ }
+ return false;
+}
+
bool sphereSphere(const SphereShape* sphereA, const SphereShape* sphereB, CollisionList& collisions) {
glm::vec3 BA = sphereB->getPosition() - sphereA->getPosition();
float distanceSquared = glm::dot(BA, BA);
@@ -61,7 +101,7 @@ bool sphereSphere(const SphereShape* sphereA, const SphereShape* sphereB, Collis
float distance = sqrtf(distanceSquared);
if (distance < EPSILON) {
// the spheres are on top of each other, so we pick an arbitrary penetration direction
- BA = glm::vec3(0.f, 1.f, 0.f);
+ BA = glm::vec3(0.0f, 1.0f, 0.0f);
distance = totalRadius;
} else {
BA /= distance;
@@ -96,7 +136,7 @@ bool sphereCapsule(const SphereShape* sphereA, const CapsuleShape* capsuleB, Col
}
if (absAxialDistance > capsuleB->getHalfHeight()) {
// sphere hits capsule on a cap --> recompute radialAxis to point from spherA to cap center
- float sign = (axialDistance > 0.f) ? 1.f : -1.f;
+ float sign = (axialDistance > 0.0f) ? 1.0f : -1.0f;
radialAxis = BA + (sign * capsuleB->getHalfHeight()) * capsuleAxis;
radialDistance2 = glm::length2(radialAxis);
if (radialDistance2 > totalRadius2) {
@@ -128,12 +168,12 @@ bool sphereCapsule(const SphereShape* sphereA, const CapsuleShape* capsuleB, Col
return false;
}
// ... but still defined for the cap case
- if (axialDistance < 0.f) {
+ if (axialDistance < 0.0f) {
// we're hitting the start cap, so we negate the capsuleAxis
capsuleAxis *= -1;
}
// penetration points from A into B
- float sign = (axialDistance > 0.f) ? -1.f : 1.f;
+ float sign = (axialDistance > 0.0f) ? -1.0f : 1.0f;
collision->_penetration = (sign * (totalRadius + capsuleB->getHalfHeight() - absAxialDistance)) * capsuleAxis;
// contactPoint is on surface of sphereA
collision->_contactPoint = sphereA->getPosition() + (sign * sphereA->getRadius()) * capsuleAxis;
@@ -143,6 +183,20 @@ bool sphereCapsule(const SphereShape* sphereA, const CapsuleShape* capsuleB, Col
return false;
}
+bool spherePlane(const SphereShape* sphereA, const PlaneShape* planeB, CollisionList& collisions) {
+ glm::vec3 penetration;
+ if (findSpherePlanePenetration(sphereA->getPosition(), sphereA->getRadius(), planeB->getCoefficients(), penetration)) {
+ CollisionInfo* collision = collisions.getNewCollision();
+ if (!collision) {
+ return false; // collision list is full
+ }
+ collision->_penetration = penetration;
+ collision->_contactPoint = sphereA->getPosition() + sphereA->getRadius() * glm::normalize(penetration);
+ return true;
+ }
+ return false;
+}
+
bool capsuleSphere(const CapsuleShape* capsuleA, const SphereShape* sphereB, CollisionList& collisions) {
// find sphereB's closest approach to axis of capsuleA
glm::vec3 AB = capsuleA->getPosition() - sphereB->getPosition();
@@ -166,7 +220,7 @@ bool capsuleSphere(const CapsuleShape* capsuleA, const SphereShape* sphereB, Col
if (absAxialDistance > capsuleA->getHalfHeight()) {
// sphere hits capsule on a cap
// --> recompute radialAxis and closestApproach
- float sign = (axialDistance > 0.f) ? 1.f : -1.f;
+ float sign = (axialDistance > 0.0f) ? 1.0f : -1.0f;
closestApproach = capsuleA->getPosition() + (sign * capsuleA->getHalfHeight()) * capsuleAxis;
radialAxis = closestApproach - sphereB->getPosition();
radialDistance2 = glm::length2(radialAxis);
@@ -199,11 +253,11 @@ bool capsuleSphere(const CapsuleShape* capsuleA, const SphereShape* sphereB, Col
return false;
}
// ... but still defined for the cap case
- if (axialDistance < 0.f) {
+ if (axialDistance < 0.0f) {
// we're hitting the start cap, so we negate the capsuleAxis
capsuleAxis *= -1;
}
- float sign = (axialDistance > 0.f) ? 1.f : -1.f;
+ float sign = (axialDistance > 0.0f) ? 1.0f : -1.0f;
collision->_penetration = (sign * (totalRadius + capsuleA->getHalfHeight() - absAxialDistance)) * capsuleAxis;
// contactPoint is on surface of sphereA
collision->_contactPoint = closestApproach + (sign * capsuleA->getRadius()) * capsuleAxis;
@@ -226,7 +280,7 @@ bool capsuleCapsule(const CapsuleShape* capsuleA, const CapsuleShape* capsuleB,
// d = [(B - A) . (a - (a.b)b)] / (1 - (a.b)^2)
float aDotB = glm::dot(axisA, axisB);
- float denominator = 1.f - aDotB * aDotB;
+ float denominator = 1.0f - aDotB * aDotB;
float totalRadius = capsuleA->getRadius() + capsuleB->getRadius();
if (denominator > EPSILON) {
// distances to points of closest approach
@@ -236,12 +290,12 @@ bool capsuleCapsule(const CapsuleShape* capsuleA, const CapsuleShape* capsuleB,
// clamp the distances to the ends of the capsule line segments
float absDistanceA = fabs(distanceA);
if (absDistanceA > capsuleA->getHalfHeight() + capsuleA->getRadius()) {
- float signA = distanceA < 0.f ? -1.f : 1.f;
+ float signA = distanceA < 0.0f ? -1.0f : 1.0f;
distanceA = signA * capsuleA->getHalfHeight();
}
float absDistanceB = fabs(distanceB);
if (absDistanceB > capsuleB->getHalfHeight() + capsuleB->getRadius()) {
- float signB = distanceB < 0.f ? -1.f : 1.f;
+ float signB = distanceB < 0.0f ? -1.0f : 1.0f;
distanceB = signB * capsuleB->getHalfHeight();
}
@@ -268,7 +322,7 @@ bool capsuleCapsule(const CapsuleShape* capsuleA, const CapsuleShape* capsuleB,
{
// the capsule centers are on top of each other!
// give up on a valid penetration direction and just use the yAxis
- BA = glm::vec3(0.f, 1.f, 0.f);
+ BA = glm::vec3(0.0f, 1.0f, 0.0f);
distance = glm::max(capsuleB->getRadius(), capsuleA->getRadius());
}
} else {
@@ -300,7 +354,7 @@ bool capsuleCapsule(const CapsuleShape* capsuleA, const CapsuleShape* capsuleB,
float distance = sqrtf(distanceSquared);
if (distance < EPSILON) {
// the spheres are on top of each other, so we pick an arbitrary penetration direction
- BA = glm::vec3(0.f, 1.f, 0.f);
+ BA = glm::vec3(0.0f, 1.0f, 0.0f);
} else {
BA /= distance;
}
@@ -349,6 +403,63 @@ bool capsuleCapsule(const CapsuleShape* capsuleA, const CapsuleShape* capsuleB,
return false;
}
+bool capsulePlane(const CapsuleShape* capsuleA, const PlaneShape* planeB, CollisionList& collisions) {
+ glm::vec3 start, end, penetration;
+ capsuleA->getStartPoint(start);
+ capsuleA->getEndPoint(end);
+ glm::vec4 plane = planeB->getCoefficients();
+ if (findCapsulePlanePenetration(start, end, capsuleA->getRadius(), plane, penetration)) {
+ CollisionInfo* collision = collisions.getNewCollision();
+ if (!collision) {
+ return false; // collision list is full
+ }
+ collision->_penetration = penetration;
+ glm::vec3 deepestEnd = (glm::dot(start, glm::vec3(plane)) < glm::dot(end, glm::vec3(plane))) ? start : end;
+ collision->_contactPoint = deepestEnd + capsuleA->getRadius() * glm::normalize(penetration);
+ return true;
+ }
+ return false;
+}
+
+bool planeSphere(const PlaneShape* planeA, const SphereShape* sphereB, CollisionList& collisions) {
+ glm::vec3 penetration;
+ if (findSpherePlanePenetration(sphereB->getPosition(), sphereB->getRadius(), planeA->getCoefficients(), penetration)) {
+ CollisionInfo* collision = collisions.getNewCollision();
+ if (!collision) {
+ return false; // collision list is full
+ }
+ collision->_penetration = -penetration;
+ collision->_contactPoint = sphereB->getPosition() +
+ (sphereB->getRadius() / glm::length(penetration) - 1.0f) * penetration;
+ return true;
+ }
+ return false;
+}
+
+bool planeCapsule(const PlaneShape* planeA, const CapsuleShape* capsuleB, CollisionList& collisions) {
+ glm::vec3 start, end, penetration;
+ capsuleB->getStartPoint(start);
+ capsuleB->getEndPoint(end);
+ glm::vec4 plane = planeA->getCoefficients();
+ if (findCapsulePlanePenetration(start, end, capsuleB->getRadius(), plane, penetration)) {
+ CollisionInfo* collision = collisions.getNewCollision();
+ if (!collision) {
+ return false; // collision list is full
+ }
+ collision->_penetration = -penetration;
+ glm::vec3 deepestEnd = (glm::dot(start, glm::vec3(plane)) < glm::dot(end, glm::vec3(plane))) ? start : end;
+ collision->_contactPoint = deepestEnd + (capsuleB->getRadius() / glm::length(penetration) - 1.0f) * penetration;
+ return true;
+ }
+ return false;
+}
+
+bool planePlane(const PlaneShape* planeA, const PlaneShape* planeB, CollisionList& collisions) {
+ // technically, planes always collide unless they're parallel and not coincident; however, that's
+ // not going to give us any useful information
+ return false;
+}
+
bool sphereList(const SphereShape* sphereA, const ListShape* listB, CollisionList& collisions) {
bool touching = false;
for (int i = 0; i < listB->size() && !collisions.isFull(); ++i) {
@@ -358,6 +469,8 @@ bool sphereList(const SphereShape* sphereA, const ListShape* listB, CollisionLis
touching = sphereSphere(sphereA, static_cast(subShape), collisions) || touching;
} else if (subType == Shape::CAPSULE_SHAPE) {
touching = sphereCapsule(sphereA, static_cast(subShape), collisions) || touching;
+ } else if (subType == Shape::PLANE_SHAPE) {
+ touching = spherePlane(sphereA, static_cast(subShape), collisions) || touching;
}
}
return touching;
@@ -372,6 +485,24 @@ bool capsuleList(const CapsuleShape* capsuleA, const ListShape* listB, Collision
touching = capsuleSphere(capsuleA, static_cast(subShape), collisions) || touching;
} else if (subType == Shape::CAPSULE_SHAPE) {
touching = capsuleCapsule(capsuleA, static_cast(subShape), collisions) || touching;
+ } else if (subType == Shape::PLANE_SHAPE) {
+ touching = capsulePlane(capsuleA, static_cast(subShape), collisions) || touching;
+ }
+ }
+ return touching;
+}
+
+bool planeList(const PlaneShape* planeA, const ListShape* listB, CollisionList& collisions) {
+ bool touching = false;
+ for (int i = 0; i < listB->size() && !collisions.isFull(); ++i) {
+ const Shape* subShape = listB->getSubShape(i);
+ int subType = subShape->getType();
+ if (subType == Shape::SPHERE_SHAPE) {
+ touching = planeSphere(planeA, static_cast(subShape), collisions) || touching;
+ } else if (subType == Shape::CAPSULE_SHAPE) {
+ touching = planeCapsule(planeA, static_cast(subShape), collisions) || touching;
+ } else if (subType == Shape::PLANE_SHAPE) {
+ touching = planePlane(planeA, static_cast(subShape), collisions) || touching;
}
}
return touching;
@@ -386,6 +517,8 @@ bool listSphere(const ListShape* listA, const SphereShape* sphereB, CollisionLis
touching = sphereSphere(static_cast(subShape), sphereB, collisions) || touching;
} else if (subType == Shape::CAPSULE_SHAPE) {
touching = capsuleSphere(static_cast(subShape), sphereB, collisions) || touching;
+ } else if (subType == Shape::PLANE_SHAPE) {
+ touching = planeSphere(static_cast(subShape), sphereB, collisions) || touching;
}
}
return touching;
@@ -400,6 +533,24 @@ bool listCapsule(const ListShape* listA, const CapsuleShape* capsuleB, Collision
touching = sphereCapsule(static_cast(subShape), capsuleB, collisions) || touching;
} else if (subType == Shape::CAPSULE_SHAPE) {
touching = capsuleCapsule(static_cast(subShape), capsuleB, collisions) || touching;
+ } else if (subType == Shape::PLANE_SHAPE) {
+ touching = planeCapsule(static_cast(subShape), capsuleB, collisions) || touching;
+ }
+ }
+ return touching;
+}
+
+bool listPlane(const ListShape* listA, const PlaneShape* planeB, CollisionList& collisions) {
+ bool touching = false;
+ for (int i = 0; i < listA->size() && !collisions.isFull(); ++i) {
+ const Shape* subShape = listA->getSubShape(i);
+ int subType = subShape->getType();
+ if (subType == Shape::SPHERE_SHAPE) {
+ touching = spherePlane(static_cast(subShape), planeB, collisions) || touching;
+ } else if (subType == Shape::CAPSULE_SHAPE) {
+ touching = capsulePlane(static_cast(subShape), planeB, collisions) || touching;
+ } else if (subType == Shape::PLANE_SHAPE) {
+ touching = planePlane(static_cast(subShape), planeB, collisions) || touching;
}
}
return touching;
@@ -410,7 +561,7 @@ bool listList(const ListShape* listA, const ListShape* listB, CollisionList& col
for (int i = 0; i < listA->size() && !collisions.isFull(); ++i) {
const Shape* subShape = listA->getSubShape(i);
for (int j = 0; j < listB->size() && !collisions.isFull(); ++j) {
- touching = shapeShape(subShape, listB->getSubShape(j), collisions) || touching;
+ touching = collideShapes(subShape, listB->getSubShape(j), collisions) || touching;
}
}
return touching;
diff --git a/libraries/shared/src/ShapeCollider.h b/libraries/shared/src/ShapeCollider.h
index 7f02acf15c..d554775e7b 100644
--- a/libraries/shared/src/ShapeCollider.h
+++ b/libraries/shared/src/ShapeCollider.h
@@ -15,6 +15,7 @@
#include "CapsuleShape.h"
#include "CollisionInfo.h"
#include "ListShape.h"
+#include "PlaneShape.h"
#include "SharedUtil.h"
#include "SphereShape.h"
@@ -22,9 +23,15 @@ namespace ShapeCollider {
/// \param shapeA pointer to first shape
/// \param shapeB pointer to second shape
- /// \param[out] collisions where to append collision details
+ /// \param collisions[out] collision details
/// \return true if shapes collide
- bool shapeShape(const Shape* shapeA, const Shape* shapeB, CollisionList& collisions);
+ bool collideShapes(const Shape* shapeA, const Shape* shapeB, CollisionList& collisions);
+
+ /// \param shapesA list of shapes
+ /// \param shapeB list of shapes
+ /// \param collisions[out] average collision details
+ /// \return true if any shapes collide
+ bool collideShapesCoarse(const QVector& shapesA, const QVector& shapesB, CollisionInfo& collision);
/// \param sphereA pointer to first shape
/// \param sphereB pointer to second shape
@@ -38,6 +45,12 @@ namespace ShapeCollider {
/// \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[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[out] collisions where to append collision details
@@ -50,6 +63,30 @@ namespace ShapeCollider {
/// \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[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[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[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[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[out] collisions where to append collision details
@@ -62,6 +99,12 @@ namespace ShapeCollider {
/// \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[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[out] collisions where to append collision details
@@ -74,6 +117,12 @@ namespace ShapeCollider {
/// \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[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[out] collisions where to append collision details
diff --git a/tests/physics/src/ShapeColliderTests.cpp b/tests/physics/src/ShapeColliderTests.cpp
index 2c25decdee..f9e76bac0b 100644
--- a/tests/physics/src/ShapeColliderTests.cpp
+++ b/tests/physics/src/ShapeColliderTests.cpp
@@ -43,7 +43,7 @@ void ShapeColliderTests::sphereMissesSphere() {
// collide A to B...
{
- bool touching = ShapeCollider::shapeShape(&sphereA, &sphereB, collisions);
+ bool touching = ShapeCollider::collideShapes(&sphereA, &sphereB, collisions);
if (touching) {
std::cout << __FILE__ << ":" << __LINE__
<< " ERROR: sphereA and sphereB should NOT touch" << std::endl;
@@ -52,7 +52,7 @@ void ShapeColliderTests::sphereMissesSphere() {
// collide B to A...
{
- bool touching = ShapeCollider::shapeShape(&sphereB, &sphereA, collisions);
+ bool touching = ShapeCollider::collideShapes(&sphereB, &sphereA, collisions);
if (touching) {
std::cout << __FILE__ << ":" << __LINE__
<< " ERROR: sphereA and sphereB should NOT touch" << std::endl;
@@ -61,7 +61,7 @@ void ShapeColliderTests::sphereMissesSphere() {
// also test shapeShape
{
- bool touching = ShapeCollider::shapeShape(&sphereB, &sphereA, collisions);
+ bool touching = ShapeCollider::collideShapes(&sphereB, &sphereA, collisions);
if (touching) {
std::cout << __FILE__ << ":" << __LINE__
<< " ERROR: sphereA and sphereB should NOT touch" << std::endl;
@@ -93,7 +93,7 @@ void ShapeColliderTests::sphereTouchesSphere() {
// collide A to B...
{
- bool touching = ShapeCollider::shapeShape(&sphereA, &sphereB, collisions);
+ bool touching = ShapeCollider::collideShapes(&sphereA, &sphereB, collisions);
if (!touching) {
std::cout << __FILE__ << ":" << __LINE__
<< " ERROR: sphereA and sphereB should touch" << std::endl;
@@ -136,7 +136,7 @@ void ShapeColliderTests::sphereTouchesSphere() {
// collide B to A...
{
- bool touching = ShapeCollider::shapeShape(&sphereB, &sphereA, collisions);
+ bool touching = ShapeCollider::collideShapes(&sphereB, &sphereA, collisions);
if (!touching) {
std::cout << __FILE__ << ":" << __LINE__
<< " ERROR: sphereA and sphereB should touch" << std::endl;
@@ -199,7 +199,7 @@ void ShapeColliderTests::sphereMissesCapsule() {
sphereA.setPosition(rotation * localPosition + translation);
// sphereA agains capsuleB
- if (ShapeCollider::shapeShape(&sphereA, &capsuleB, collisions))
+ if (ShapeCollider::collideShapes(&sphereA, &capsuleB, collisions))
{
std::cout << __FILE__ << ":" << __LINE__
<< " ERROR: sphere and capsule should NOT touch"
@@ -207,7 +207,7 @@ void ShapeColliderTests::sphereMissesCapsule() {
}
// capsuleB against sphereA
- if (ShapeCollider::shapeShape(&capsuleB, &sphereA, collisions))
+ if (ShapeCollider::collideShapes(&capsuleB, &sphereA, collisions))
{
std::cout << __FILE__ << ":" << __LINE__
<< " ERROR: sphere and capsule should NOT touch"
@@ -241,7 +241,7 @@ void ShapeColliderTests::sphereTouchesCapsule() {
{ // sphereA collides with capsuleB's cylindrical wall
sphereA.setPosition(radialOffset * xAxis);
- if (!ShapeCollider::shapeShape(&sphereA, &capsuleB, collisions))
+ if (!ShapeCollider::collideShapes(&sphereA, &capsuleB, collisions))
{
std::cout << __FILE__ << ":" << __LINE__
<< " ERROR: sphere and capsule should touch"
@@ -272,7 +272,7 @@ void ShapeColliderTests::sphereTouchesCapsule() {
}
// capsuleB collides with sphereA
- if (!ShapeCollider::shapeShape(&capsuleB, &sphereA, collisions))
+ if (!ShapeCollider::collideShapes(&capsuleB, &sphereA, collisions))
{
std::cout << __FILE__ << ":" << __LINE__
<< " ERROR: capsule and sphere should touch"
@@ -308,7 +308,7 @@ void ShapeColliderTests::sphereTouchesCapsule() {
glm::vec3 axialOffset = (halfHeightB + alpha * radiusA + beta * radiusB) * yAxis;
sphereA.setPosition(axialOffset * yAxis);
- if (!ShapeCollider::shapeShape(&sphereA, &capsuleB, collisions))
+ if (!ShapeCollider::collideShapes(&sphereA, &capsuleB, collisions))
{
std::cout << __FILE__ << ":" << __LINE__
<< " ERROR: sphere and capsule should touch"
@@ -339,7 +339,7 @@ void ShapeColliderTests::sphereTouchesCapsule() {
}
// capsuleB collides with sphereA
- if (!ShapeCollider::shapeShape(&capsuleB, &sphereA, collisions))
+ if (!ShapeCollider::collideShapes(&capsuleB, &sphereA, collisions))
{
std::cout << __FILE__ << ":" << __LINE__
<< " ERROR: capsule and sphere should touch"
@@ -375,7 +375,7 @@ void ShapeColliderTests::sphereTouchesCapsule() {
glm::vec3 axialOffset = - (halfHeightB + alpha * radiusA + beta * radiusB) * yAxis;
sphereA.setPosition(axialOffset * yAxis);
- if (!ShapeCollider::shapeShape(&sphereA, &capsuleB, collisions))
+ if (!ShapeCollider::collideShapes(&sphereA, &capsuleB, collisions))
{
std::cout << __FILE__ << ":" << __LINE__
<< " ERROR: sphere and capsule should touch"
@@ -406,7 +406,7 @@ void ShapeColliderTests::sphereTouchesCapsule() {
}
// capsuleB collides with sphereA
- if (!ShapeCollider::shapeShape(&capsuleB, &sphereA, collisions))
+ if (!ShapeCollider::collideShapes(&capsuleB, &sphereA, collisions))
{
std::cout << __FILE__ << ":" << __LINE__
<< " ERROR: capsule and sphere should touch"
@@ -462,13 +462,13 @@ void ShapeColliderTests::capsuleMissesCapsule() {
// side by side
capsuleB.setPosition((1.01f * totalRadius) * xAxis);
- if (ShapeCollider::shapeShape(&capsuleA, &capsuleB, collisions))
+ if (ShapeCollider::collideShapes(&capsuleA, &capsuleB, collisions))
{
std::cout << __FILE__ << ":" << __LINE__
<< " ERROR: capsule and capsule should NOT touch"
<< std::endl;
}
- if (ShapeCollider::shapeShape(&capsuleB, &capsuleA, collisions))
+ if (ShapeCollider::collideShapes(&capsuleB, &capsuleA, collisions))
{
std::cout << __FILE__ << ":" << __LINE__
<< " ERROR: capsule and capsule should NOT touch"
@@ -477,13 +477,13 @@ void ShapeColliderTests::capsuleMissesCapsule() {
// end to end
capsuleB.setPosition((1.01f * totalHalfLength) * xAxis);
- if (ShapeCollider::shapeShape(&capsuleA, &capsuleB, collisions))
+ if (ShapeCollider::collideShapes(&capsuleA, &capsuleB, collisions))
{
std::cout << __FILE__ << ":" << __LINE__
<< " ERROR: capsule and capsule should NOT touch"
<< std::endl;
}
- if (ShapeCollider::shapeShape(&capsuleB, &capsuleA, collisions))
+ if (ShapeCollider::collideShapes(&capsuleB, &capsuleA, collisions))
{
std::cout << __FILE__ << ":" << __LINE__
<< " ERROR: capsule and capsule should NOT touch"
@@ -494,13 +494,13 @@ void ShapeColliderTests::capsuleMissesCapsule() {
glm::quat rotation = glm::angleAxis(PI_OVER_TWO, zAxis);
capsuleB.setRotation(rotation);
capsuleB.setPosition((1.01f * (totalRadius + capsuleB.getHalfHeight())) * xAxis);
- if (ShapeCollider::shapeShape(&capsuleA, &capsuleB, collisions))
+ if (ShapeCollider::collideShapes(&capsuleA, &capsuleB, collisions))
{
std::cout << __FILE__ << ":" << __LINE__
<< " ERROR: capsule and capsule should NOT touch"
<< std::endl;
}
- if (ShapeCollider::shapeShape(&capsuleB, &capsuleA, collisions))
+ if (ShapeCollider::collideShapes(&capsuleB, &capsuleA, collisions))
{
std::cout << __FILE__ << ":" << __LINE__
<< " ERROR: capsule and capsule should NOT touch"
@@ -532,7 +532,7 @@ void ShapeColliderTests::capsuleTouchesCapsule() {
{ // side by side
capsuleB.setPosition((0.99f * totalRadius) * xAxis);
- if (!ShapeCollider::shapeShape(&capsuleA, &capsuleB, collisions))
+ if (!ShapeCollider::collideShapes(&capsuleA, &capsuleB, collisions))
{
std::cout << __FILE__ << ":" << __LINE__
<< " ERROR: capsule and capsule should touch"
@@ -540,7 +540,7 @@ void ShapeColliderTests::capsuleTouchesCapsule() {
} else {
++numCollisions;
}
- if (!ShapeCollider::shapeShape(&capsuleB, &capsuleA, collisions))
+ if (!ShapeCollider::collideShapes(&capsuleB, &capsuleA, collisions))
{
std::cout << __FILE__ << ":" << __LINE__
<< " ERROR: capsule and capsule should touch"
@@ -553,7 +553,7 @@ void ShapeColliderTests::capsuleTouchesCapsule() {
{ // end to end
capsuleB.setPosition((0.99f * totalHalfLength) * yAxis);
- if (!ShapeCollider::shapeShape(&capsuleA, &capsuleB, collisions))
+ if (!ShapeCollider::collideShapes(&capsuleA, &capsuleB, collisions))
{
std::cout << __FILE__ << ":" << __LINE__
<< " ERROR: capsule and capsule should touch"
@@ -561,7 +561,7 @@ void ShapeColliderTests::capsuleTouchesCapsule() {
} else {
++numCollisions;
}
- if (!ShapeCollider::shapeShape(&capsuleB, &capsuleA, collisions))
+ if (!ShapeCollider::collideShapes(&capsuleB, &capsuleA, collisions))
{
std::cout << __FILE__ << ":" << __LINE__
<< " ERROR: capsule and capsule should touch"
@@ -576,7 +576,7 @@ void ShapeColliderTests::capsuleTouchesCapsule() {
capsuleB.setRotation(rotation);
capsuleB.setPosition((0.99f * (totalRadius + capsuleB.getHalfHeight())) * xAxis);
- if (!ShapeCollider::shapeShape(&capsuleA, &capsuleB, collisions))
+ if (!ShapeCollider::collideShapes(&capsuleA, &capsuleB, collisions))
{
std::cout << __FILE__ << ":" << __LINE__
<< " ERROR: capsule and capsule should touch"
@@ -584,7 +584,7 @@ void ShapeColliderTests::capsuleTouchesCapsule() {
} else {
++numCollisions;
}
- if (!ShapeCollider::shapeShape(&capsuleB, &capsuleA, collisions))
+ if (!ShapeCollider::collideShapes(&capsuleB, &capsuleA, collisions))
{
std::cout << __FILE__ << ":" << __LINE__
<< " ERROR: capsule and capsule should touch"
@@ -602,7 +602,7 @@ void ShapeColliderTests::capsuleTouchesCapsule() {
capsuleB.setPosition(positionB);
// capsuleA vs capsuleB
- if (!ShapeCollider::shapeShape(&capsuleA, &capsuleB, collisions))
+ if (!ShapeCollider::collideShapes(&capsuleA, &capsuleB, collisions))
{
std::cout << __FILE__ << ":" << __LINE__
<< " ERROR: capsule and capsule should touch"
@@ -631,7 +631,7 @@ void ShapeColliderTests::capsuleTouchesCapsule() {
}
// capsuleB vs capsuleA
- if (!ShapeCollider::shapeShape(&capsuleB, &capsuleA, collisions))
+ if (!ShapeCollider::collideShapes(&capsuleB, &capsuleA, collisions))
{
std::cout << __FILE__ << ":" << __LINE__
<< " ERROR: capsule and capsule should touch"
@@ -669,7 +669,7 @@ void ShapeColliderTests::capsuleTouchesCapsule() {
capsuleB.setPosition(positionB);
// capsuleA vs capsuleB
- if (!ShapeCollider::shapeShape(&capsuleA, &capsuleB, collisions))
+ if (!ShapeCollider::collideShapes(&capsuleA, &capsuleB, collisions))
{
std::cout << __FILE__ << ":" << __LINE__
<< " ERROR: capsule and capsule should touch"