Merge branch 'master' of https://github.com/highfidelity/hifi into domain-wizard

This commit is contained in:
Stephen Birarda 2014-09-26 14:31:52 -07:00
commit e75a2d355e
49 changed files with 1003 additions and 134 deletions

View file

@ -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);

View file

@ -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;

View file

@ -1127,8 +1127,6 @@ function keyPressEvent(event) {
} else if (event.text == "z") {
undoSound.playRandom();
}
}
trackKeyPressEvent(event); // used by preview support

View file

@ -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);

View file

@ -0,0 +1,18 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 18.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 49 49" enable-background="new 0 0 49 49" xml:space="preserve">
<g>
<g>
<path fill="#333" d="M49,46c0,1.7-1.3,3-3,3H3c-1.7,0-3-1.3-3-3V3c0-1.7,1.3-3,3-3h43c1.7,0,3,1.3,3,3V46z"/>
</g>
<g>
<g id="Your_Icon_11_">
<g>
<polygon fill="#E7EEEE" points="23.6,19.9 15.1,19.9 15.1,27.8 23.6,27.8 23.6,33.8 33,23.9 23.6,14 "/>
</g>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 759 B

View file

@ -0,0 +1,18 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 18.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 49 49" enable-background="new 0 0 49 49" xml:space="preserve">
<g>
<g>
<path fill="#0E7077" d="M49,46c0,1.7-1.3,3-3,3H3c-1.7,0-3-1.3-3-3V3c0-1.7,1.3-3,3-3h43c1.7,0,3,1.3,3,3V46z"/>
</g>
<g>
<g id="Your_Icon_11_">
<g>
<polygon fill="#E7EEEE" points="23.6,19.9 15.1,19.9 15.1,27.8 23.6,27.8 23.6,33.8 33,23.9 23.6,14 "/>
</g>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 762 B

View file

@ -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:

View file

@ -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() {

View file

@ -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> _preferencesDialog;
QPointer<AttachmentsDialog> _attachmentsDialog;
QPointer<AnimationsDialog> _animationsDialog;
QPointer<AddressBarDialog> _addressBarDialog;
QPointer<LoginDialog> _loginDialog;
bool _hasLoginDialogDisplayed;
QAction* _chatAction;

View file

@ -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;
}

View file

@ -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;

View file

@ -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<AnimationHandlePointer>& getAnimationHandles() const { return _animationHandles; }

View file

@ -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();

View file

@ -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

View file

@ -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;

View file

@ -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();

View file

@ -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());
}
}

View file

@ -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 <QLineEdit>
#include <QHBoxLayout>
#include <QSpacerItem>
#include <QVBoxLayout>
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

View file

@ -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<const char*>(&_options.getPosition()), sizeof(_options.getPosition()));
packetStream.writeRawData(reinterpret_cast<const char*>(&_options.getPosition()),
sizeof(_options.getPosition()));
// pack our orientation for injected audio
int orientationOptionOffset = injectAudioPacket.size();
packetStream.writeRawData(reinterpret_cast<const char*>(&_options.getOrientation()), sizeof(_options.getOrientation()));
packetStream.writeRawData(reinterpret_cast<const char*>(&_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;
}
}
}

View file

@ -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*)

View file

@ -13,6 +13,9 @@
AudioInjector* AudioScriptingInterface::playSound(Sound* sound, const AudioInjectorOptions* injectorOptions) {
if (sound->isStereo()) {
const_cast<AudioInjectorOptions*>(injectorOptions)->setIsStereo(true);
}
AudioInjector* injector = new AudioInjector(sound, *injectorOptions);
QThread* injectorThread = new QThread();

View file

@ -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;

View file

@ -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);
}
}
}
}

View file

@ -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();

View file

@ -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;

View file

@ -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:

View file

@ -9,6 +9,7 @@
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#include <AudioRingBuffer.h>
#include <GLMHelpers.h>
#include <NodeList.h>
#include <StreamUtils.h>
@ -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;
}

View file

@ -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<AudioInjector> _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

View file

@ -9,6 +9,7 @@
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#include <AudioRingBuffer.h>
#include <GLMHelpers.h>
#include <NetworkAccessManager.h>
#include <NodeList.h>
@ -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);

View file

@ -13,8 +13,10 @@
#include <AbstractAudioInterface.h>
#include <VoxelTree.h>
#include <AvatarData.h>
#include <CollisionInfo.h>
#include <HeadData.h>
#include <HandData.h>
#include <SphereShape.h>
#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<EntityItem*>(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);
}
}
}
}

View file

@ -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());
}

View file

@ -16,6 +16,7 @@
#include <glm/glm.hpp>
#include <AACubeShape.h>
#include <AnimationCache.h> // for Animation, AnimationCache, and AnimationPointer classes
#include <Octree.h> // for EncodeBitstreamParams class
#include <OctreeElement.h> // 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;
};

View file

@ -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;
}

View file

@ -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<EntityPropertyList> 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...

View file

@ -11,6 +11,9 @@
#include <glm/gtx/transform.hpp>
#include <AACubeShape.h>
#include <ShapeCollider.h>
#include <FBXReader.h>
#include <GeometryUtil.h>
@ -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<EntityItem*>::iterator entityItr = _entityItems->begin();
QList<EntityItem*>::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++) {

View file

@ -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<EntityItem*>& getEntities() const { return *_entityItems; }
QList<EntityItem*>& getEntities() { return *_entityItems; }
bool hasEntities() const { return _entityItems ? _entityItems->size() > 0 : false; }

View file

@ -88,4 +88,11 @@ void SphereEntityItem::appendSubclassData(OctreePacketData* packetData, EncodeBi
bool successPropertyFits = true;
APPEND_ENTITY_PROPERTY(PROP_COLOR, appendColor, getColor());
}
}
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);
}

View file

@ -12,7 +12,8 @@
#ifndef hifi_SphereEntityItem_h
#define hifi_SphereEntityItem_h
#include "EntityItem.h"
#include <SphereShape.h>
#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

View file

@ -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());
}

View file

@ -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);

View file

@ -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<ShapeArgs*>(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;
}

View file

@ -9,6 +9,7 @@
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#include <assert.h>
#include <cmath>
#include <cstring>
#include <stdio.h>
@ -17,7 +18,8 @@
#include <NodeList.h>
#include <PerfStat.h>
#include <assert.h>
#include <AACubeShape.h>
#include <ShapeCollider.h>
#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) {

View file

@ -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;

View file

@ -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;
}

View file

@ -12,6 +12,7 @@
#ifndef hifi_AACubeShape_h
#define hifi_AACubeShape_h
#include <QDebug>
#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

View file

@ -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

View file

@ -14,6 +14,7 @@
#include <glm/glm.hpp>
#include <glm/gtc/quaternion.hpp>
#include <QDebug>
#include <QtGlobal>
#include <QVector>
@ -80,6 +81,8 @@ public:
virtual float getVolume() const { return 1.0; }
virtual void getVerletPoints(QVector<VerletPoint*>& 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

View file

@ -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<const AACubeShape*>(shapeA);
const AACubeShape* cubeB = static_cast<const AACubeShape*>(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;
}

View file

@ -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