diff --git a/examples/Recorder.js b/examples/Recorder.js
index 8efa9408a9..40bf2d2ed1 100644
--- a/examples/Recorder.js
+++ b/examples/Recorder.js
@@ -40,6 +40,7 @@ var timerOffset;
setupToolBar();
var timer = null;
+var slider = null;
setupTimer();
var watchStop = false;
@@ -115,6 +116,30 @@ function setupTimer() {
alpha: 1.0,
visible: true
});
+
+ slider = { x: 0, y: 0,
+ w: 200, h: 20,
+ pos: 0.0, // 0.0 <= pos <= 1.0
+ };
+ slider.background = Overlays.addOverlay("text", {
+ text: "",
+ backgroundColor: { red: 128, green: 128, blue: 128 },
+ x: slider.x, y: slider.y,
+ width: slider.w,
+ height: slider.h,
+ alpha: 1.0,
+ visible: true
+ });
+ slider.foreground = Overlays.addOverlay("text", {
+ text: "",
+ backgroundColor: { red: 200, green: 200, blue: 200 },
+ x: slider.x, y: slider.y,
+ width: slider.pos * slider.w,
+ height: slider.h,
+ alpha: 1.0,
+ visible: true
+ });
+
}
function updateTimer() {
@@ -131,6 +156,16 @@ function updateTimer() {
text: text
})
toolBar.changeSpacing(text.length * 8 + ((MyAvatar.isRecording()) ? 15 : 0), spacing);
+
+ if (MyAvatar.isRecording()) {
+ slider.pos = 1.0;
+ } else if (MyAvatar.playerLength() > 0) {
+ slider.pos = MyAvatar.playerElapsed() / MyAvatar.playerLength();
+ }
+
+ Overlays.editOverlay(slider.foreground, {
+ width: slider.pos * slider.w
+ });
}
function formatTime(time) {
@@ -163,7 +198,19 @@ function moveUI() {
Overlays.editOverlay(timer, {
x: relative.x + timerOffset - ToolBar.SPACING,
y: windowDimensions.y - relative.y - ToolBar.SPACING
- });
+ });
+
+ slider.x = relative.x - ToolBar.SPACING;
+ slider.y = windowDimensions.y - relative.y - slider.h - ToolBar.SPACING;
+
+ Overlays.editOverlay(slider.background, {
+ x: slider.x,
+ y: slider.y,
+ });
+ Overlays.editOverlay(slider.foreground, {
+ x: slider.x,
+ y: slider.y,
+ });
}
function mousePressEvent(event) {
@@ -188,7 +235,7 @@ function mousePressEvent(event) {
}
} else if (playIcon === toolBar.clicked(clickedOverlay) && !MyAvatar.isRecording()) {
if (MyAvatar.isPlaying()) {
- MyAvatar.stopPlaying();
+ MyAvatar.pausePlayer();
toolBar.setAlpha(ALPHA_ON, recordIcon);
toolBar.setAlpha(ALPHA_ON, saveIcon);
toolBar.setAlpha(ALPHA_ON, loadIcon);
@@ -203,7 +250,7 @@ function mousePressEvent(event) {
}
} else if (playLoopIcon === toolBar.clicked(clickedOverlay) && !MyAvatar.isRecording()) {
if (MyAvatar.isPlaying()) {
- MyAvatar.stopPlaying();
+ MyAvatar.pausePlayer();
toolBar.setAlpha(ALPHA_ON, recordIcon);
toolBar.setAlpha(ALPHA_ON, saveIcon);
toolBar.setAlpha(ALPHA_ON, loadIcon);
@@ -234,10 +281,30 @@ function mousePressEvent(event) {
toolBar.setAlpha(ALPHA_ON, saveIcon);
}
}
- } else {
-
+ } else if (MyAvatar.playerLength() > 0 &&
+ slider.x < event.x && event.x < slider.x + slider.w &&
+ slider.y < event.y && event.y < slider.y + slider.h) {
+ isSliding = true;
+ slider.pos = (event.x - slider.x) / slider.w;
+ MyAvatar.setPlayerTime(slider.pos * MyAvatar.playerLength());
}
}
+var isSliding = false;
+
+function mouseMoveEvent(event) {
+ if (isSliding) {
+ slider.pos = (event.x - slider.x) / slider.w;
+ if (slider.pos < 0.0 || slider.pos > 1.0) {
+ MyAvatar.stopPlaying();
+ slider.pos = 0.0;
+ }
+ MyAvatar.setPlayerTime(slider.pos * MyAvatar.playerLength());
+ }
+}
+
+function mouseReleaseEvent(event) {
+ isSliding = false;
+}
function update() {
var newDimensions = Controller.getViewportDimensions();
@@ -264,11 +331,15 @@ function scriptEnding() {
if (MyAvatar.isPlaying()) {
MyAvatar.stopPlaying();
}
- toolBar.cleanup();
- Overlays.deleteOverlay(timer);
+ toolBar.cleanup();
+ Overlays.deleteOverlay(timer);
+ Overlays.deleteOverlay(slider.background);
+ Overlays.deleteOverlay(slider.foreground);
}
Controller.mousePressEvent.connect(mousePressEvent);
+Controller.mouseMoveEvent.connect(mouseMoveEvent);
+Controller.mouseReleaseEvent.connect(mouseReleaseEvent);
Script.update.connect(update);
Script.scriptEnding.connect(scriptEnding);
diff --git a/examples/editModels.js b/examples/editModels.js
index 740c992888..43e73104e2 100644
--- a/examples/editModels.js
+++ b/examples/editModels.js
@@ -2449,6 +2449,7 @@ function Tooltip() {
text += "Lifetime: " + properties.lifetime + "\n"
}
text += "Age: " + properties.ageAsText + "\n"
+ text += "Mass: " + properties.mass + "\n"
text += "Script: " + properties.script + "\n"
@@ -2922,6 +2923,15 @@ function handeMenuEvent(menuItem) {
array.push({ label: "Gravity Z:", value: properties.gravity.z.toFixed(decimals) });
index++;
+ array.push({ label: "Collisions:", type: "header" });
+ index++;
+ array.push({ label: "Mass:", value: properties.mass.toFixed(decimals) });
+ index++;
+ array.push({ label: "Ignore for Collisions:", value: properties.ignoreForCollisions });
+ index++;
+ array.push({ label: "Collisions Will Move:", value: properties.collisionsWillMove });
+ index++;
+
array.push({ label: "Lifetime:", value: properties.lifetime.toFixed(decimals) });
index++;
@@ -3061,6 +3071,12 @@ Window.nonBlockingFormClosed.connect(function() {
properties.gravity.x = array[index++].value;
properties.gravity.y = array[index++].value;
properties.gravity.z = array[index++].value;
+
+ index++; // skip header
+ properties.mass = array[index++].value;
+ properties.ignoreForCollisions = array[index++].value;
+ properties.collisionsWillMove = array[index++].value;
+
properties.lifetime = array[index++].value;
properties.visible = array[index++].value;
diff --git a/examples/editVoxels.js b/examples/editVoxels.js
index f4e468fd40..3543f062e7 100644
--- a/examples/editVoxels.js
+++ b/examples/editVoxels.js
@@ -1127,8 +1127,6 @@ function keyPressEvent(event) {
} else if (event.text == "z") {
undoSound.playRandom();
}
-
-
}
trackKeyPressEvent(event); // used by preview support
diff --git a/examples/radio.js b/examples/radio.js
index 83e81e7e02..575d9d70c8 100644
--- a/examples/radio.js
+++ b/examples/radio.js
@@ -10,7 +10,7 @@
//
-var modelURL = "https://s3-us-west-1.amazonaws.com/highfidelity-public/models/entities/radio/Speakers2Finished.fbx";
+var modelURL = "https://s3-us-west-1.amazonaws.com/highfidelity-public/models/entities/radio/Speakers.fbx";
var soundURL = "https://s3-us-west-1.amazonaws.com/highfidelity-public/sounds/FamilyStereo.raw";
var AudioRotationOffset = Quat.fromPitchYawRollDegrees(0, -90, 0);
@@ -20,7 +20,7 @@ audioOptions.loop = true;
audioOptions.isStereo = true;
var injector = null;
-var sound = new Sound(soundURL);
+var sound = new Sound(soundURL, audioOptions.isStereo);
var entity = null;
var properties = null;
@@ -31,14 +31,16 @@ function update() {
print("Sound file downloaded");
var position = Vec3.sum(MyAvatar.position,
Vec3.multiplyQbyV(MyAvatar.orientation,
- { x: 0, y: 0.3, z: -1 }));
+ { x: 0, y: -0.3, z: -2 }));
var rotation = Quat.multiply(MyAvatar.orientation,
Quat.fromPitchYawRollDegrees(0, -90, 0));
entity = Entities.addEntity({
type: "Model",
position: position,
rotation: rotation,
- dimensions: { x: 0.5, y: 0.5, z: 0.5 },
+ dimensions: { x: 0.391,
+ y: 1.000,
+ z: 1.701 },
modelURL: modelURL
});
properties = Entities.getEntityProperties(entity);
diff --git a/interface/resources/images/address-bar-submit-active.svg b/interface/resources/images/address-bar-submit-active.svg
new file mode 100644
index 0000000000..313b366033
--- /dev/null
+++ b/interface/resources/images/address-bar-submit-active.svg
@@ -0,0 +1,18 @@
+
+
+
+
diff --git a/interface/resources/images/address-bar-submit.svg b/interface/resources/images/address-bar-submit.svg
new file mode 100644
index 0000000000..df4d7e90f6
--- /dev/null
+++ b/interface/resources/images/address-bar-submit.svg
@@ -0,0 +1,18 @@
+
+
+
+
diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp
index 60073f8d2b..bf5241427f 100644
--- a/interface/src/Application.cpp
+++ b/interface/src/Application.cpp
@@ -680,7 +680,7 @@ void Application::paintGL() {
if (whichCamera.getMode() == CAMERA_MODE_MIRROR) {
OculusManager::display(whichCamera.getRotation(), whichCamera.getPosition(), whichCamera);
} else {
- OculusManager::display(_myAvatar->getWorldAlignedOrientation(), whichCamera.getPosition(), whichCamera);
+ OculusManager::display(_myAvatar->getWorldAlignedOrientation(), _myAvatar->getDefaultEyePosition(), whichCamera);
}
} else if (TV3DManager::isConnected()) {
@@ -921,12 +921,11 @@ void Application::keyPressEvent(QKeyEvent* event) {
case Qt::Key_Return:
case Qt::Key_Enter:
- if (isMeta) {
- Menu::getInstance()->triggerOption(MenuOption::AddressBar);
- } else {
- Menu::getInstance()->triggerOption(MenuOption::Chat);
- }
+ Menu::getInstance()->triggerOption(MenuOption::AddressBar);
+ break;
+ case Qt::Key_Backslash:
+ Menu::getInstance()->triggerOption(MenuOption::Chat);
break;
case Qt::Key_N:
diff --git a/interface/src/Menu.cpp b/interface/src/Menu.cpp
index a4ef9cc022..cd5677e9e5 100644
--- a/interface/src/Menu.cpp
+++ b/interface/src/Menu.cpp
@@ -176,7 +176,7 @@ Menu::Menu() :
SLOT(toggleLocationList()));
addActionToQMenuAndActionHash(fileMenu,
MenuOption::AddressBar,
- Qt::CTRL | Qt::Key_Enter,
+ Qt::Key_Enter,
this,
SLOT(toggleAddressBar()));
@@ -1156,22 +1156,13 @@ void Menu::changePrivateKey() {
}
void Menu::toggleAddressBar() {
-
- QInputDialog addressBarDialog(Application::getInstance()->getWindow());
- addressBarDialog.setWindowTitle("Address Bar");
- addressBarDialog.setWindowFlags(Qt::Sheet);
- addressBarDialog.setLabelText("place, domain, @user, example.com, /position/orientation");
-
- addressBarDialog.resize(addressBarDialog.parentWidget()->size().width() * DIALOG_RATIO_OF_WINDOW,
- addressBarDialog.size().height());
-
- int dialogReturn = addressBarDialog.exec();
- if (dialogReturn == QDialog::Accepted && !addressBarDialog.textValue().isEmpty()) {
- // let the AddressManger figure out what to do with this
- AddressManager::getInstance().handleLookupString(addressBarDialog.textValue());
+ if (!_addressBarDialog) {
+ _addressBarDialog = new AddressBarDialog();
}
- sendFakeEnterEvent();
+ if (!_addressBarDialog->isVisible()) {
+ _addressBarDialog->show();
+ }
}
void Menu::displayAddressOfflineMessage() {
diff --git a/interface/src/Menu.h b/interface/src/Menu.h
index c5588da074..b267ab8b2c 100644
--- a/interface/src/Menu.h
+++ b/interface/src/Menu.h
@@ -27,6 +27,7 @@
#include "SpeechRecognizer.h"
#endif
+#include "ui/AddressBarDialog.h"
#include "ui/ChatWindow.h"
#include "ui/DataWebDialog.h"
#include "ui/JSConsole.h"
@@ -298,6 +299,7 @@ private:
QPointer _preferencesDialog;
QPointer _attachmentsDialog;
QPointer _animationsDialog;
+ QPointer _addressBarDialog;
QPointer _loginDialog;
bool _hasLoginDialogDisplayed;
QAction* _chatAction;
diff --git a/interface/src/avatar/Avatar.cpp b/interface/src/avatar/Avatar.cpp
index 3eae0ef382..c82eaa2bac 100644
--- a/interface/src/avatar/Avatar.cpp
+++ b/interface/src/avatar/Avatar.cpp
@@ -505,10 +505,11 @@ void Avatar::renderBody(RenderMode renderMode, bool postLighting, float glowLeve
{
Glower glower(glowLevel);
- if ((_shouldRenderBillboard || !(_skeletonModel.isRenderable() && getHead()->getFaceModel().isRenderable())) &&
- (postLighting || renderMode == SHADOW_RENDER_MODE)) {
- // render the billboard until both models are loaded
- renderBillboard();
+ if (_shouldRenderBillboard || !(_skeletonModel.isRenderable() && getHead()->getFaceModel().isRenderable())) {
+ if (postLighting || renderMode == SHADOW_RENDER_MODE) {
+ // render the billboard until both models are loaded
+ renderBillboard();
+ }
return;
}
diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp
index 51f582c4f8..51e21942f1 100644
--- a/interface/src/avatar/MyAvatar.cpp
+++ b/interface/src/avatar/MyAvatar.cpp
@@ -997,6 +997,10 @@ glm::vec3 MyAvatar::getUprightHeadPosition() const {
return _position + getWorldAlignedOrientation() * glm::vec3(0.0f, getPelvisToHeadLength(), 0.0f);
}
+glm::vec3 MyAvatar::getDefaultEyePosition() const {
+ return _position + getWorldAlignedOrientation() * _skeletonModel.getDefaultEyeModelPosition();
+}
+
const float SCRIPT_PRIORITY = DEFAULT_PRIORITY + 1.0f;
const float RECORDER_PRIORITY = SCRIPT_PRIORITY + 1.0f;
diff --git a/interface/src/avatar/MyAvatar.h b/interface/src/avatar/MyAvatar.h
index e38f3f4b2b..10c3e878cc 100644
--- a/interface/src/avatar/MyAvatar.h
+++ b/interface/src/avatar/MyAvatar.h
@@ -66,6 +66,7 @@ public:
const glm::vec3& getMouseRayDirection() const { return _mouseRayDirection; }
glm::vec3 getGravity() const { return _gravity; }
glm::vec3 getUprightHeadPosition() const;
+ glm::vec3 getDefaultEyePosition() const;
bool getShouldRenderLocally() const { return _shouldRender; }
const QList& getAnimationHandles() const { return _animationHandles; }
diff --git a/interface/src/avatar/SkeletonModel.cpp b/interface/src/avatar/SkeletonModel.cpp
index 86ca42b15e..1af5cfc893 100644
--- a/interface/src/avatar/SkeletonModel.cpp
+++ b/interface/src/avatar/SkeletonModel.cpp
@@ -27,7 +27,8 @@ SkeletonModel::SkeletonModel(Avatar* owningAvatar, QObject* parent) :
_owningAvatar(owningAvatar),
_boundingShape(),
_boundingShapeLocalOffset(0.0f),
- _ragdoll(NULL) {
+ _ragdoll(NULL),
+ _defaultEyeModelPosition(glm::vec3(0.f, 0.f, 0.f)) {
}
SkeletonModel::~SkeletonModel() {
@@ -470,23 +471,23 @@ bool SkeletonModel::getNeckParentRotationFromDefaultOrientation(glm::quat& neckP
return success;
}
-bool SkeletonModel::getEyePositions(glm::vec3& firstEyePosition, glm::vec3& secondEyePosition) const {
+bool SkeletonModel::getEyeModelPositions(glm::vec3& firstEyePosition, glm::vec3& secondEyePosition) const {
if (!isActive()) {
return false;
- }
+ }
const FBXGeometry& geometry = _geometry->getFBXGeometry();
- if (getJointPositionInWorldFrame(geometry.leftEyeJointIndex, firstEyePosition) &&
- getJointPositionInWorldFrame(geometry.rightEyeJointIndex, secondEyePosition)) {
+ if (getJointPosition(geometry.leftEyeJointIndex, firstEyePosition) &&
+ getJointPosition(geometry.rightEyeJointIndex, secondEyePosition)) {
return true;
}
// no eye joints; try to estimate based on head/neck joints
glm::vec3 neckPosition, headPosition;
- if (getJointPositionInWorldFrame(geometry.neckJointIndex, neckPosition) &&
- getJointPositionInWorldFrame(geometry.headJointIndex, headPosition)) {
+ if (getJointPosition(geometry.neckJointIndex, neckPosition) &&
+ getJointPosition(geometry.headJointIndex, headPosition)) {
const float EYE_PROPORTION = 0.6f;
glm::vec3 baseEyePosition = glm::mix(neckPosition, headPosition, EYE_PROPORTION);
glm::quat headRotation;
- getJointRotationInWorldFrame(geometry.headJointIndex, headRotation);
+ getJointRotation(geometry.headJointIndex, headRotation);
const float EYES_FORWARD = 0.25f;
const float EYE_SEPARATION = 0.1f;
float headHeight = glm::distance(neckPosition, headPosition);
@@ -497,6 +498,15 @@ bool SkeletonModel::getEyePositions(glm::vec3& firstEyePosition, glm::vec3& seco
return false;
}
+bool SkeletonModel::getEyePositions(glm::vec3& firstEyePosition, glm::vec3& secondEyePosition) const {
+ if (getEyeModelPositions(firstEyePosition, secondEyePosition)) {
+ firstEyePosition = _translation + _rotation * firstEyePosition;
+ secondEyePosition = _translation + _rotation * secondEyePosition;
+ return true;
+ }
+ return false;
+}
+
void SkeletonModel::renderRagdoll() {
if (!_ragdoll) {
return;
@@ -656,6 +666,20 @@ void SkeletonModel::buildShapes() {
// This method moves the shapes to their default positions in Model frame.
computeBoundingShape(geometry);
+ int headJointIndex = _geometry->getFBXGeometry().headJointIndex;
+ if (0 <= headJointIndex && headJointIndex < _jointStates.size()) {
+ glm::vec3 leftEyePosition, rightEyePosition;
+ getEyeModelPositions(leftEyePosition, rightEyePosition);
+ glm::vec3 midEyePosition = (leftEyePosition + rightEyePosition) / 2.f;
+
+ int rootJointIndex = _geometry->getFBXGeometry().rootJointIndex;
+ glm::vec3 rootModelPosition;
+ getJointPosition(rootJointIndex, rootModelPosition);
+
+ _defaultEyeModelPosition = midEyePosition - rootModelPosition;
+ _defaultEyeModelPosition.z = -_defaultEyeModelPosition.z;
+ }
+
// While the shapes are in their default position we disable collisions between
// joints that are currently colliding.
disableCurrentSelfCollisions();
diff --git a/interface/src/avatar/SkeletonModel.h b/interface/src/avatar/SkeletonModel.h
index ca0007ddb4..35122d5e18 100644
--- a/interface/src/avatar/SkeletonModel.h
+++ b/interface/src/avatar/SkeletonModel.h
@@ -97,6 +97,10 @@ public:
/// \return whether or not both eye meshes were found
bool getEyePositions(glm::vec3& firstEyePosition, glm::vec3& secondEyePosition) const;
+ /// Gets the default position of the mid eye point in model frame coordinates.
+ /// \return whether or not the head was found.
+ glm::vec3 getDefaultEyeModelPosition() const { return _defaultEyeModelPosition; }
+
virtual void updateVisibleJointStates();
SkeletonRagdoll* buildRagdoll();
@@ -140,12 +144,16 @@ private:
/// \param position position of joint in model-frame
/// \param rotation rotation of joint in model-frame
void setHandPosition(int jointIndex, const glm::vec3& position, const glm::quat& rotation);
+
+ bool getEyeModelPositions(glm::vec3& firstEyePosition, glm::vec3& secondEyePosition) const;
Avatar* _owningAvatar;
CapsuleShape _boundingShape;
glm::vec3 _boundingShapeLocalOffset;
SkeletonRagdoll* _ragdoll;
+
+ glm::vec3 _defaultEyeModelPosition;
};
#endif // hifi_SkeletonModel_h
diff --git a/interface/src/renderer/Model.cpp b/interface/src/renderer/Model.cpp
index 2031035726..858d8c9239 100644
--- a/interface/src/renderer/Model.cpp
+++ b/interface/src/renderer/Model.cpp
@@ -602,6 +602,14 @@ bool Model::getJointRotationInWorldFrame(int jointIndex, glm::quat& rotation) co
return true;
}
+bool Model::getJointRotation(int jointIndex, glm::quat& rotation) const {
+ if (jointIndex == -1 || jointIndex >= _jointStates.size()) {
+ return false;
+ }
+ rotation = _jointStates[jointIndex].getRotation();
+ return true;
+}
+
bool Model::getJointCombinedRotation(int jointIndex, glm::quat& rotation) const {
if (jointIndex == -1 || jointIndex >= _jointStates.size()) {
return false;
diff --git a/interface/src/renderer/Model.h b/interface/src/renderer/Model.h
index 89c47e229d..b82db73624 100644
--- a/interface/src/renderer/Model.h
+++ b/interface/src/renderer/Model.h
@@ -148,6 +148,11 @@ public:
/// \return true if joint exists
bool getJointPosition(int jointIndex, glm::vec3& position) const;
+ /// \param jointIndex index of joint in model structure
+ /// \param rotation[out] rotation of joint in model-frame
+ /// \return true if joint exists
+ bool getJointRotation(int jointIndex, glm::quat& rotation) const;
+
QStringList getJointNames() const;
AnimationHandlePointer createAnimationHandle();
diff --git a/interface/src/ui/AddressBarDialog.cpp b/interface/src/ui/AddressBarDialog.cpp
new file mode 100644
index 0000000000..25a4299d6c
--- /dev/null
+++ b/interface/src/ui/AddressBarDialog.cpp
@@ -0,0 +1,131 @@
+//
+// AddressBarDialog.cpp
+// interface/src/ui
+//
+// Created by Stojce Slavkovski on 9/22/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 "AddressBarDialog.h"
+#include "AddressManager.h"
+#include "Application.h"
+
+const QString ADDRESSBAR_GO_BUTTON_ICON = "images/address-bar-submit.svg";
+const QString ADDRESSBAR_GO_BUTTON_ACTIVE_ICON = "images/address-bar-submit-active.svg";
+
+AddressBarDialog::AddressBarDialog() :
+ FramelessDialog(Application::getInstance()->getWindow(), 0, FramelessDialog::POSITION_TOP) {
+
+ setAttribute(Qt::WA_DeleteOnClose, false);
+ setupUI();
+}
+
+void AddressBarDialog::setupUI() {
+
+ const QString DIALOG_STYLESHEET = "font-family: Helvetica, Arial, sans-serif;";
+ const QString ADDRESSBAR_PLACEHOLDER = "Go to: domain, @user, #location";
+ const QString ADDRESSBAR_STYLESHEET = "padding: 0 10px;";
+ const QString ADDRESSBAR_FONT_FAMILY = "Helvetica,Arial,sans-serif";
+ const int ADDRESSBAR_FONT_SIZE = 20;
+
+ const int ADDRESSBAR_MIN_WIDTH = 200;
+ const int ADDRESSBAR_MAX_WIDTH = 615;
+ const int ADDRESSBAR_HEIGHT = 54;
+ const int ADDRESSBAR_STRETCH = 60;
+
+ const int BUTTON_SPACER_SIZE = 10;
+ const int DEFAULT_SPACER_SIZE = 20;
+ const int ADDRESS_LAYOUT_RIGHT_MARGIN = 10;
+
+ const int GO_BUTTON_SIZE = 55;
+ const int CLOSE_BUTTON_SIZE = 16;
+ const QString CLOSE_BUTTON_ICON = "styles/close.svg";
+
+ const int DIALOG_HEIGHT = 100;
+ const int DIALOG_INITIAL_WIDTH = 560;
+
+ setModal(true);
+ setWindowModality(Qt::WindowModal);
+ setHideOnBlur(false);
+
+ QSizePolicy sizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);
+ setSizePolicy(sizePolicy);
+ setMinimumSize(QSize(DIALOG_INITIAL_WIDTH, DIALOG_HEIGHT));
+ setStyleSheet(DIALOG_STYLESHEET);
+
+ _verticalLayout = new QVBoxLayout(this);
+
+ _addressLayout = new QHBoxLayout();
+ _addressLayout->setContentsMargins(0, 0, ADDRESS_LAYOUT_RIGHT_MARGIN, 0);
+
+ _leftSpacer = new QSpacerItem(DEFAULT_SPACER_SIZE,
+ DEFAULT_SPACER_SIZE,
+ QSizePolicy::MinimumExpanding,
+ QSizePolicy::Minimum);
+
+ _addressLayout->addItem(_leftSpacer);
+
+ _addressLineEdit = new QLineEdit(this);
+ _addressLineEdit->setPlaceholderText(ADDRESSBAR_PLACEHOLDER);
+ QSizePolicy sizePolicyLineEdit(QSizePolicy::Preferred, QSizePolicy::Fixed);
+ sizePolicyLineEdit.setHorizontalStretch(ADDRESSBAR_STRETCH);
+ _addressLineEdit->setSizePolicy(sizePolicyLineEdit);
+ _addressLineEdit->setMinimumSize(QSize(ADDRESSBAR_MIN_WIDTH, ADDRESSBAR_HEIGHT));
+ _addressLineEdit->setMaximumSize(QSize(ADDRESSBAR_MAX_WIDTH, ADDRESSBAR_HEIGHT));
+ QFont font(ADDRESSBAR_FONT_FAMILY, ADDRESSBAR_FONT_SIZE);
+ _addressLineEdit->setFont(font);
+ _addressLineEdit->setStyleSheet(ADDRESSBAR_STYLESHEET);
+ _addressLayout->addWidget(_addressLineEdit);
+
+ _buttonSpacer = new QSpacerItem(BUTTON_SPACER_SIZE, BUTTON_SPACER_SIZE, QSizePolicy::Fixed, QSizePolicy::Minimum);
+ _addressLayout->addItem(_buttonSpacer);
+
+ _goButton = new QPushButton(this);
+ _goButton->setSizePolicy(sizePolicy);
+ _goButton->setMinimumSize(QSize(GO_BUTTON_SIZE, GO_BUTTON_SIZE));
+ _goButton->setMaximumSize(QSize(GO_BUTTON_SIZE, GO_BUTTON_SIZE));
+ _goButton->setIcon(QIcon(Application::resourcesPath() + ADDRESSBAR_GO_BUTTON_ICON));
+ _goButton->setIconSize(QSize(GO_BUTTON_SIZE, GO_BUTTON_SIZE));
+ _goButton->setDefault(true);
+ _goButton->setFlat(true);
+ _addressLayout->addWidget(_goButton);
+
+ _rightSpacer = new QSpacerItem(DEFAULT_SPACER_SIZE,
+ DEFAULT_SPACER_SIZE,
+ QSizePolicy::MinimumExpanding,
+ QSizePolicy::Minimum);
+
+ _addressLayout->addItem(_rightSpacer);
+
+ _closeButton = new QPushButton(this);
+ _closeButton->setSizePolicy(sizePolicy);
+ _closeButton->setMinimumSize(QSize(CLOSE_BUTTON_SIZE, CLOSE_BUTTON_SIZE));
+ _closeButton->setMaximumSize(QSize(CLOSE_BUTTON_SIZE, CLOSE_BUTTON_SIZE));
+ QIcon icon(Application::resourcesPath() + CLOSE_BUTTON_ICON);
+ _closeButton->setIcon(icon);
+ _closeButton->setFlat(true);
+ _addressLayout->addWidget(_closeButton, 0, Qt::AlignRight);
+
+ _verticalLayout->addLayout(_addressLayout);
+
+ connect(_goButton, &QPushButton::clicked, this, &AddressBarDialog::accept);
+ connect(_closeButton, &QPushButton::clicked, this, &QDialog::close);
+}
+
+void AddressBarDialog::showEvent(QShowEvent* event) {
+ _goButton->setIcon(QIcon(Application::resourcesPath() + ADDRESSBAR_GO_BUTTON_ICON));
+ _addressLineEdit->setText(QString());
+ FramelessDialog::showEvent(event);
+}
+
+void AddressBarDialog::accept() {
+ if (!_addressLineEdit->text().isEmpty()) {
+ _goButton->setIcon(QIcon(Application::resourcesPath() + ADDRESSBAR_GO_BUTTON_ACTIVE_ICON));
+ AddressManager& addressManager = AddressManager::getInstance();
+ connect(&addressManager, &AddressManager::lookupResultsFinished, this, &QDialog::hide);
+ addressManager.handleLookupString(_addressLineEdit->text());
+ }
+}
\ No newline at end of file
diff --git a/interface/src/ui/AddressBarDialog.h b/interface/src/ui/AddressBarDialog.h
new file mode 100644
index 0000000000..8f2cf2d7b8
--- /dev/null
+++ b/interface/src/ui/AddressBarDialog.h
@@ -0,0 +1,46 @@
+//
+// AddressBarDialog.h
+// interface/src/ui
+//
+// Created by Stojce Slavkovski on 9/22/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_AddressBarDialog_h
+#define hifi_AddressBarDialog_h
+
+#include "FramelessDialog.h"
+
+#include
+#include
+#include
+#include
+
+class AddressBarDialog : public FramelessDialog {
+ Q_OBJECT
+
+public:
+ AddressBarDialog();
+
+private:
+ void setupUI();
+ void showEvent(QShowEvent* event);
+
+ QVBoxLayout *_verticalLayout;
+ QHBoxLayout *_addressLayout;
+ QSpacerItem *_leftSpacer;
+ QSpacerItem *_rightSpacer;
+ QSpacerItem *_buttonSpacer;
+ QPushButton *_goButton;
+ QPushButton *_closeButton;
+ QLineEdit *_addressLineEdit;
+
+private slots:
+ void accept();
+
+};
+
+#endif // hifi_AddressBarDialog_h
diff --git a/libraries/audio/src/AudioInjector.cpp b/libraries/audio/src/AudioInjector.cpp
index 17b082f07a..10623651a0 100644
--- a/libraries/audio/src/AudioInjector.cpp
+++ b/libraries/audio/src/AudioInjector.cpp
@@ -25,7 +25,8 @@ AudioInjector::AudioInjector(QObject* parent) :
QObject(parent),
_sound(NULL),
_options(),
- _shouldStop(false)
+ _shouldStop(false),
+ _currentSendPosition(0)
{
}
@@ -77,11 +78,13 @@ void AudioInjector::injectAudio() {
// pack the position for injected audio
int positionOptionOffset = injectAudioPacket.size();
- packetStream.writeRawData(reinterpret_cast(&_options.getPosition()), sizeof(_options.getPosition()));
+ packetStream.writeRawData(reinterpret_cast(&_options.getPosition()),
+ sizeof(_options.getPosition()));
// pack our orientation for injected audio
int orientationOptionOffset = injectAudioPacket.size();
- packetStream.writeRawData(reinterpret_cast(&_options.getOrientation()), sizeof(_options.getOrientation()));
+ packetStream.writeRawData(reinterpret_cast(&_options.getOrientation()),
+ sizeof(_options.getOrientation()));
// pack zero for radius
float radius = 0;
@@ -95,17 +98,15 @@ void AudioInjector::injectAudio() {
timer.start();
int nextFrame = 0;
- int currentSendPosition = 0;
-
int numPreAudioDataBytes = injectAudioPacket.size();
bool shouldLoop = _options.getLoop();
// loop to send off our audio in NETWORK_BUFFER_LENGTH_SAMPLES_PER_CHANNEL byte chunks
quint16 outgoingInjectedAudioSequenceNumber = 0;
- while (currentSendPosition < soundByteArray.size() && !_shouldStop) {
+ while (_currentSendPosition < soundByteArray.size() && !_shouldStop) {
- int bytesToCopy = std::min(NETWORK_BUFFER_LENGTH_BYTES_PER_CHANNEL,
- soundByteArray.size() - currentSendPosition);
+ int bytesToCopy = std::min(((_options.isStereo()) ? 2 : 1) * NETWORK_BUFFER_LENGTH_BYTES_PER_CHANNEL,
+ soundByteArray.size() - _currentSendPosition);
memcpy(injectAudioPacket.data() + positionOptionOffset,
&_options.getPosition(),
sizeof(_options.getPosition()));
@@ -117,10 +118,12 @@ void AudioInjector::injectAudio() {
injectAudioPacket.resize(numPreAudioDataBytes + bytesToCopy);
// pack the sequence number
- memcpy(injectAudioPacket.data() + numPreSequenceNumberBytes, &outgoingInjectedAudioSequenceNumber, sizeof(quint16));
+ memcpy(injectAudioPacket.data() + numPreSequenceNumberBytes,
+ &outgoingInjectedAudioSequenceNumber, sizeof(quint16));
// copy the next NETWORK_BUFFER_LENGTH_BYTES_PER_CHANNEL bytes to the packet
- memcpy(injectAudioPacket.data() + numPreAudioDataBytes, soundByteArray.data() + currentSendPosition, bytesToCopy);
+ memcpy(injectAudioPacket.data() + numPreAudioDataBytes,
+ soundByteArray.data() + _currentSendPosition, bytesToCopy);
// grab our audio mixer from the NodeList, if it exists
NodeList* nodeList = NodeList::getInstance();
@@ -130,22 +133,22 @@ void AudioInjector::injectAudio() {
nodeList->writeDatagram(injectAudioPacket, audioMixer);
outgoingInjectedAudioSequenceNumber++;
- currentSendPosition += bytesToCopy;
+ _currentSendPosition += bytesToCopy;
// send two packets before the first sleep so the mixer can start playback right away
- if (currentSendPosition != bytesToCopy && currentSendPosition < soundByteArray.size()) {
+ if (_currentSendPosition != bytesToCopy && _currentSendPosition < soundByteArray.size()) {
// not the first packet and not done
// sleep for the appropriate time
int usecToSleep = (++nextFrame * BUFFER_SEND_INTERVAL_USECS) - timer.nsecsElapsed() / 1000;
if (usecToSleep > 0) {
usleep(usecToSleep);
- }
+ }
}
- if (shouldLoop && currentSendPosition == soundByteArray.size()) {
- currentSendPosition = 0;
+ if (shouldLoop && _currentSendPosition >= soundByteArray.size()) {
+ _currentSendPosition = 0;
}
}
}
diff --git a/libraries/audio/src/AudioInjector.h b/libraries/audio/src/AudioInjector.h
index 966a4dd1cf..af9b5e55d1 100644
--- a/libraries/audio/src/AudioInjector.h
+++ b/libraries/audio/src/AudioInjector.h
@@ -26,16 +26,20 @@ class AudioInjector : public QObject {
public:
AudioInjector(QObject* parent);
AudioInjector(Sound* sound, const AudioInjectorOptions& injectorOptions);
+
+ int getCurrentSendPosition() const { return _currentSendPosition; }
public slots:
void injectAudio();
void stop() { _shouldStop = true; }
void setOptions(AudioInjectorOptions& options);
+ void setCurrentSendPosition(int currentSendPosition) { _currentSendPosition = currentSendPosition; }
signals:
void finished();
private:
Sound* _sound;
AudioInjectorOptions _options;
bool _shouldStop;
+ int _currentSendPosition;
};
Q_DECLARE_METATYPE(AudioInjector*)
diff --git a/libraries/audio/src/AudioScriptingInterface.cpp b/libraries/audio/src/AudioScriptingInterface.cpp
index fa0d3a9565..43c7d35c1d 100644
--- a/libraries/audio/src/AudioScriptingInterface.cpp
+++ b/libraries/audio/src/AudioScriptingInterface.cpp
@@ -13,6 +13,9 @@
AudioInjector* AudioScriptingInterface::playSound(Sound* sound, const AudioInjectorOptions* injectorOptions) {
+ if (sound->isStereo()) {
+ const_cast(injectorOptions)->setIsStereo(true);
+ }
AudioInjector* injector = new AudioInjector(sound, *injectorOptions);
QThread* injectorThread = new QThread();
diff --git a/libraries/audio/src/InjectedAudioStream.cpp b/libraries/audio/src/InjectedAudioStream.cpp
index 84d33c53e3..0cc13eae05 100644
--- a/libraries/audio/src/InjectedAudioStream.cpp
+++ b/libraries/audio/src/InjectedAudioStream.cpp
@@ -30,7 +30,9 @@ InjectedAudioStream::InjectedAudioStream(const QUuid& streamIdentifier, const In
const uchar MAX_INJECTOR_VOLUME = 255;
-int InjectedAudioStream::parseStreamProperties(PacketType type, const QByteArray& packetAfterSeqNum, int& numAudioSamples) {
+int InjectedAudioStream::parseStreamProperties(PacketType type,
+ const QByteArray& packetAfterSeqNum,
+ int& numAudioSamples) {
// setup a data stream to read from this packet
QDataStream packetStream(packetAfterSeqNum);
@@ -38,6 +40,9 @@ int InjectedAudioStream::parseStreamProperties(PacketType type, const QByteArray
packetStream.skipRawData(NUM_BYTES_RFC4122_UUID);
packetStream >> _isStereo;
+ if (isStereo()) {
+ _ringBuffer.resizeForFrameSize(NETWORK_BUFFER_LENGTH_SAMPLES_STEREO);
+ }
// pull the loopback flag and set our boolean
uchar shouldLoopback;
diff --git a/libraries/audio/src/Sound.cpp b/libraries/audio/src/Sound.cpp
index 9edb04aa2c..6fa002a664 100644
--- a/libraries/audio/src/Sound.cpp
+++ b/libraries/audio/src/Sound.cpp
@@ -31,7 +31,8 @@
// procedural audio version of Sound
Sound::Sound(float volume, float frequency, float duration, float decay, QObject* parent) :
- QObject(parent)
+ QObject(parent),
+ _isStereo(false)
{
static char monoAudioData[MAX_PACKET_SIZE];
static int16_t* monoAudioSamples = (int16_t*)(monoAudioData);
@@ -69,8 +70,9 @@ Sound::Sound(float volume, float frequency, float duration, float decay, QObject
}
}
-Sound::Sound(const QUrl& sampleURL, QObject* parent) :
+Sound::Sound(const QUrl& sampleURL, bool isStereo, QObject* parent) :
QObject(parent),
+ _isStereo(isStereo),
_hasDownloaded(false)
{
// assume we have a QApplication or QCoreApplication instance and use the
@@ -82,12 +84,14 @@ Sound::Sound(const QUrl& sampleURL, QObject* parent) :
QNetworkReply* soundDownload = networkAccessManager.get(QNetworkRequest(sampleURL));
connect(soundDownload, &QNetworkReply::finished, this, &Sound::replyFinished);
- connect(soundDownload, SIGNAL(error(QNetworkReply::NetworkError)), this, SLOT(replyError(QNetworkReply::NetworkError)));
+ connect(soundDownload, SIGNAL(error(QNetworkReply::NetworkError)),
+ this, SLOT(replyError(QNetworkReply::NetworkError)));
}
Sound::Sound(const QByteArray byteArray, QObject* parent) :
QObject(parent),
_byteArray(byteArray),
+ _isStereo(false),
_hasDownloaded(true)
{
}
@@ -149,11 +153,20 @@ void Sound::downSample(const QByteArray& rawAudioByteArray) {
int16_t* sourceSamples = (int16_t*) rawAudioByteArray.data();
int16_t* destinationSamples = (int16_t*) _byteArray.data();
- for (int i = 1; i < numSourceSamples; i += 2) {
- if (i + 1 >= numSourceSamples) {
- destinationSamples[(i - 1) / 2] = (sourceSamples[i - 1] / 2) + (sourceSamples[i] / 2);
- } else {
- destinationSamples[(i - 1) / 2] = (sourceSamples[i - 1] / 4) + (sourceSamples[i] / 2) + (sourceSamples[i + 1] / 4);
+
+ if (_isStereo) {
+ for (int i = 0; i < numSourceSamples; i += 4) {
+ destinationSamples[i / 2] = (sourceSamples[i] / 2) + (sourceSamples[i + 2] / 2);
+ destinationSamples[(i / 2) + 1] = (sourceSamples[i + 1] / 2) + (sourceSamples[i + 3] / 2);
+ }
+ } else {
+ for (int i = 1; i < numSourceSamples; i += 2) {
+ if (i + 1 >= numSourceSamples) {
+ destinationSamples[(i - 1) / 2] = (sourceSamples[i - 1] / 2) + (sourceSamples[i] / 2);
+ } else {
+ destinationSamples[(i - 1) / 2] = (sourceSamples[i - 1] / 4) + (sourceSamples[i] / 2)
+ + (sourceSamples[i + 1] / 4);
+ }
}
}
}
diff --git a/libraries/audio/src/Sound.h b/libraries/audio/src/Sound.h
index fa2dd97903..b8fdc6b458 100644
--- a/libraries/audio/src/Sound.h
+++ b/libraries/audio/src/Sound.h
@@ -20,17 +20,19 @@ class Sound : public QObject {
Q_PROPERTY(bool downloaded READ hasDownloaded)
public:
- Sound(const QUrl& sampleURL, QObject* parent = NULL);
+ Sound(const QUrl& sampleURL, bool isStereo = false, QObject* parent = NULL);
Sound(float volume, float frequency, float duration, float decay, QObject* parent = NULL);
Sound(const QByteArray byteArray, QObject* parent = NULL);
void append(const QByteArray byteArray);
+ bool isStereo() const { return _isStereo; }
bool hasDownloaded() const { return _hasDownloaded; }
const QByteArray& getByteArray() { return _byteArray; }
private:
QByteArray _byteArray;
+ bool _isStereo;
bool _hasDownloaded;
void trimFrames();
diff --git a/libraries/avatars/src/AvatarData.cpp b/libraries/avatars/src/AvatarData.cpp
index 5ac0c69864..126da654ae 100644
--- a/libraries/avatars/src/AvatarData.cpp
+++ b/libraries/avatars/src/AvatarData.cpp
@@ -587,18 +587,13 @@ bool AvatarData::hasReferential() {
}
bool AvatarData::isPlaying() {
- if (!_player) {
- return false;
- }
- if (QThread::currentThread() != thread()) {
- bool result;
- QMetaObject::invokeMethod(this, "isPlaying", Qt::BlockingQueuedConnection,
- Q_RETURN_ARG(bool, result));
- return result;
- }
return _player && _player->isPlaying();
}
+bool AvatarData::isPaused() {
+ return _player && _player->isPaused();
+}
+
qint64 AvatarData::playerElapsed() {
if (!_player) {
return 0;
@@ -625,6 +620,14 @@ qint64 AvatarData::playerLength() {
return _player->getRecording()->getLength();
}
+int AvatarData::playerCurrentFrame() {
+ return (_player) ? _player->getCurrentFrame() : 0;
+}
+
+int AvatarData::playerFrameNumber() {
+ return (_player && _player->getRecording()) ? _player->getRecording()->getFrameNumber() : 0;
+}
+
void AvatarData::loadRecording(QString filename) {
if (QThread::currentThread() != thread()) {
QMetaObject::invokeMethod(this, "loadRecording", Qt::BlockingQueuedConnection,
@@ -649,6 +652,18 @@ void AvatarData::startPlaying() {
_player->startPlaying();
}
+void AvatarData::setPlayerFrame(int frame) {
+ if (_player) {
+ _player->setCurrentFrame(frame);
+ }
+}
+
+void AvatarData::setPlayerTime(qint64 time) {
+ if (_player) {
+ _player->setCurrentTime(time);
+ }
+}
+
void AvatarData::setPlayFromCurrentLocation(bool playFromCurrentLocation) {
if (_player) {
_player->setPlayFromCurrentLocation(playFromCurrentLocation);
@@ -696,6 +711,19 @@ void AvatarData::play() {
}
}
+void AvatarData::pausePlayer() {
+ if (!_player) {
+ return;
+ }
+ if (QThread::currentThread() != thread()) {
+ QMetaObject::invokeMethod(this, "pausePlayer", Qt::BlockingQueuedConnection);
+ return;
+ }
+ if (_player) {
+ _player->pausePlayer();
+ }
+}
+
void AvatarData::stopPlaying() {
if (!_player) {
return;
diff --git a/libraries/avatars/src/AvatarData.h b/libraries/avatars/src/AvatarData.h
index bc68103ca6..9b28fdc258 100755
--- a/libraries/avatars/src/AvatarData.h
+++ b/libraries/avatars/src/AvatarData.h
@@ -296,10 +296,16 @@ public slots:
bool hasReferential();
bool isPlaying();
+ bool isPaused();
qint64 playerElapsed();
qint64 playerLength();
+ int playerCurrentFrame();
+ int playerFrameNumber();
+
void loadRecording(QString filename);
void startPlaying();
+ void setPlayerFrame(int frame);
+ void setPlayerTime(qint64 time);
void setPlayFromCurrentLocation(bool playFromCurrentLocation);
void setPlayerLoop(bool loop);
void setPlayerUseDisplayName(bool useDisplayName);
@@ -307,6 +313,7 @@ public slots:
void setPlayerUseHeadModel(bool useHeadModel);
void setPlayerUseSkeletonModel(bool useSkeletonModel);
void play();
+ void pausePlayer();
void stopPlaying();
protected:
diff --git a/libraries/avatars/src/Player.cpp b/libraries/avatars/src/Player.cpp
index b548d452e7..9732ccd780 100644
--- a/libraries/avatars/src/Player.cpp
+++ b/libraries/avatars/src/Player.cpp
@@ -9,6 +9,7 @@
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
+#include
#include
#include
#include
@@ -18,6 +19,8 @@
Player::Player(AvatarData* avatar) :
_recording(new Recording()),
+ _pausedFrame(-1),
+ _timerOffset(0),
_avatar(avatar),
_audioThread(NULL),
_playFromCurrentPosition(true),
@@ -36,16 +39,26 @@ bool Player::isPlaying() const {
return _timer.isValid();
}
+bool Player::isPaused() const {
+ return (_pausedFrame != -1);
+}
+
qint64 Player::elapsed() const {
if (isPlaying()) {
- return _timer.elapsed();
+ return _timerOffset + _timer.elapsed();
+ } else if (isPaused()) {
+ return _timerOffset;
} else {
return 0;
}
}
void Player::startPlaying() {
- if (_recording && _recording->getFrameNumber() > 0) {
+ if (!_recording || _recording->getFrameNumber() <= 1) {
+ return;
+ }
+
+ if (!isPaused()) {
_currentContext.globalTimestamp = usecTimestampNow();
_currentContext.domain = NodeList::getInstance()->getDomainHandler().getHostname();
_currentContext.position = _avatar->getPosition();
@@ -97,9 +110,17 @@ void Player::startPlaying() {
_avatar->setForceFaceshiftConnected(true);
qDebug() << "Recorder::startPlaying()";
+ setupAudioThread();
_currentFrame = 0;
+ _timerOffset = 0;
+ _timer.start();
+ } else {
+ qDebug() << "Recorder::startPlaying(): Unpause";
setupAudioThread();
_timer.start();
+
+ setCurrentFrame(_pausedFrame);
+ _pausedFrame = -1;
}
}
@@ -107,6 +128,7 @@ void Player::stopPlaying() {
if (!isPlaying()) {
return;
}
+ _pausedFrame = -1;
_timer.invalidate();
cleanupAudioThread();
_avatar->clearJointsData();
@@ -130,6 +152,15 @@ void Player::stopPlaying() {
qDebug() << "Recorder::stopPlaying()";
}
+void Player::pausePlayer() {
+ _timerOffset = elapsed();
+ _timer.invalidate();
+ cleanupAudioThread();
+
+ _pausedFrame = _currentFrame;
+ qDebug() << "Recorder::pausePlayer()";
+}
+
void Player::setupAudioThread() {
_audioThread = new QThread();
_options.setPosition(_avatar->getPosition());
@@ -156,6 +187,7 @@ void Player::loopRecording() {
cleanupAudioThread();
setupAudioThread();
_currentFrame = 0;
+ _timerOffset = 0;
_timer.restart();
}
@@ -166,10 +198,13 @@ void Player::loadFromFile(const QString& file) {
_recording = RecordingPointer(new Recording());
}
readRecordingFromFile(_recording, file);
+
+ _pausedFrame = -1;
}
void Player::loadRecording(RecordingPointer recording) {
_recording = recording;
+ _pausedFrame = -1;
}
void Player::play() {
@@ -213,6 +248,77 @@ void Player::play() {
_injector->setOptions(_options);
}
+void Player::setCurrentFrame(int currentFrame) {
+ if (_recording && (currentFrame < 0 || currentFrame >= _recording->getFrameNumber())) {
+ stopPlaying();
+ return;
+ }
+
+ _currentFrame = currentFrame;
+ _timerOffset = _recording->getFrameTimestamp(_currentFrame);
+
+ if (isPlaying()) {
+ _timer.start();
+ setAudionInjectorPosition();
+ } else {
+ _pausedFrame = currentFrame;
+ }
+}
+
+void Player::setCurrentTime(qint64 currentTime) {
+ if (currentTime < 0 || currentTime >= _recording->getLength()) {
+ stopPlaying();
+ return;
+ }
+
+ // Find correct frame
+ int lowestBound = 0;
+ int highestBound = _recording->getFrameNumber() - 1;
+ while (lowestBound + 1 != highestBound) {
+ assert(lowestBound < highestBound);
+
+ int bestGuess = lowestBound +
+ (highestBound - lowestBound) *
+ (float)(currentTime - _recording->getFrameTimestamp(lowestBound)) /
+ (float)(_recording->getFrameTimestamp(highestBound) - _recording->getFrameTimestamp(lowestBound));
+
+ if (_recording->getFrameTimestamp(bestGuess) <= currentTime) {
+ if (currentTime < _recording->getFrameTimestamp(bestGuess + 1)) {
+ lowestBound = bestGuess;
+ highestBound = bestGuess + 1;
+ } else {
+ lowestBound = bestGuess + 1;
+ }
+ } else {
+ if (_recording->getFrameTimestamp(bestGuess - 1) <= currentTime) {
+ lowestBound = bestGuess - 1;
+ highestBound = bestGuess;
+ } else {
+ highestBound = bestGuess - 1;
+ }
+ }
+ }
+
+ _currentFrame = lowestBound;
+ _timerOffset = _recording->getFrameTimestamp(lowestBound);
+
+ if (isPlaying()) {
+ _timer.start();
+ setAudionInjectorPosition();
+ } else {
+ _pausedFrame = lowestBound;
+ }
+}
+
+void Player::setAudionInjectorPosition() {
+ int MSEC_PER_SEC = 1000;
+ int SAMPLE_SIZE = 2; // 16 bits
+ int CHANNEL_COUNT = 1;
+ int FRAME_SIZE = SAMPLE_SIZE * CHANNEL_COUNT;
+ int currentAudioFrame = elapsed() * FRAME_SIZE * (SAMPLE_RATE / MSEC_PER_SEC);
+ _injector->setCurrentSendPosition(currentAudioFrame);
+}
+
void Player::setPlayFromCurrentLocation(bool playFromCurrentLocation) {
_playFromCurrentPosition = playFromCurrentLocation;
}
@@ -227,7 +333,7 @@ bool Player::computeCurrentFrame() {
}
while (_currentFrame < _recording->getFrameNumber() - 1 &&
- _recording->getFrameTimestamp(_currentFrame) < _timer.elapsed()) {
+ _recording->getFrameTimestamp(_currentFrame) < elapsed()) {
++_currentFrame;
}
diff --git a/libraries/avatars/src/Player.h b/libraries/avatars/src/Player.h
index 772209d435..51e120917c 100644
--- a/libraries/avatars/src/Player.h
+++ b/libraries/avatars/src/Player.h
@@ -30,17 +30,23 @@ public:
Player(AvatarData* avatar);
bool isPlaying() const;
+ bool isPaused() const;
qint64 elapsed() const;
RecordingPointer getRecording() const { return _recording; }
+ int getCurrentFrame() const { return _currentFrame; }
public slots:
void startPlaying();
void stopPlaying();
+ void pausePlayer();
void loadFromFile(const QString& file);
void loadRecording(RecordingPointer recording);
void play();
+ void setCurrentFrame(int currentFrame);
+ void setCurrentTime(qint64 currentTime);
+
void setPlayFromCurrentLocation(bool playFromCurrentPosition);
void setLoop(bool loop) { _loop = loop; }
void useAttachements(bool useAttachments) { _useAttachments = useAttachments; }
@@ -52,11 +58,14 @@ private:
void setupAudioThread();
void cleanupAudioThread();
void loopRecording();
+ void setAudionInjectorPosition();
bool computeCurrentFrame();
QElapsedTimer _timer;
RecordingPointer _recording;
int _currentFrame;
+ int _pausedFrame;
+ qint64 _timerOffset;
QSharedPointer _injector;
AudioInjectorOptions _options;
@@ -64,7 +73,6 @@ private:
AvatarData* _avatar;
QThread* _audioThread;
-
RecordingContext _currentContext;
bool _playFromCurrentPosition;
bool _loop;
@@ -72,7 +80,6 @@ private:
bool _useDisplayName;
bool _useHeadURL;
bool _useSkeletonURL;
-
};
#endif // hifi_Player_h
\ No newline at end of file
diff --git a/libraries/avatars/src/Recording.cpp b/libraries/avatars/src/Recording.cpp
index b34391d580..7465eb1aac 100644
--- a/libraries/avatars/src/Recording.cpp
+++ b/libraries/avatars/src/Recording.cpp
@@ -9,6 +9,7 @@
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
+#include
#include
#include
#include
@@ -60,6 +61,9 @@ qint32 Recording::getFrameTimestamp(int i) const {
if (i >= _timestamps.size()) {
return getLength();
}
+ if (i < 0) {
+ return 0;
+ }
return _timestamps[i];
}
@@ -781,7 +785,6 @@ RecordingPointer readRecordingFromRecFile(RecordingPointer recording, const QStr
fileStream >> audioArray;
// Cut down audio if necessary
- int SAMPLE_RATE = 48000; // 48 kHz
int SAMPLE_SIZE = 2; // 16 bits
int MSEC_PER_SEC = 1000;
int audioLength = recording->getLength() * SAMPLE_SIZE * (SAMPLE_RATE / MSEC_PER_SEC);
diff --git a/libraries/entities/src/EntityCollisionSystem.cpp b/libraries/entities/src/EntityCollisionSystem.cpp
index c9040c7a6e..609b91f3a1 100644
--- a/libraries/entities/src/EntityCollisionSystem.cpp
+++ b/libraries/entities/src/EntityCollisionSystem.cpp
@@ -13,8 +13,10 @@
#include
#include
#include
+#include
#include
#include
+#include
#include "EntityItem.h"
#include "EntityCollisionSystem.h"
@@ -103,11 +105,33 @@ void EntityCollisionSystem::updateCollisionWithVoxels(EntityItem* entity) {
}
void EntityCollisionSystem::updateCollisionWithEntities(EntityItem* entityA) {
- glm::vec3 center = entityA->getPosition() * (float)(TREE_SCALE);
- float radius = entityA->getRadius() * (float)(TREE_SCALE);
+
+ if (entityA->getIgnoreForCollisions()) {
+ return; // bail early if this entity is to be ignored...
+ }
+
glm::vec3 penetration;
- EntityItem* entityB;
- if (_entities->findSpherePenetration(center, radius, penetration, (void**)&entityB, Octree::NoLock)) {
+ EntityItem* entityB = NULL;
+
+ const int MAX_COLLISIONS_PER_ENTITY = 32;
+ CollisionList collisions(MAX_COLLISIONS_PER_ENTITY);
+ bool shapeCollisionsAccurate = false;
+
+ bool shapeCollisions = _entities->findShapeCollisions(&entityA->getCollisionShapeInMeters(),
+ collisions, Octree::NoLock, &shapeCollisionsAccurate);
+
+ if (shapeCollisions) {
+ for(int i = 0; i < collisions.size(); i++) {
+ CollisionInfo* collision = collisions[i];
+ penetration = collision->_penetration;
+ entityB = static_cast(collision->_extraData);
+
+ // TODO: how to handle multiple collisions?
+ break;
+ }
+ }
+
+ if (shapeCollisions) {
// NOTE: 'penetration' is the depth that 'entityA' overlaps 'entityB'. It points from A into B.
glm::vec3 penetrationInTreeUnits = penetration / (float)(TREE_SCALE);
@@ -115,8 +139,13 @@ void EntityCollisionSystem::updateCollisionWithEntities(EntityItem* entityA) {
// we don't want to count this as a collision.
glm::vec3 relativeVelocity = entityA->getVelocity() - entityB->getVelocity();
+ bool wantToMoveA = entityA->getCollisionsWillMove();
+ bool wantToMoveB = entityB->getCollisionsWillMove();
bool movingTowardEachOther = glm::dot(relativeVelocity, penetrationInTreeUnits) > 0.0f;
- bool doCollisions = movingTowardEachOther; // don't do collisions if the entities are moving away from each other
+
+ // only do collisions if the entities are moving toward each other and one or the other
+ // of the entities are movable from collisions
+ bool doCollisions = movingTowardEachOther && (wantToMoveA || wantToMoveB);
if (doCollisions) {
quint64 now = usecTimestampNow();
@@ -133,35 +162,53 @@ void EntityCollisionSystem::updateCollisionWithEntities(EntityItem* entityA) {
float massA = entityA->getMass();
float massB = entityB->getMass();
float totalMass = massA + massB;
+ float massRatioA = (2.0f * massB / totalMass);
+ float massRatioB = (2.0f * massA / totalMass);
- // handle Entity A
- glm::vec3 newVelocityA = entityA->getVelocity() - axialVelocity * (2.0f * massB / totalMass);
- glm::vec3 newPositionA = entityA->getPosition() - 0.5f * penetrationInTreeUnits;
+ // in the event that one of our entities is non-moving, then fix up these ratios
+ if (wantToMoveA && !wantToMoveB) {
+ massRatioA = 2.0f;
+ massRatioB = 0.0f;
+ }
- EntityItemProperties propertiesA = entityA->getProperties();
- EntityItemID idA(entityA->getID());
- propertiesA.setVelocity(newVelocityA * (float)TREE_SCALE);
- propertiesA.setPosition(newPositionA * (float)TREE_SCALE);
- propertiesA.setLastEdited(now);
+ if (!wantToMoveA && wantToMoveB) {
+ massRatioA = 0.0f;
+ massRatioB = 2.0f;
+ }
- _entities->updateEntity(idA, propertiesA);
- _packetSender->queueEditEntityMessage(PacketTypeEntityAddOrEdit, idA, propertiesA);
+ // unless the entity is configured to not be moved by collision, calculate it's new position
+ // and velocity and apply it
+ if (wantToMoveA) {
+ // handle Entity A
+ glm::vec3 newVelocityA = entityA->getVelocity() - axialVelocity * massRatioA;
+ glm::vec3 newPositionA = entityA->getPosition() - 0.5f * penetrationInTreeUnits;
- glm::vec3 newVelocityB = entityB->getVelocity() + axialVelocity * (2.0f * massA / totalMass);
- glm::vec3 newPositionB = entityB->getPosition() + 0.5f * penetrationInTreeUnits;
+ EntityItemProperties propertiesA = entityA->getProperties();
+ EntityItemID idA(entityA->getID());
+ propertiesA.setVelocity(newVelocityA * (float)TREE_SCALE);
+ propertiesA.setPosition(newPositionA * (float)TREE_SCALE);
+ propertiesA.setLastEdited(now);
+
+ _entities->updateEntity(idA, propertiesA);
+ _packetSender->queueEditEntityMessage(PacketTypeEntityAddOrEdit, idA, propertiesA);
+ }
- EntityItemProperties propertiesB = entityB->getProperties();
+ // unless the entity is configured to not be moved by collision, calculate it's new position
+ // and velocity and apply it
+ if (wantToMoveB) {
+ glm::vec3 newVelocityB = entityB->getVelocity() + axialVelocity * massRatioB;
+ glm::vec3 newPositionB = entityB->getPosition() + 0.5f * penetrationInTreeUnits;
- EntityItemID idB(entityB->getID());
- propertiesB.setVelocity(newVelocityB * (float)TREE_SCALE);
- propertiesB.setPosition(newPositionB * (float)TREE_SCALE);
- propertiesB.setLastEdited(now);
+ EntityItemProperties propertiesB = entityB->getProperties();
- _entities->updateEntity(idB, propertiesB);
- _packetSender->queueEditEntityMessage(PacketTypeEntityAddOrEdit, idB, propertiesB);
-
- // TODO: Do we need this?
- //_packetSender->releaseQueuedMessages();
+ EntityItemID idB(entityB->getID());
+ propertiesB.setVelocity(newVelocityB * (float)TREE_SCALE);
+ propertiesB.setPosition(newPositionB * (float)TREE_SCALE);
+ propertiesB.setLastEdited(now);
+
+ _entities->updateEntity(idB, propertiesB);
+ _packetSender->queueEditEntityMessage(PacketTypeEntityAddOrEdit, idB, propertiesB);
+ }
}
}
}
diff --git a/libraries/entities/src/EntityItem.cpp b/libraries/entities/src/EntityItem.cpp
index cfed16c443..86fac0997e 100644
--- a/libraries/entities/src/EntityItem.cpp
+++ b/libraries/entities/src/EntityItem.cpp
@@ -42,6 +42,8 @@ const glm::vec3 EntityItem::NO_ANGULAR_VELOCITY = glm::vec3(0.0f, 0.0f, 0.0f);
const glm::vec3 EntityItem::DEFAULT_ANGULAR_VELOCITY = NO_ANGULAR_VELOCITY;
const float EntityItem::DEFAULT_ANGULAR_DAMPING = 0.5f;
const bool EntityItem::DEFAULT_VISIBLE = true;
+const bool EntityItem::DEFAULT_IGNORE_FOR_COLLISIONS = false;
+const bool EntityItem::DEFAULT_COLLISIONS_WILL_MOVE = false;
void EntityItem::initFromEntityItemID(const EntityItemID& entityItemID) {
_id = entityItemID.id;
@@ -70,6 +72,10 @@ void EntityItem::initFromEntityItemID(const EntityItemID& entityItemID) {
_angularVelocity = DEFAULT_ANGULAR_VELOCITY;
_angularDamping = DEFAULT_ANGULAR_DAMPING;
_visible = DEFAULT_VISIBLE;
+ _ignoreForCollisions = DEFAULT_IGNORE_FOR_COLLISIONS;
+ _collisionsWillMove = DEFAULT_COLLISIONS_WILL_MOVE;
+
+ recalculateCollisionShape();
}
EntityItem::EntityItem(const EntityItemID& entityItemID) {
@@ -109,6 +115,8 @@ EntityPropertyFlags EntityItem::getEntityProperties(EncodeBitstreamParams& param
requestedProperties += PROP_ANGULAR_VELOCITY;
requestedProperties += PROP_ANGULAR_DAMPING;
requestedProperties += PROP_VISIBLE;
+ requestedProperties += PROP_IGNORE_FOR_COLLISIONS;
+ requestedProperties += PROP_COLLISIONS_WILL_MOVE;
return requestedProperties;
}
@@ -222,6 +230,8 @@ OctreeElement::AppendState EntityItem::appendEntityData(OctreePacketData* packet
APPEND_ENTITY_PROPERTY(PROP_ANGULAR_VELOCITY, appendValue, getAngularVelocity());
APPEND_ENTITY_PROPERTY(PROP_ANGULAR_DAMPING, appendValue, getAngularDamping());
APPEND_ENTITY_PROPERTY(PROP_VISIBLE, appendValue, getVisible());
+ APPEND_ENTITY_PROPERTY(PROP_IGNORE_FOR_COLLISIONS, appendValue, getIgnoreForCollisions());
+ APPEND_ENTITY_PROPERTY(PROP_COLLISIONS_WILL_MOVE, appendValue, getCollisionsWillMove());
appendSubclassData(packetData, params, entityTreeElementExtraEncodeData,
requestedProperties,
@@ -482,14 +492,19 @@ int EntityItem::readEntityDataFromBuffer(const unsigned char* data, int bytesLef
READ_ENTITY_PROPERTY(PROP_ANGULAR_VELOCITY, glm::vec3, _angularVelocity);
READ_ENTITY_PROPERTY(PROP_ANGULAR_DAMPING, float, _angularDamping);
READ_ENTITY_PROPERTY(PROP_VISIBLE, bool, _visible);
+ READ_ENTITY_PROPERTY(PROP_IGNORE_FOR_COLLISIONS, bool, _ignoreForCollisions);
+ READ_ENTITY_PROPERTY(PROP_COLLISIONS_WILL_MOVE, bool, _collisionsWillMove);
if (wantDebug) {
qDebug() << " readEntityDataFromBuffer() _registrationPoint:" << _registrationPoint;
qDebug() << " readEntityDataFromBuffer() _visible:" << _visible;
+ qDebug() << " readEntityDataFromBuffer() _ignoreForCollisions:" << _ignoreForCollisions;
+ qDebug() << " readEntityDataFromBuffer() _collisionsWillMove:" << _collisionsWillMove;
}
bytesRead += readEntitySubclassDataFromBuffer(dataAt, (bytesLeftToRead - bytesRead), args, propertyFlags, overwriteLocalData);
+ recalculateCollisionShape();
}
return bytesRead;
}
@@ -675,7 +690,7 @@ void EntityItem::update(const quint64& updateTime) {
velocity = NO_VELOCITY;
}
- setPosition(position);
+ setPosition(position); // this will automatically recalculate our collision shape
setVelocity(velocity);
if (wantDebug) {
@@ -731,6 +746,8 @@ EntityItemProperties EntityItem::getProperties() const {
COPY_ENTITY_PROPERTY_TO_PROPERTIES(angularDamping, getAngularDamping);
COPY_ENTITY_PROPERTY_TO_PROPERTIES(glowLevel, getGlowLevel);
COPY_ENTITY_PROPERTY_TO_PROPERTIES(visible, getVisible);
+ COPY_ENTITY_PROPERTY_TO_PROPERTIES(ignoreForCollisions, getIgnoreForCollisions);
+ COPY_ENTITY_PROPERTY_TO_PROPERTIES(collisionsWillMove, getCollisionsWillMove);
properties._defaultSettings = false;
@@ -749,7 +766,7 @@ bool EntityItem::setProperties(const EntityItemProperties& properties, bool forc
}
}
- SET_ENTITY_PROPERTY_FROM_PROPERTIES(position, setPositionInMeters);
+ SET_ENTITY_PROPERTY_FROM_PROPERTIES(position, setPositionInMeters); // this will call recalculate collision shape if needed
SET_ENTITY_PROPERTY_FROM_PROPERTIES(dimensions, setDimensionsInMeters); // NOTE: radius is obsolete
SET_ENTITY_PROPERTY_FROM_PROPERTIES(rotation, setRotation);
SET_ENTITY_PROPERTY_FROM_PROPERTIES(mass, setMass);
@@ -763,6 +780,8 @@ bool EntityItem::setProperties(const EntityItemProperties& properties, bool forc
SET_ENTITY_PROPERTY_FROM_PROPERTIES(angularDamping, setAngularDamping);
SET_ENTITY_PROPERTY_FROM_PROPERTIES(glowLevel, setGlowLevel);
SET_ENTITY_PROPERTY_FROM_PROPERTIES(visible, setVisible);
+ SET_ENTITY_PROPERTY_FROM_PROPERTIES(ignoreForCollisions, setIgnoreForCollisions);
+ SET_ENTITY_PROPERTY_FROM_PROPERTIES(collisionsWillMove, setCollisionsWillMove);
if (somethingChanged) {
somethingChangedNotification(); // notify derived classes that something has changed
@@ -903,3 +922,11 @@ float EntityItem::getRadius() const {
return radius;
}
+void EntityItem::recalculateCollisionShape() {
+ AACube entityAACube = getMinimumAACube();
+ entityAACube.scale(TREE_SCALE); // scale to meters
+ _collisionShape.setTranslation(entityAACube.calcCenter());
+ _collisionShape.setScale(entityAACube.getScale());
+}
+
+
diff --git a/libraries/entities/src/EntityItem.h b/libraries/entities/src/EntityItem.h
index bbfba30e9c..621bd94287 100644
--- a/libraries/entities/src/EntityItem.h
+++ b/libraries/entities/src/EntityItem.h
@@ -16,6 +16,7 @@
#include
+#include
#include // for Animation, AnimationCache, and AnimationPointer classes
#include // for EncodeBitstreamParams class
#include // for OctreeElement::AppendState
@@ -123,7 +124,9 @@ public:
EntityTypes::EntityType getType() const { return _type; }
const glm::vec3& getPosition() const { return _position; } /// get position in domain scale units (0.0 - 1.0)
glm::vec3 getPositionInMeters() const { return _position * (float) TREE_SCALE; } /// get position in meters
- void setPosition(const glm::vec3& value) { _position = value; } /// set position in domain scale units (0.0 - 1.0)
+
+ /// set position in domain scale units (0.0 - 1.0)
+ void setPosition(const glm::vec3& value) { _position = value; recalculateCollisionShape(); }
void setPositionInMeters(const glm::vec3& value) /// set position in meter units (0.0 - TREE_SCALE)
{ setPosition(glm::clamp(value / (float) TREE_SCALE, 0.0f, 1.0f)); }
@@ -137,14 +140,14 @@ public:
float getLargestDimension() const { return glm::length(_dimensions); } /// get the largest possible dimension
/// set dimensions in domain scale units (0.0 - 1.0) this will also reset radius appropriately
- void setDimensions(const glm::vec3& value) { _dimensions = value; }
+ void setDimensions(const glm::vec3& value) { _dimensions = value; ; recalculateCollisionShape(); }
/// set dimensions in meter units (0.0 - TREE_SCALE) this will also reset radius appropriately
void setDimensionsInMeters(const glm::vec3& value) { setDimensions(value / (float) TREE_SCALE); }
static const glm::quat DEFAULT_ROTATION;
const glm::quat& getRotation() const { return _rotation; }
- void setRotation(const glm::quat& rotation) { _rotation = rotation; }
+ void setRotation(const glm::quat& rotation) { _rotation = rotation; ; recalculateCollisionShape(); }
static const float DEFAULT_GLOW_LEVEL;
float getGlowLevel() const { return _glowLevel; }
@@ -207,7 +210,10 @@ public:
static const glm::vec3 DEFAULT_REGISTRATION_POINT;
const glm::vec3& getRegistrationPoint() const { return _registrationPoint; } /// registration point as ratio of entity
- void setRegistrationPoint(const glm::vec3& value) { _registrationPoint = glm::clamp(value, 0.0f, 1.0f); } /// registration point as ratio of entity
+
+ /// registration point as ratio of entity
+ void setRegistrationPoint(const glm::vec3& value)
+ { _registrationPoint = glm::clamp(value, 0.0f, 1.0f); recalculateCollisionShape(); }
static const glm::vec3 NO_ANGULAR_VELOCITY;
static const glm::vec3 DEFAULT_ANGULAR_VELOCITY;
@@ -224,14 +230,24 @@ public:
void setVisible(bool value) { _visible = value; }
bool isVisible() const { return _visible; }
bool isInvisible() const { return !_visible; }
+
+ static const bool DEFAULT_IGNORE_FOR_COLLISIONS;
+ bool getIgnoreForCollisions() const { return _ignoreForCollisions; }
+ void setIgnoreForCollisions(bool value) { _ignoreForCollisions = value; }
+
+ static const bool DEFAULT_COLLISIONS_WILL_MOVE;
+ bool getCollisionsWillMove() const { return _collisionsWillMove; }
+ void setCollisionsWillMove(bool value) { _collisionsWillMove = value; }
// TODO: We need to get rid of these users of getRadius()...
float getRadius() const;
void applyHardCollision(const CollisionInfo& collisionInfo);
+ virtual const Shape& getCollisionShapeInMeters() const { return _collisionShape; }
protected:
virtual void initFromEntityItemID(const EntityItemID& entityItemID); // maybe useful to allow subclasses to init
+ virtual void recalculateCollisionShape();
EntityTypes::EntityType _type;
QUuid _id;
@@ -257,6 +273,8 @@ protected:
glm::vec3 _angularVelocity;
float _angularDamping;
bool _visible;
+ bool _ignoreForCollisions;
+ bool _collisionsWillMove;
// NOTE: Radius support is obsolete, but these private helper functions are available for this class to
// parse old data streams
@@ -264,6 +282,7 @@ protected:
/// set radius in domain scale units (0.0 - 1.0) this will also reset dimensions to be equal for each axis
void setRadius(float value);
+ AACubeShape _collisionShape;
};
diff --git a/libraries/entities/src/EntityItemProperties.cpp b/libraries/entities/src/EntityItemProperties.cpp
index 7cc5ebb545..f54ce274f9 100644
--- a/libraries/entities/src/EntityItemProperties.cpp
+++ b/libraries/entities/src/EntityItemProperties.cpp
@@ -41,6 +41,8 @@ EntityItemProperties::EntityItemProperties() :
_angularVelocity(EntityItem::DEFAULT_ANGULAR_VELOCITY),
_angularDamping(EntityItem::DEFAULT_ANGULAR_DAMPING),
_visible(EntityItem::DEFAULT_VISIBLE),
+ _ignoreForCollisions(EntityItem::DEFAULT_IGNORE_FOR_COLLISIONS),
+ _collisionsWillMove(EntityItem::DEFAULT_COLLISIONS_WILL_MOVE),
_positionChanged(false),
_dimensionsChanged(false),
@@ -55,6 +57,8 @@ EntityItemProperties::EntityItemProperties() :
_angularVelocityChanged(false),
_angularDampingChanged(false),
_visibleChanged(false),
+ _ignoreForCollisionsChanged(false),
+ _collisionsWillMoveChanged(false),
_color(),
_modelURL(""),
@@ -112,6 +116,8 @@ EntityPropertyFlags EntityItemProperties::getChangedProperties() const {
CHECK_PROPERTY_CHANGE(PROP_REGISTRATION_POINT, registrationPoint);
CHECK_PROPERTY_CHANGE(PROP_ANGULAR_VELOCITY, angularVelocity);
CHECK_PROPERTY_CHANGE(PROP_ANGULAR_DAMPING, angularDamping);
+ CHECK_PROPERTY_CHANGE(PROP_IGNORE_FOR_COLLISIONS, ignoreForCollisions);
+ CHECK_PROPERTY_CHANGE(PROP_COLLISIONS_WILL_MOVE, collisionsWillMove);
return changedProperties;
}
@@ -134,6 +140,7 @@ QScriptValue EntityItemProperties::copyToScriptValue(QScriptEngine* engine) cons
COPY_PROPERTY_TO_QSCRIPTVALUE_VEC3(velocity);
COPY_PROPERTY_TO_QSCRIPTVALUE_VEC3(gravity);
COPY_PROPERTY_TO_QSCRIPTVALUE(damping);
+ COPY_PROPERTY_TO_QSCRIPTVALUE(mass);
COPY_PROPERTY_TO_QSCRIPTVALUE(lifetime);
COPY_PROPERTY_TO_QSCRIPTVALUE_GETTER(age, getAge()); // gettable, but not settable
COPY_PROPERTY_TO_QSCRIPTVALUE_GETTER(ageAsText, formatSecondsElapsed(getAge())); // gettable, but not settable
@@ -149,6 +156,8 @@ QScriptValue EntityItemProperties::copyToScriptValue(QScriptEngine* engine) cons
COPY_PROPERTY_TO_QSCRIPTVALUE(animationFrameIndex);
COPY_PROPERTY_TO_QSCRIPTVALUE(animationFPS);
COPY_PROPERTY_TO_QSCRIPTVALUE(glowLevel);
+ COPY_PROPERTY_TO_QSCRIPTVALUE(ignoreForCollisions);
+ COPY_PROPERTY_TO_QSCRIPTVALUE(collisionsWillMove);
// Sitting properties support
QScriptValue sittingPoints = engine->newObject();
@@ -193,6 +202,8 @@ void EntityItemProperties::copyFromScriptValue(const QScriptValue& object) {
COPY_PROPERTY_FROM_QSCRIPTVALUE_FLOAT(animationFPS, setAnimationFPS);
COPY_PROPERTY_FROM_QSCRIPTVALUE_FLOAT(animationFrameIndex, setAnimationFrameIndex);
COPY_PROPERTY_FROM_QSCRIPTVALUE_FLOAT(glowLevel, setGlowLevel);
+ COPY_PROPERTY_FROM_QSCRIPTVALUE_BOOL(ignoreForCollisions, setIgnoreForCollisions);
+ COPY_PROPERTY_FROM_QSCRIPTVALUE_BOOL(collisionsWillMove, setCollisionsWillMove);
_lastEdited = usecTimestampNow();
}
@@ -341,6 +352,8 @@ bool EntityItemProperties::encodeEntityEditPacket(PacketType command, EntityItem
APPEND_ENTITY_PROPERTY(PROP_ANGULAR_VELOCITY, appendValue, properties.getAngularVelocity());
APPEND_ENTITY_PROPERTY(PROP_ANGULAR_DAMPING, appendValue, properties.getAngularDamping());
APPEND_ENTITY_PROPERTY(PROP_VISIBLE, appendValue, properties.getVisible());
+ APPEND_ENTITY_PROPERTY(PROP_IGNORE_FOR_COLLISIONS, appendValue, properties.getIgnoreForCollisions());
+ APPEND_ENTITY_PROPERTY(PROP_COLLISIONS_WILL_MOVE, appendValue, properties.getCollisionsWillMove());
}
if (propertyCount > 0) {
int endOfEntityItemData = packetData->getUncompressedByteOffset();
@@ -537,6 +550,8 @@ bool EntityItemProperties::decodeEntityEditPacket(const unsigned char* data, int
READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_ANGULAR_VELOCITY, glm::vec3, setAngularVelocity);
READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_ANGULAR_DAMPING, float, setAngularDamping);
READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_VISIBLE, bool, setVisible);
+ READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_IGNORE_FOR_COLLISIONS, bool, setIgnoreForCollisions);
+ READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_COLLISIONS_WILL_MOVE, bool, setCollisionsWillMove);
return valid;
}
diff --git a/libraries/entities/src/EntityItemProperties.h b/libraries/entities/src/EntityItemProperties.h
index f4f9343f76..0d993acda9 100644
--- a/libraries/entities/src/EntityItemProperties.h
+++ b/libraries/entities/src/EntityItemProperties.h
@@ -62,8 +62,10 @@ enum EntityPropertyList {
PROP_REGISTRATION_POINT,
PROP_ANGULAR_VELOCITY,
PROP_ANGULAR_DAMPING,
+ PROP_IGNORE_FOR_COLLISIONS,
+ PROP_COLLISIONS_WILL_MOVE,
- PROP_LAST_ITEM = PROP_ANGULAR_DAMPING
+ PROP_LAST_ITEM = PROP_COLLISIONS_WILL_MOVE
};
typedef PropertyFlags EntityPropertyFlags;
@@ -221,6 +223,12 @@ public:
bool getVisible() const { return _visible; }
void setVisible(bool value) { _visible = value; _visibleChanged = true; }
+ bool getIgnoreForCollisions() const { return _ignoreForCollisions; }
+ void setIgnoreForCollisions(bool value) { _ignoreForCollisions = value; _ignoreForCollisionsChanged = true; }
+
+ bool getCollisionsWillMove() const { return _collisionsWillMove; }
+ void setCollisionsWillMove(bool value) { _collisionsWillMove = value; _collisionsWillMoveChanged = true; }
+
void setLastEdited(quint64 usecTime) { _lastEdited = usecTime; }
private:
@@ -247,6 +255,8 @@ private:
glm::vec3 _angularVelocity;
float _angularDamping;
bool _visible;
+ bool _ignoreForCollisions;
+ bool _collisionsWillMove;
bool _positionChanged;
bool _dimensionsChanged;
@@ -261,6 +271,8 @@ private:
bool _angularVelocityChanged;
bool _angularDampingChanged;
bool _visibleChanged;
+ bool _ignoreForCollisionsChanged;
+ bool _collisionsWillMoveChanged;
// TODO: this need to be more generic. for now, we're going to have the properties class support these as
// named getter/setters, but we want to move them to generic types...
diff --git a/libraries/entities/src/EntityTreeElement.cpp b/libraries/entities/src/EntityTreeElement.cpp
index a58dd9065f..5df98d43a8 100644
--- a/libraries/entities/src/EntityTreeElement.cpp
+++ b/libraries/entities/src/EntityTreeElement.cpp
@@ -11,6 +11,9 @@
#include
+#include
+#include
+
#include
#include
@@ -546,6 +549,27 @@ bool EntityTreeElement::findSpherePenetration(const glm::vec3& center, float rad
return false;
}
+bool EntityTreeElement::findShapeCollisions(const Shape* shape, CollisionList& collisions) const {
+ bool atLeastOneCollision = false;
+ QList::iterator entityItr = _entityItems->begin();
+ QList::const_iterator entityEnd = _entityItems->end();
+ while(entityItr != entityEnd) {
+ EntityItem* entity = (*entityItr);
+
+ // entities that are set for ignore for collisions then don't consider them for collision
+ const Shape* otherCollisionShape = &entity->getCollisionShapeInMeters();
+ if (shape != otherCollisionShape && !entity->getIgnoreForCollisions()) {
+ if (ShapeCollider::collideShapes(shape, otherCollisionShape, collisions)) {
+ CollisionInfo* lastCollision = collisions.getLastCollision();
+ lastCollision->_extraData = entity;
+ atLeastOneCollision = true;
+ }
+ }
+ ++entityItr;
+ }
+ return atLeastOneCollision;
+}
+
void EntityTreeElement::updateEntityItemID(const EntityItemID& creatorTokenEntityID, const EntityItemID& knownIDEntityID) {
uint16_t numberOfEntities = _entityItems->size();
for (uint16_t i = 0; i < numberOfEntities; i++) {
diff --git a/libraries/entities/src/EntityTreeElement.h b/libraries/entities/src/EntityTreeElement.h
index 5790903411..ab3754749b 100644
--- a/libraries/entities/src/EntityTreeElement.h
+++ b/libraries/entities/src/EntityTreeElement.h
@@ -142,6 +142,8 @@ public:
virtual bool findSpherePenetration(const glm::vec3& center, float radius,
glm::vec3& penetration, void** penetratedObject) const;
+ virtual bool findShapeCollisions(const Shape* shape, CollisionList& collisions) const;
+
const QList& getEntities() const { return *_entityItems; }
QList& getEntities() { return *_entityItems; }
bool hasEntities() const { return _entityItems ? _entityItems->size() > 0 : false; }
diff --git a/libraries/entities/src/SphereEntityItem.cpp b/libraries/entities/src/SphereEntityItem.cpp
index 09364ddbfe..5da218c11a 100644
--- a/libraries/entities/src/SphereEntityItem.cpp
+++ b/libraries/entities/src/SphereEntityItem.cpp
@@ -88,4 +88,11 @@ void SphereEntityItem::appendSubclassData(OctreePacketData* packetData, EncodeBi
bool successPropertyFits = true;
APPEND_ENTITY_PROPERTY(PROP_COLOR, appendColor, getColor());
-}
\ No newline at end of file
+}
+
+void SphereEntityItem::recalculateCollisionShape() {
+ _sphereShape.setTranslation(getCenterInMeters());
+ glm::vec3 dimensionsInMeters = getDimensionsInMeters();
+ float largestDiameter = glm::max(dimensionsInMeters.x, dimensionsInMeters.y, dimensionsInMeters.z);
+ _sphereShape.setRadius(largestDiameter / 2.0f);
+}
diff --git a/libraries/entities/src/SphereEntityItem.h b/libraries/entities/src/SphereEntityItem.h
index 337f229a69..d57ada760b 100644
--- a/libraries/entities/src/SphereEntityItem.h
+++ b/libraries/entities/src/SphereEntityItem.h
@@ -12,7 +12,8 @@
#ifndef hifi_SphereEntityItem_h
#define hifi_SphereEntityItem_h
-#include "EntityItem.h"
+#include
+#include "EntityItem.h"
class SphereEntityItem : public EntityItem {
public:
@@ -49,9 +50,14 @@ public:
_color[GREEN_INDEX] = value.green;
_color[BLUE_INDEX] = value.blue;
}
+
+ virtual const Shape& getCollisionShapeInMeters() const { return _sphereShape; }
protected:
+ virtual void recalculateCollisionShape();
+
rgbColor _color;
+ SphereShape _sphereShape;
};
#endif // hifi_SphereEntityItem_h
diff --git a/libraries/networking/src/AddressManager.cpp b/libraries/networking/src/AddressManager.cpp
index 8ea6d1107a..9a7da955d6 100644
--- a/libraries/networking/src/AddressManager.cpp
+++ b/libraries/networking/src/AddressManager.cpp
@@ -80,6 +80,7 @@ bool AddressManager::handleUrl(const QUrl& lookupUrl) {
// if this is a relative path then handle it as a relative viewpoint
handleRelativeViewpoint(lookupUrl.path());
+ emit lookupResultsFinished();
}
return false;
@@ -149,6 +150,7 @@ void AddressManager::handleAPIResponse(const QJsonObject &jsonObject) {
// we've been told that this result exists but is offline, emit our signal so the application can handle
emit lookupResultIsOffline();
}
+ emit lookupResultsFinished();
}
void AddressManager::handleAPIError(QNetworkReply& errorReply) {
@@ -157,6 +159,7 @@ void AddressManager::handleAPIError(QNetworkReply& errorReply) {
if (errorReply.error() == QNetworkReply::ContentNotFoundError) {
emit lookupResultIsNotFound();
}
+ emit lookupResultsFinished();
}
const QString GET_PLACE = "/api/v1/places/%1";
@@ -164,7 +167,7 @@ const QString GET_PLACE = "/api/v1/places/%1";
void AddressManager::attemptPlaceNameLookup(const QString& lookupString) {
// assume this is a place name and see if we can get any info on it
QString placeName = QUrl::toPercentEncoding(lookupString);
- AccountManager::getInstance().authenticatedRequest(GET_PLACE.arg(placeName),
+ AccountManager::getInstance().unauthenticatedRequest(GET_PLACE.arg(placeName),
QNetworkAccessManager::GetOperation,
apiCallbackParameters());
}
@@ -180,6 +183,7 @@ bool AddressManager::handleNetworkAddress(const QString& lookupString) {
if (hostnameRegex.indexIn(lookupString) != -1) {
emit possibleDomainChangeRequired(hostnameRegex.cap(0));
+ emit lookupResultsFinished();
return true;
}
@@ -187,6 +191,7 @@ bool AddressManager::handleNetworkAddress(const QString& lookupString) {
if (ipAddressRegex.indexIn(lookupString) != -1) {
emit possibleDomainChangeRequired(ipAddressRegex.cap(0));
+ emit lookupResultsFinished();
return true;
}
@@ -263,7 +268,7 @@ bool AddressManager::handleUsername(const QString& lookupString) {
void AddressManager::goToUser(const QString& username) {
QString formattedUsername = QUrl::toPercentEncoding(username);
// this is a username - pull the captured name and lookup that user's location
- AccountManager::getInstance().authenticatedRequest(GET_USER_LOCATION.arg(formattedUsername),
+ AccountManager::getInstance().unauthenticatedRequest(GET_USER_LOCATION.arg(formattedUsername),
QNetworkAccessManager::GetOperation,
apiCallbackParameters());
}
diff --git a/libraries/networking/src/AddressManager.h b/libraries/networking/src/AddressManager.h
index 2590e8f80c..f7cc7c52ee 100644
--- a/libraries/networking/src/AddressManager.h
+++ b/libraries/networking/src/AddressManager.h
@@ -37,6 +37,7 @@ public slots:
void handleAPIError(QNetworkReply& errorReply);
void goToUser(const QString& username);
signals:
+ void lookupResultsFinished();
void lookupResultIsOffline();
void lookupResultIsNotFound();
void possibleDomainChangeRequired(const QString& newHostname);
diff --git a/libraries/octree/src/Octree.cpp b/libraries/octree/src/Octree.cpp
index df2e40d96f..ceda91a441 100644
--- a/libraries/octree/src/Octree.cpp
+++ b/libraries/octree/src/Octree.cpp
@@ -817,9 +817,6 @@ bool findCapsulePenetrationOp(OctreeElement* element, void* extraData) {
if (!box.expandedIntersectsSegment(args->start, args->end, args->radius)) {
return false;
}
- if (!element->isLeaf()) {
- return true; // recurse on children
- }
if (element->hasContent()) {
glm::vec3 nodePenetration;
if (box.findCapsulePenetration(args->start, args->end, args->radius, nodePenetration)) {
@@ -827,27 +824,29 @@ bool findCapsulePenetrationOp(OctreeElement* element, void* extraData) {
args->found = true;
}
}
+ if (!element->isLeaf()) {
+ return true; // recurse on children
+ }
return false;
}
bool findShapeCollisionsOp(OctreeElement* element, void* extraData) {
ShapeArgs* args = static_cast(extraData);
-
// coarse check against bounds
AACube cube = element->getAACube();
cube.scale(TREE_SCALE);
if (!cube.expandedContains(args->shape->getTranslation(), args->shape->getBoundingRadius())) {
return false;
}
- if (!element->isLeaf()) {
- return true; // recurse on children
- }
if (element->hasContent()) {
- if (ShapeCollider::collideShapeWithAACubeLegacy(args->shape, cube.calcCenter(), cube.getScale(), args->collisions)) {
+ if (element->findShapeCollisions(args->shape, args->collisions)) {
args->found = true;
return true;
}
}
+ if (!element->isLeaf()) {
+ return true; // recurse on children
+ }
return false;
}
diff --git a/libraries/octree/src/OctreeElement.cpp b/libraries/octree/src/OctreeElement.cpp
index e641ddece2..271e885d17 100644
--- a/libraries/octree/src/OctreeElement.cpp
+++ b/libraries/octree/src/OctreeElement.cpp
@@ -9,6 +9,7 @@
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
+#include
#include
#include
#include
@@ -17,7 +18,8 @@
#include
#include
-#include
+#include
+#include
#include "AACube.h"
#include "OctalCode.h"
@@ -1369,6 +1371,11 @@ bool OctreeElement::findSpherePenetration(const glm::vec3& center, float radius,
return _cube.findSpherePenetration(center, radius, penetration);
}
+bool OctreeElement::findShapeCollisions(const Shape* shape, CollisionList& collisions) const {
+ AACube cube = getAACube();
+ cube.scale(TREE_SCALE);
+ return ShapeCollider::collideShapeWithAACubeLegacy(shape, cube.calcCenter(), cube.getScale(), collisions);
+}
// TODO: consider removing this, or switching to using getOrCreateChildElementContaining(const AACube& box)...
OctreeElement* OctreeElement::getOrCreateChildElementAt(float x, float y, float z, float s) {
diff --git a/libraries/octree/src/OctreeElement.h b/libraries/octree/src/OctreeElement.h
index 093a35720f..31a9dfddc1 100644
--- a/libraries/octree/src/OctreeElement.h
+++ b/libraries/octree/src/OctreeElement.h
@@ -25,6 +25,7 @@
#include "ViewFrustum.h"
#include "OctreeConstants.h"
+class CollisionList;
class EncodeBitstreamParams;
class Octree;
class OctreeElement;
@@ -32,6 +33,7 @@ class OctreeElementBag;
class OctreeElementDeleteHook;
class OctreePacketData;
class ReadBitstreamToTreeParams;
+class Shape;
class VoxelSystem;
const float SMALLEST_REASONABLE_OCTREE_ELEMENT_SCALE = (1.0f / TREE_SCALE) / 10000.0f; // 1/10,000th of a meter
@@ -128,6 +130,8 @@ public:
virtual bool findSpherePenetration(const glm::vec3& center, float radius,
glm::vec3& penetration, void** penetratedObject) const;
+ virtual bool findShapeCollisions(const Shape* shape, CollisionList& collisions) const;
+
// Base class methods you don't need to implement
const unsigned char* getOctalCode() const { return (_octcodePointer) ? _octalCode.pointer : &_octalCode.buffer[0]; }
OctreeElement* getChildAtIndex(int childIndex) const;
diff --git a/libraries/script-engine/src/ScriptEngine.cpp b/libraries/script-engine/src/ScriptEngine.cpp
index 545e4709f9..47e0ced6bf 100644
--- a/libraries/script-engine/src/ScriptEngine.cpp
+++ b/libraries/script-engine/src/ScriptEngine.cpp
@@ -48,7 +48,8 @@ EntityScriptingInterface ScriptEngine::_entityScriptingInterface;
static QScriptValue soundConstructor(QScriptContext* context, QScriptEngine* engine) {
QUrl soundURL = QUrl(context->argument(0).toString());
- QScriptValue soundScriptValue = engine->newQObject(new Sound(soundURL), QScriptEngine::ScriptOwnership);
+ bool isStereo = context->argument(1).toBool();
+ QScriptValue soundScriptValue = engine->newQObject(new Sound(soundURL, isStereo), QScriptEngine::ScriptOwnership);
return soundScriptValue;
}
diff --git a/libraries/shared/src/AACubeShape.h b/libraries/shared/src/AACubeShape.h
index 4b834aa1bf..da7ba9d53f 100644
--- a/libraries/shared/src/AACubeShape.h
+++ b/libraries/shared/src/AACubeShape.h
@@ -12,6 +12,7 @@
#ifndef hifi_AACubeShape_h
#define hifi_AACubeShape_h
+#include
#include "Shape.h"
class AACubeShape : public Shape {
@@ -28,9 +29,22 @@ public:
bool findRayIntersection(RayIntersectionInfo& intersection) const;
float getVolume() const { return _scale * _scale * _scale; }
+ virtual QDebug& dumpToDebug(QDebug& debugConext) const;
protected:
float _scale;
};
+inline QDebug& AACubeShape::dumpToDebug(QDebug& debugConext) const {
+ debugConext << "AACubeShape[ ("
+ << "type: " << getType()
+ << "position: "
+ << getTranslation().x << ", " << getTranslation().y << ", " << getTranslation().z
+ << "scale: "
+ << getScale()
+ << "]";
+
+ return debugConext;
+}
+
#endif // hifi_AACubeShape_h
diff --git a/libraries/shared/src/CollisionInfo.h b/libraries/shared/src/CollisionInfo.h
index 6e70654d15..0f134c1b23 100644
--- a/libraries/shared/src/CollisionInfo.h
+++ b/libraries/shared/src/CollisionInfo.h
@@ -54,6 +54,8 @@ public:
const Shape* _shapeA; // pointer to shapeA in this collision
const Shape* _shapeB; // pointer to shapeB in this collision
+ void* _extraData; // pointer to extraData for this collision, opaque to the collision info, useful for external data
+
float _damping; // range [0,1] of friction coeficient
float _elasticity; // range [0,1] of energy conservation
glm::vec3 _contactPoint; // world-frame point on BodyA that is deepest into BodyB
diff --git a/libraries/shared/src/Shape.h b/libraries/shared/src/Shape.h
index 4b85234eb3..cdf3ba72e5 100644
--- a/libraries/shared/src/Shape.h
+++ b/libraries/shared/src/Shape.h
@@ -14,6 +14,7 @@
#include
#include
+#include
#include
#include
@@ -80,6 +81,8 @@ public:
virtual float getVolume() const { return 1.0; }
virtual void getVerletPoints(QVector& points) {}
+
+ virtual QDebug& dumpToDebug(QDebug& debugConext) const;
protected:
// these ctors are protected (used by derived classes only)
@@ -113,4 +116,25 @@ protected:
float _mass;
};
+inline QDebug& Shape::dumpToDebug(QDebug& debugConext) const {
+ debugConext << "Shape[ ("
+ << "type: " << getType()
+ << "position: "
+ << getTranslation().x << ", " << getTranslation().y << ", " << getTranslation().z
+ << "radius: "
+ << getBoundingRadius()
+ << "]";
+
+ return debugConext;
+}
+
+inline QDebug operator<<(QDebug debug, const Shape& shape) {
+ return shape.dumpToDebug(debug);
+}
+
+inline QDebug operator<<(QDebug debug, const Shape* shape) {
+ return shape->dumpToDebug(debug);
+}
+
+
#endif // hifi_Shape_h
diff --git a/libraries/shared/src/ShapeCollider.cpp b/libraries/shared/src/ShapeCollider.cpp
index be3b086776..3f79fa081a 100644
--- a/libraries/shared/src/ShapeCollider.cpp
+++ b/libraries/shared/src/ShapeCollider.cpp
@@ -20,6 +20,8 @@
#include "PlaneShape.h"
#include "SphereShape.h"
+#include "StreamUtils.h"
+
// NOTE:
//
// * Large ListShape's are inefficient keep the lists short.
@@ -978,7 +980,112 @@ bool aaCubeVsCapsule(const Shape* shapeA, const Shape* shapeB, CollisionList& co
return capsuleVsAACube(shapeB, shapeA, collisions);
}
+// helper function
+CollisionInfo* aaCubeVsAACubeHelper(const glm::vec3& cubeCenterA, float cubeSideA, const glm::vec3& cubeCenterB,
+ float cubeSideB, CollisionList& collisions) {
+ // cube is A
+ // cube is B
+ // BA = B - A = from center of A to center of B
+ float halfCubeSideA = 0.5f * cubeSideA;
+ float halfCubeSideB = 0.5f * cubeSideB;
+ glm::vec3 BA = cubeCenterB - cubeCenterA;
+
+ float distance = glm::length(BA);
+
+ if (distance > EPSILON) {
+ float maxBA = glm::max(glm::max(glm::abs(BA.x), glm::abs(BA.y)), glm::abs(BA.z));
+ if (maxBA > halfCubeSideB + halfCubeSideA) {
+ // cube misses cube entirely
+ return NULL;
+ }
+ CollisionInfo* collision = collisions.getNewCollision();
+ if (!collision) {
+ return NULL; // no more room for collisions
+ }
+ if (maxBA > halfCubeSideB) {
+ // cube hits cube but its center is outside cube
+ // compute contact anti-pole on cube (in cube frame)
+ glm::vec3 cubeContact = glm::abs(BA);
+ if (cubeContact.x > halfCubeSideB) {
+ cubeContact.x = halfCubeSideB;
+ }
+ if (cubeContact.y > halfCubeSideB) {
+ cubeContact.y = halfCubeSideB;
+ }
+ if (cubeContact.z > halfCubeSideB) {
+ cubeContact.z = halfCubeSideB;
+ }
+ glm::vec3 signs = glm::sign(BA);
+ cubeContact.x *= signs.x;
+ cubeContact.y *= signs.y;
+ cubeContact.z *= signs.z;
+
+ // compute penetration direction
+ glm::vec3 direction = BA - cubeContact;
+
+ float lengthDirection = glm::length(direction);
+
+ if (lengthDirection < EPSILON) {
+ // cubeCenterA is touching cube B surface, so we can't use the difference between those two
+ // points to compute the penetration direction. Instead we use the unitary components of
+ // cubeContact.
+ glm::modf(cubeContact / halfCubeSideB, direction);
+ lengthDirection = glm::length(direction);
+ } else if (lengthDirection > halfCubeSideA) {
+ collisions.deleteLastCollision();
+ return NULL;
+ }
+ direction /= lengthDirection;
+
+ // compute collision details
+ collision->_contactPoint = cubeCenterA + halfCubeSideA * direction;
+ collision->_penetration = halfCubeSideA * direction - (BA - cubeContact);
+ } else {
+ // cube center is inside cube
+ // --> push out nearest face
+ glm::vec3 direction;
+ BA /= maxBA;
+ glm::modf(BA, direction);
+ float lengthDirection = glm::length(direction);
+ direction /= lengthDirection;
+
+ // compute collision details
+ collision->_floatData = cubeSideB;
+ collision->_vecData = cubeCenterB;
+ collision->_penetration = (halfCubeSideB * lengthDirection + halfCubeSideA - maxBA * glm::dot(BA, direction)) * direction;
+ collision->_contactPoint = cubeCenterA + halfCubeSideA * direction;
+ }
+ collision->_shapeA = NULL;
+ collision->_shapeB = NULL;
+ return collision;
+ } else if (halfCubeSideA + halfCubeSideB > distance) {
+ // NOTE: for cocentric approximation we collide sphere and cube as two spheres which means
+ // this algorithm will probably be wrong when both sphere and cube are very small (both ~EPSILON)
+ CollisionInfo* collision = collisions.getNewCollision();
+ if (collision) {
+ // the penetration and contactPoint are undefined, so we pick a penetration direction (-yAxis)
+ collision->_penetration = (halfCubeSideA + halfCubeSideB) * glm::vec3(0.0f, -1.0f, 0.0f);
+ // contactPoint is on surface of A
+ collision->_contactPoint = cubeCenterA + collision->_penetration;
+ collision->_shapeA = NULL;
+ collision->_shapeB = NULL;
+ return collision;
+ }
+ }
+ return NULL;
+}
+
bool aaCubeVsAACube(const Shape* shapeA, const Shape* shapeB, CollisionList& collisions) {
+ // BA = B - A = from center of A to center of B
+ const AACubeShape* cubeA = static_cast(shapeA);
+ const AACubeShape* cubeB = static_cast(shapeB);
+ CollisionInfo* collision = aaCubeVsAACubeHelper( cubeA->getTranslation(), cubeA->getScale(),
+ cubeB->getTranslation(), cubeB->getScale(), collisions);
+ if (collision) {
+ collision->_shapeA = shapeA;
+ collision->_shapeB = shapeB;
+ return true;
+ }
return false;
}
diff --git a/libraries/shared/src/SphereShape.h b/libraries/shared/src/SphereShape.h
index b5f2c50d8f..59a53c97d6 100644
--- a/libraries/shared/src/SphereShape.h
+++ b/libraries/shared/src/SphereShape.h
@@ -39,4 +39,26 @@ public:
float getVolume() const { return 1.3333333333f * PI * _boundingRadius * _boundingRadius * _boundingRadius; }
};
+inline QDebug operator<<(QDebug debug, const SphereShape& shape) {
+ debug << "SphereShape[ ("
+ << "position: "
+ << shape.getTranslation().x << ", " << shape.getTranslation().y << ", " << shape.getTranslation().z
+ << "radius: "
+ << shape.getRadius()
+ << "]";
+
+ return debug;
+}
+
+inline QDebug operator<<(QDebug debug, const SphereShape* shape) {
+ debug << "SphereShape[ ("
+ << "center: "
+ << shape->getTranslation().x << ", " << shape->getTranslation().y << ", " << shape->getTranslation().z
+ << "radius: "
+ << shape->getRadius()
+ << "]";
+
+ return debug;
+}
+
#endif // hifi_SphereShape_h