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