Merge branch 'master' of https://github.com/highfidelity/hifi into 20061

This commit is contained in:
stojce 2014-09-25 23:57:19 +02:00
commit 6a3bc8f255
38 changed files with 871 additions and 286 deletions

View file

@ -43,7 +43,7 @@ var noFly = true;
var fixedWalkVelocity = true;
//var roomLimits = { xMin: 618, xMax: 635.5, zMin: 528, zMax: 552.5 };
var roomLimits = { xMin: 193.0, xMax: 206.5, zMin: 251.4, zMax: 269.5 };
var roomLimits = { xMin: 100.0, xMax: 206.5, zMin: 251.4, zMax: 269.5 };
function isInRoom(position) {
var BUFFER = 2.0;

19
examples/xbox.js Normal file
View file

@ -0,0 +1,19 @@
//
// xbox.js
// examples
//
// Created by Stephen Birarda on September 23, 2014
//
// Copyright 2014 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
gamepad = Joysticks.joystickWithName("Wireless 360 Controller");
function reportAxisValue(axis, newValue, oldValue) {
print("The value for axis " + axis + " has changed to " + newValue + ". It was " + oldValue);
}
gamepad.axisValueChanged.connect(reportAxisValue);

View file

@ -2,7 +2,7 @@ set(TARGET_NAME interface)
project(${TARGET_NAME})
# set a default root dir for each of our optional externals if it was not passed
set(OPTIONAL_EXTERNALS "Faceplus" "Faceshift" "LibOVR" "PrioVR" "Sixense" "Visage" "LeapMotion" "RtMidi" "Qxmpp")
set(OPTIONAL_EXTERNALS "Faceplus" "Faceshift" "LibOVR" "PrioVR" "Sixense" "Visage" "LeapMotion" "RtMidi" "Qxmpp" "SDL")
foreach(EXTERNAL ${OPTIONAL_EXTERNALS})
string(TOUPPER ${EXTERNAL} ${EXTERNAL}_UPPERCASE)
if (NOT ${${EXTERNAL}_UPPERCASE}_ROOT_DIR)
@ -120,6 +120,10 @@ foreach(EXTERNAL ${OPTIONAL_EXTERNALS})
add_definitions(-DHAVE_${${EXTERNAL}_UPPERCASE})
# include the library directories (ignoring warnings)
if (NOT ${${EXTERNAL}_UPPERCASE}_INCLUDE_DIRS)
set(${${EXTERNAL}_UPPERCASE}_INCLUDE_DIRS ${${${EXTERNAL}_UPPERCASE}_INCLUDE_DIR})
endif ()
include_directories(SYSTEM ${${${EXTERNAL}_UPPERCASE}_INCLUDE_DIRS})
# perform the system include hack for OS X to ignore warnings
@ -129,6 +133,10 @@ foreach(EXTERNAL ${OPTIONAL_EXTERNALS})
endforeach()
endif ()
if (NOT ${${EXTERNAL}_UPPERCASE}_LIBRARIES)
set(${${EXTERNAL}_UPPERCASE}_LIBRARIES ${${${EXTERNAL}_UPPERCASE}_LIBRARY})
endif ()
target_link_libraries(${TARGET_NAME} ${${${EXTERNAL}_UPPERCASE}_LIBRARIES})
endif ()

View file

@ -82,6 +82,7 @@
#include "scripting/AccountScriptingInterface.h"
#include "scripting/AudioDeviceScriptingInterface.h"
#include "scripting/ClipboardScriptingInterface.h"
#include "scripting/JoystickScriptingInterface.h"
#include "scripting/GlobalServicesScriptingInterface.h"
#include "scripting/LocationScriptingInterface.h"
#include "scripting/MenuScriptingInterface.h"
@ -1821,6 +1822,8 @@ void Application::init() {
_entities.init();
_entities.setViewFrustum(getViewFrustum());
_entityCollisionSystem.init(&_entityEditSender, _entities.getTree(), _voxels.getTree(), &_audio, &_avatarManager);
_entityClipboardRenderer.init();
_entityClipboardRenderer.setViewFrustum(getViewFrustum());
_entityClipboardRenderer.setTree(&_entityClipboard);
@ -1886,13 +1889,12 @@ void Application::shrinkMirrorView() {
}
}
const float HEAD_SPHERE_RADIUS = 0.07f;
const float HEAD_SPHERE_RADIUS = 0.1f;
bool Application::isLookingAtMyAvatar(Avatar* avatar) {
glm::vec3 theirLookat = avatar->getHead()->getLookAtPosition();
glm::vec3 myHeadPosition = _myAvatar->getHead()->getPosition();
if (pointInSphere(theirLookat, myHeadPosition, HEAD_SPHERE_RADIUS * _myAvatar->getScale())) {
glm::vec3 theirLookAt = avatar->getHead()->getLookAtPosition();
glm::vec3 myEyePosition = _myAvatar->getHead()->getEyePosition();
if (pointInSphere(theirLookAt, myEyePosition, HEAD_SPHERE_RADIUS * _myAvatar->getScale())) {
return true;
}
return false;
@ -1998,21 +2000,23 @@ void Application::updateMyAvatarLookAtPosition() {
lookAtSpot = _myCamera.getPosition();
} else {
if (_myAvatar->getLookAtTargetAvatar() && _myAvatar != _myAvatar->getLookAtTargetAvatar()) {
AvatarSharedPointer lookingAt = _myAvatar->getLookAtTargetAvatar().toStrongRef();
if (lookingAt && _myAvatar != lookingAt.data()) {
isLookingAtSomeone = true;
// If I am looking at someone else, look directly at one of their eyes
if (tracker) {
// If tracker active, look at the eye for the side my gaze is biased toward
if (tracker->getEstimatedEyeYaw() > _myAvatar->getHead()->getFinalYaw()) {
// Look at their right eye
lookAtSpot = static_cast<Avatar*>(_myAvatar->getLookAtTargetAvatar())->getHead()->getRightEyePosition();
lookAtSpot = static_cast<Avatar*>(lookingAt.data())->getHead()->getRightEyePosition();
} else {
// Look at their left eye
lookAtSpot = static_cast<Avatar*>(_myAvatar->getLookAtTargetAvatar())->getHead()->getLeftEyePosition();
lookAtSpot = static_cast<Avatar*>(lookingAt.data())->getHead()->getLeftEyePosition();
}
} else {
// Need to add randomly looking back and forth between left and right eye for case with no tracker
lookAtSpot = static_cast<Avatar*>(_myAvatar->getLookAtTargetAvatar())->getHead()->getEyePosition();
lookAtSpot = static_cast<Avatar*>(lookingAt.data())->getHead()->getEyePosition();
}
} else {
// I am not looking at anyone else, so just look forward
@ -2159,7 +2163,7 @@ void Application::update(float deltaTime) {
updateFaceshift();
updateVisage();
_sixenseManager.update(deltaTime);
_joystickManager.update();
JoystickScriptingInterface::getInstance().update();
_prioVR.update(deltaTime);
}
@ -2188,6 +2192,10 @@ void Application::update(float deltaTime) {
{
PerformanceTimer perfTimer("entities");
_entities.update(); // update the models...
{
PerformanceTimer perfTimer("collisions");
_entityCollisionSystem.update(); // collide the entities...
}
}
{
@ -3769,6 +3777,14 @@ void Application::saveScripts() {
_settings->endArray();
}
QScriptValue joystickToScriptValue(QScriptEngine *engine, Joystick* const &in) {
return engine->newQObject(in);
}
void joystickFromScriptValue(const QScriptValue &object, Joystick* &out) {
out = qobject_cast<Joystick*>(object.toQObject());
}
ScriptEngine* Application::loadScript(const QString& scriptFilename, bool isUserLoaded,
bool loadScriptFromEditor, bool activateMainWindow) {
QUrl scriptUrl(scriptFilename);
@ -3851,6 +3867,9 @@ ScriptEngine* Application::loadScript(const QString& scriptFilename, bool isUser
scriptEngine->registerGlobalObject("GlobalServices", GlobalServicesScriptingInterface::getInstance());
scriptEngine->registerGlobalObject("AvatarManager", &_avatarManager);
scriptEngine->registerGlobalObject("Joysticks", &JoystickScriptingInterface::getInstance());
qScriptRegisterMetaType(scriptEngine, joystickToScriptValue, joystickFromScriptValue);
#ifdef HAVE_RTMIDI
scriptEngine->registerGlobalObject("MIDI", &MIDIManager::getInstance());

View file

@ -31,6 +31,7 @@
#include <QSystemTrayIcon>
#include <EntityEditPacketSender.h>
#include <EntityCollisionSystem.h>
#include <NetworkPacket.h>
#include <NodeList.h>
#include <PacketHeaders.h>
@ -59,7 +60,6 @@
#include "avatar/MyAvatar.h"
#include "devices/Faceplus.h"
#include "devices/Faceshift.h"
#include "devices/JoystickManager.h"
#include "devices/PrioVR.h"
#include "devices/SixenseManager.h"
#include "devices/Visage.h"
@ -221,7 +221,6 @@ public:
FaceTracker* getActiveFaceTracker();
SixenseManager* getSixenseManager() { return &_sixenseManager; }
PrioVR* getPrioVR() { return &_prioVR; }
JoystickManager* getJoystickManager() { return &_joystickManager; }
BandwidthMeter* getBandwidthMeter() { return &_bandwidthMeter; }
QUndoStack* getUndoStack() { return &_undoStack; }
QSystemTrayIcon* getTrayIcon() { return _trayIcon; }
@ -300,6 +299,8 @@ public:
ScriptEngine* getScriptEngine(QString scriptHash) { return _scriptEnginesHash.contains(scriptHash) ? _scriptEnginesHash[scriptHash] : NULL; }
void setCursorVisible(bool visible);
bool isLookingAtMyAvatar(Avatar* avatar);
signals:
@ -414,7 +415,6 @@ private:
void updateCursor(float deltaTime);
Avatar* findLookatTargetAvatar(glm::vec3& eyePosition, QUuid &nodeUUID);
bool isLookingAtMyAvatar(Avatar* avatar);
void renderLookatIndicator(glm::vec3 pointOfInterest);
@ -482,6 +482,7 @@ private:
ParticleCollisionSystem _particleCollisionSystem;
EntityTreeRenderer _entities;
EntityCollisionSystem _entityCollisionSystem;
EntityTreeRenderer _entityClipboardRenderer;
EntityTree _entityClipboard;
@ -511,7 +512,6 @@ private:
SixenseManager _sixenseManager;
PrioVR _prioVR;
JoystickManager _joystickManager;
Camera _myCamera; // My view onto the world
Camera _viewFrustumOffsetCamera; // The camera we use to sometimes show the view frustum from an offset mode

View file

@ -630,7 +630,7 @@ void Audio::handleAudioInput() {
measuredDcOffset += networkAudioSamples[i];
networkAudioSamples[i] -= (int16_t) _dcOffset;
thisSample = fabsf(networkAudioSamples[i]);
if (thisSample >= (32767.0f * CLIPPING_THRESHOLD)) {
if (thisSample >= ((float)MAX_16_BIT_AUDIO_SAMPLE * CLIPPING_THRESHOLD)) {
_timeSinceLastClip = 0.0f;
}
loudness += thisSample;
@ -1375,32 +1375,16 @@ int Audio::addBufferToScope(QByteArray* byteArray, int frameOffset, const int16_
return 0;
}
// Constant multiplier to map sample value to vertical size of scope
float multiplier = (float)MULTIPLIER_SCOPE_HEIGHT / logf(2.0f);
// Used to scale each sample. (logf(sample) + fadeOffset) is same as logf(sample * fade).
float fadeOffset = logf(fade);
// Temporary variable receives sample value
float sample;
// Temporary variable receives mapping of sample value
int16_t value;
QMutexLocker lock(&_guard);
// Short int pointer to mapped samples in byte array
int16_t* destination = (int16_t*) byteArray->data();
for (int i = 0; i < sourceSamplesPerChannel; i++) {
sample = (float)source[i * sourceNumberOfChannels + sourceChannel];
if (sample > 1) {
value = (int16_t)(multiplier * (logf(sample) + fadeOffset));
} else if (sample < -1) {
value = (int16_t)(-multiplier * (logf(-sample) + fadeOffset));
} else {
value = 0;
}
destination[frameOffset] = value;
destination[frameOffset] = sample / (float) MAX_16_BIT_AUDIO_SAMPLE * (float)SCOPE_HEIGHT / 2.0f;
frameOffset = (frameOffset == _samplesPerScope - 1) ? 0 : frameOffset + 1;
}
return frameOffset;

View file

@ -47,6 +47,8 @@
static const int NUM_AUDIO_CHANNELS = 2;
static const int MAX_16_BIT_AUDIO_SAMPLE = 32767;
class QAudioInput;
class QAudioOutput;

View file

@ -158,7 +158,8 @@ Menu::Menu() :
addActionToQMenuAndActionHash(fileMenu, MenuOption::LoadScriptURL,
Qt::CTRL | Qt::SHIFT | Qt::Key_O, appInstance, SLOT(loadScriptURLDialog()));
addActionToQMenuAndActionHash(fileMenu, MenuOption::StopAllScripts, 0, appInstance, SLOT(stopAllScripts()));
addActionToQMenuAndActionHash(fileMenu, MenuOption::ReloadAllScripts, 0, appInstance, SLOT(reloadAllScripts()));
addActionToQMenuAndActionHash(fileMenu, MenuOption::ReloadAllScripts, Qt::CTRL | Qt::SHIFT | Qt::Key_R,
appInstance, SLOT(reloadAllScripts()));
addActionToQMenuAndActionHash(fileMenu, MenuOption::RunningScripts, Qt::CTRL | Qt::Key_J,
appInstance, SLOT(toggleRunningScriptsWidget()));

View file

@ -64,7 +64,7 @@ void FaceModel::maybeUpdateEyeRotation(Model* model, const JointState& parentSta
glm::translate(state.getDefaultTranslationInConstrainedFrame()) *
joint.preTransform * glm::mat4_cast(joint.preRotation * joint.rotation));
glm::vec3 front = glm::vec3(inverse * glm::vec4(_owningHead->getFinalOrientationInWorldFrame() * IDENTITY_FRONT, 0.0f));
glm::vec3 lookAt = glm::vec3(inverse * glm::vec4(_owningHead->getLookAtPosition() +
glm::vec3 lookAt = glm::vec3(inverse * glm::vec4(_owningHead->getCorrectedLookAtPosition() +
_owningHead->getSaccade() - model->getTranslation(), 1.0f));
glm::quat between = rotationBetween(front, lookAt);
const float MAX_ANGLE = 30.0f * RADIANS_PER_DEGREE;

View file

@ -34,6 +34,7 @@ Head::Head(Avatar* owningAvatar) :
_lastLoudness(0.0f),
_longTermAverageLoudness(-1.0f),
_audioAttack(0.0f),
_audioJawOpen(0.0f),
_angularVelocity(0,0,0),
_renderLookatVectors(false),
_saccade(0.0f, 0.0f, 0.0f),
@ -47,6 +48,7 @@ Head::Head(Avatar* owningAvatar) :
_deltaLeanSideways(0.f),
_deltaLeanForward(0.f),
_isCameraMoving(false),
_isLookingAtMe(false),
_faceModel(this)
{
@ -156,11 +158,21 @@ void Head::simulate(float deltaTime, bool isMine, bool billboard) {
}
// use data to update fake Faceshift blendshape coefficients
const float JAW_OPEN_SCALE = 10.f;
const float JAW_OPEN_SCALE = 0.015f;
const float JAW_OPEN_RATE = 0.9f;
const float JAW_CLOSE_RATE = 0.90f;
float audioDelta = sqrtf(glm::max(_averageLoudness - _longTermAverageLoudness, 0.0f)) * JAW_OPEN_SCALE;
if (audioDelta > _audioJawOpen) {
_audioJawOpen += (audioDelta - _audioJawOpen) * JAW_OPEN_RATE;
} else {
_audioJawOpen *= JAW_CLOSE_RATE;
}
_audioJawOpen = glm::clamp(_audioJawOpen, 0.0f, 1.0f);
Application::getInstance()->getFaceshift()->updateFakeCoefficients(_leftEyeBlink,
_rightEyeBlink,
_browAudioLift,
glm::clamp(log(_averageLoudness) / JAW_OPEN_SCALE, 0.0f, 1.0f),
_audioJawOpen,
_blendshapeCoefficients);
}
@ -199,7 +211,7 @@ void Head::render(float alpha, Model::RenderMode mode) {
}
void Head::renderPostLighting() {
renderLookatVectors(_leftEyePosition, _rightEyePosition, _lookAtPosition);
renderLookatVectors(_leftEyePosition, _rightEyePosition, getCorrectedLookAtPosition());
}
void Head::setScale (float scale) {
@ -217,6 +229,19 @@ glm::quat Head::getFinalOrientationInLocalFrame() const {
return glm::quat(glm::radians(glm::vec3(getFinalPitch(), getFinalYaw(), getFinalRoll() )));
}
glm::vec3 Head::getCorrectedLookAtPosition() {
if (_isLookingAtMe) {
return _correctedLookAtPosition;
} else {
return getLookAtPosition();
}
}
void Head::setCorrectedLookAtPosition(glm::vec3 correctedLookAtPosition) {
_isLookingAtMe = true;
_correctedLookAtPosition = correctedLookAtPosition;
}
glm::quat Head::getCameraOrientation () const {
if (OculusManager::isConnected()) {
return getOrientation();

View file

@ -63,6 +63,11 @@ public:
const glm::vec3& getAngularVelocity() const { return _angularVelocity; }
void setAngularVelocity(glm::vec3 angularVelocity) { _angularVelocity = angularVelocity; }
void setCorrectedLookAtPosition(glm::vec3 correctedLookAtPosition);
glm::vec3 getCorrectedLookAtPosition();
void clearCorrectedLookAtPosition() { _isLookingAtMe = false; }
bool getIsLookingAtMe() { return _isLookingAtMe; }
float getScale() const { return _scale; }
glm::vec3 getPosition() const { return _position; }
const glm::vec3& getEyePosition() const { return _eyePosition; }
@ -125,6 +130,7 @@ private:
float _lastLoudness;
float _longTermAverageLoudness;
float _audioAttack;
float _audioJawOpen;
glm::vec3 _angularVelocity;
bool _renderLookatVectors;
glm::vec3 _saccade;
@ -143,8 +149,11 @@ private:
float _deltaLeanForward;
bool _isCameraMoving;
bool _isLookingAtMe;
FaceModel _faceModel;
glm::vec3 _correctedLookAtPosition;
// private methods
void renderLookatVectors(glm::vec3 leftEyePosition, glm::vec3 rightEyePosition, glm::vec3 lookatPosition);

View file

@ -951,20 +951,37 @@ void MyAvatar::updateLookAtTargetAvatar() {
//
_lookAtTargetAvatar.clear();
_targetAvatarPosition = glm::vec3(0.0f);
const float MIN_LOOKAT_ANGLE = PI / 4.0f; // Smallest angle between face and person where we will look at someone
float smallestAngleTo = MIN_LOOKAT_ANGLE;
glm::quat faceRotation = Application::getInstance()->getViewFrustum()->getOrientation();
FaceTracker* tracker = Application::getInstance()->getActiveFaceTracker();
if (tracker) {
// If faceshift or other face tracker in use, add on the actual angle of the head
faceRotation *= tracker->getHeadRotation();
}
glm::vec3 lookForward = faceRotation * IDENTITY_FRONT;
glm::vec3 cameraPosition = Application::getInstance()->getCamera()->getPosition();
float smallestAngleTo = glm::radians(Application::getInstance()->getCamera()->getFieldOfView()) / 2.f;
int howManyLookingAtMe = 0;
foreach (const AvatarSharedPointer& avatarPointer, Application::getInstance()->getAvatarManager().getAvatarHash()) {
Avatar* avatar = static_cast<Avatar*>(avatarPointer.data());
avatar->setIsLookAtTarget(false);
if (!avatar->isMyAvatar()) {
glm::vec3 DEFAULT_GAZE_IN_HEAD_FRAME = glm::vec3(0.0f, 0.0f, -1.0f);
float angleTo = glm::angle(getHead()->getFinalOrientationInWorldFrame() * DEFAULT_GAZE_IN_HEAD_FRAME,
glm::normalize(avatar->getHead()->getEyePosition() - getHead()->getEyePosition()));
if (!avatar->isMyAvatar() && avatar->isInitialized()) {
float angleTo = glm::angle(lookForward, glm::normalize(avatar->getHead()->getEyePosition() - cameraPosition));
if (angleTo < smallestAngleTo) {
_lookAtTargetAvatar = avatarPointer;
_targetAvatarPosition = avatarPointer->getPosition();
smallestAngleTo = angleTo;
}
// Check if this avatar is looking at me, and fix their gaze on my camera if so
if (Application::getInstance()->isLookingAtMyAvatar(avatar)) {
howManyLookingAtMe++;
// Have that avatar look directly at my camera
// Philip TODO: correct to look at left/right eye
avatar->getHead()->setCorrectedLookAtPosition(Application::getInstance()->getViewFrustum()->getPosition());
} else {
avatar->getHead()->clearCorrectedLookAtPosition();
}
}
}
if (_lookAtTargetAvatar) {

View file

@ -119,7 +119,7 @@ public:
Q_INVOKABLE glm::vec3 getEyePosition() const { return getHead()->getEyePosition(); }
Q_INVOKABLE glm::vec3 getTargetAvatarPosition() const { return _targetAvatarPosition; }
AvatarData* getLookAtTargetAvatar() const { return _lookAtTargetAvatar.data(); }
QWeakPointer<AvatarData> getLookAtTargetAvatar() const { return _lookAtTargetAvatar; }
void updateLookAtTargetAvatar();
void clearLookAtTargetAvatar();

View file

@ -0,0 +1,60 @@
//
// Joystick.cpp
// interface/src/devices
//
// Created by Stephen Birarda on 2014-09-23.
// 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 <limits>
#include <glm/glm.hpp>
#include "Joystick.h"
#ifdef HAVE_SDL
Joystick::Joystick(const QString& name, SDL_Joystick* sdlJoystick) :
_name(name),
_axes(QVector<float>(SDL_JoystickNumAxes(sdlJoystick))),
_buttons(QVector<bool>(SDL_JoystickNumButtons(sdlJoystick))),
_sdlJoystick(sdlJoystick)
{
}
#endif
Joystick::~Joystick() {
#ifdef HAVE_SDL
SDL_JoystickClose(_sdlJoystick);
#endif
}
void Joystick::update() {
#ifdef HAVE_SDL
// update our current values, emit a signal when there is a change
for (int j = 0; j < getNumAxes(); j++) {
float value = glm::round(SDL_JoystickGetAxis(_sdlJoystick, j) + 0.5f) / std::numeric_limits<short>::max();
const float DEAD_ZONE = 0.1f;
float cleanValue = glm::abs(value) < DEAD_ZONE ? 0.0f : value;
if (_axes[j] != cleanValue) {
float oldValue = _axes[j];
_axes[j] = cleanValue;
emit axisValueChanged(j, cleanValue, oldValue);
}
}
for (int j = 0; j < getNumButtons(); j++) {
bool newValue = SDL_JoystickGetButton(_sdlJoystick, j);
if (_buttons[j] != newValue) {
bool oldValue = _buttons[j];
_buttons[j] = newValue;
emit buttonStateChanged(j, newValue, oldValue);
}
}
#endif
}

View file

@ -0,0 +1,61 @@
//
// Joystick.h
// interface/src/devices
//
// Created by Stephen Birarda on 2014-09-23.
// 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_Joystick_h
#define hifi_Joystick_h
#include <qobject.h>
#include <qvector.h>
#ifdef HAVE_SDL
#include <SDL.h>
#undef main
#endif
class Joystick : public QObject {
Q_OBJECT
Q_PROPERTY(QString name READ getName)
Q_PROPERTY(int numAxes READ getNumAxes)
Q_PROPERTY(int numButtons READ getNumButtons)
public:
Joystick();
~Joystick();
#ifdef HAVE_SDL
Joystick(const QString& name, SDL_Joystick* sdlJoystick);
#endif
void update();
const QString& getName() const { return _name; }
const QVector<float>& getAxes() const { return _axes; }
const QVector<bool>& getButtons() const { return _buttons; }
int getNumAxes() const { return _axes.size(); }
int getNumButtons() const { return _buttons.size(); }
signals:
void axisValueChanged(int axis, float newValue, float oldValue);
void buttonStateChanged(int button, float newValue, float oldValue);
private:
QString _name;
QVector<float> _axes;
QVector<bool> _buttons;
#ifdef HAVE_SDL
SDL_Joystick* _sdlJoystick;
#endif
};
#endif // hifi_JoystickTracker_h

View file

@ -1,66 +0,0 @@
//
// JoystickManager.cpp
// interface/src/devices
//
// Created by Andrzej Kapolka on 5/15/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 <limits>
#include <QtDebug>
#include <glm/glm.hpp>
#include <PerfStat.h>
#include "JoystickManager.h"
using namespace std;
JoystickManager::JoystickManager() {
#ifdef HAVE_SDL
SDL_Init(SDL_INIT_JOYSTICK);
int joystickCount = SDL_NumJoysticks();
for (int i = 0; i < joystickCount; i++) {
SDL_Joystick* joystick = SDL_JoystickOpen(i);
if (joystick) {
JoystickState state = { SDL_JoystickName(i), QVector<float>(SDL_JoystickNumAxes(joystick)),
QVector<bool>(SDL_JoystickNumButtons(joystick)) };
_joystickStates.append(state);
_joysticks.append(joystick);
}
}
#endif
}
JoystickManager::~JoystickManager() {
#ifdef HAVE_SDL
foreach (SDL_Joystick* joystick, _joysticks) {
SDL_JoystickClose(joystick);
}
SDL_Quit();
#endif
}
void JoystickManager::update() {
#ifdef HAVE_SDL
PerformanceTimer perfTimer("joystick");
SDL_JoystickUpdate();
for (int i = 0; i < _joystickStates.size(); i++) {
SDL_Joystick* joystick = _joysticks.at(i);
JoystickState& state = _joystickStates[i];
for (int j = 0; j < state.axes.size(); j++) {
float value = glm::round(SDL_JoystickGetAxis(joystick, j) + 0.5f) / numeric_limits<short>::max();
const float DEAD_ZONE = 0.1f;
state.axes[j] = glm::abs(value) < DEAD_ZONE ? 0.0f : value;
}
for (int j = 0; j < state.buttons.size(); j++) {
state.buttons[j] = SDL_JoystickGetButton(joystick, j);
}
}
#endif
}

View file

@ -1,53 +0,0 @@
//
// JoystickManager.h
// interface/src/devices
//
// Created by Andrzej Kapolka on 5/15/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_JoystickManager_h
#define hifi_JoystickManager_h
#include <QObject>
#include <QVector>
#ifdef HAVE_SDL
#include <SDL.h>
#undef main
#endif
class JoystickState;
/// Handles joystick input through SDL.
class JoystickManager : public QObject {
Q_OBJECT
public:
JoystickManager();
virtual ~JoystickManager();
const QVector<JoystickState>& getJoystickStates() const { return _joystickStates; }
void update();
private:
QVector<JoystickState> _joystickStates;
#ifdef HAVE_SDL
QVector<SDL_Joystick*> _joysticks;
#endif
};
class JoystickState {
public:
QString name;
QVector<float> axes;
QVector<bool> buttons;
};
#endif // hifi_JoystickManager_h

View file

@ -17,6 +17,7 @@
#include "Application.h"
#include "PrioVR.h"
#include "scripting/JoystickScriptingInterface.h"
#include "ui/TextRenderer.h"
#ifdef HAVE_PRIOVR
@ -61,18 +62,22 @@ static void setPalm(float deltaTime, int index) {
palm->setActive(true);
// Read controller buttons and joystick into the hand
if (!Application::getInstance()->getJoystickManager()->getJoystickStates().isEmpty()) {
const JoystickState& state = Application::getInstance()->getJoystickManager()->getJoystickStates().at(0);
if (state.axes.size() >= 4 && state.buttons.size() >= 4) {
const QString PRIO_JOYSTICK_NAME = "PrioVR";
Joystick* prioJoystick = JoystickScriptingInterface::getInstance().joystickWithName(PRIO_JOYSTICK_NAME);
if (prioJoystick) {
const QVector<float> axes = prioJoystick->getAxes();
const QVector<bool> buttons = prioJoystick->getButtons();
if (axes.size() >= 4 && buttons.size() >= 4) {
if (index == LEFT_HAND_INDEX) {
palm->setControllerButtons(state.buttons.at(1) ? BUTTON_FWD : 0);
palm->setTrigger(state.buttons.at(0) ? 1.0f : 0.0f);
palm->setJoystick(state.axes.at(0), -state.axes.at(1));
palm->setControllerButtons(buttons[1] ? BUTTON_FWD : 0);
palm->setTrigger(buttons[0] ? 1.0f : 0.0f);
palm->setJoystick(axes[0], -axes[1]);
} else {
palm->setControllerButtons(state.buttons.at(3) ? BUTTON_FWD : 0);
palm->setTrigger(state.buttons.at(2) ? 1.0f : 0.0f);
palm->setJoystick(state.axes.at(2), -state.axes.at(3));
palm->setControllerButtons(buttons[3] ? BUTTON_FWD : 0);
palm->setTrigger(buttons[2] ? 1.0f : 0.0f);
palm->setJoystick(axes[2], -axes[3]);
}
}
}

View file

@ -296,7 +296,7 @@ void EntityTreeRenderer::renderElement(OctreeElement* element, RenderArgs* args)
ModelEntityItem* modelEntity = static_cast<ModelEntityItem*>(entityItem);
qDebug() << " url:" << modelEntity->getModelURL();
}
qDebug() << " entityBox:" << entityBox;
qDebug() << " entityBox:" << entityItem->getAABox();
qDebug() << " dimensions:" << entityItem->getDimensionsInMeters() << "in meters";
qDebug() << " largestDimension:" << entityBox.getLargestDimension() << "in meters";
qDebug() << " shouldRender:" << shouldRenderEntity(entityBox.getLargestDimension(), distance);

View file

@ -0,0 +1,84 @@
//
// JoystickScriptingInterface.cpp
// interface/src/devices
//
// Created by Andrzej Kapolka on 5/15/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 <QtDebug>
#ifdef HAVE_SDL
#include <SDL.h>
#undef main
#endif
#include <PerfStat.h>
#include "JoystickScriptingInterface.h"
JoystickScriptingInterface& JoystickScriptingInterface::getInstance() {
static JoystickScriptingInterface sharedInstance;
return sharedInstance;
}
JoystickScriptingInterface::JoystickScriptingInterface() :
_openJoysticks(),
_availableDeviceNames()
{
#ifdef HAVE_SDL
SDL_Init(SDL_INIT_JOYSTICK);
int joystickCount = SDL_NumJoysticks();
for (int i = 0; i < joystickCount; i++) {
_availableDeviceNames << SDL_JoystickName(i);
}
#endif
}
JoystickScriptingInterface::~JoystickScriptingInterface() {
qDeleteAll(_openJoysticks);
#ifdef HAVE_SDL
SDL_Quit();
#endif
}
void JoystickScriptingInterface::update() {
#ifdef HAVE_SDL
PerformanceTimer perfTimer("JoystickScriptingInterface::update");
SDL_JoystickUpdate();
foreach(Joystick* joystick, _openJoysticks) {
joystick->update();
}
#endif
}
Joystick* JoystickScriptingInterface::joystickWithName(const QString& name) {
Joystick* matchingJoystick = _openJoysticks.value(name);
#ifdef HAVE_SDL
if (!matchingJoystick) {
// we haven't opened a joystick with this name yet - enumerate our SDL devices and see if it exists
int joystickCount = SDL_NumJoysticks();
for (int i = 0; i < joystickCount; i++) {
if (SDL_JoystickName(i) == name) {
matchingJoystick = _openJoysticks.insert(name, new Joystick(name, SDL_JoystickOpen(i))).value();
break;
}
}
qDebug() << "No matching joystick found with name" << name << "- returning NULL pointer.";
}
#endif
return matchingJoystick;
}

View file

@ -0,0 +1,43 @@
//
// JoystickScriptingInterface.h
// interface/src/devices
//
// Created by Andrzej Kapolka on 5/15/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_JoystickScriptingInterface_h
#define hifi_JoystickScriptingInterface_h
#include <QObject>
#include <QVector>
#include "devices/Joystick.h"
/// Handles joystick input through SDL.
class JoystickScriptingInterface : public QObject {
Q_OBJECT
Q_PROPERTY(QStringList availableJoystickNames READ getAvailableJoystickNames)
public:
static JoystickScriptingInterface& getInstance();
const QStringList& getAvailableJoystickNames() const { return _availableDeviceNames; }
void update();
public slots:
Joystick* joystickWithName(const QString& name);
private:
JoystickScriptingInterface();
~JoystickScriptingInterface();
QMap<QString, Joystick*> _openJoysticks;
QStringList _availableDeviceNames;
};
#endif // hifi_JoystickScriptingInterface_h

View file

@ -136,6 +136,7 @@ void RunningScriptsWidget::setRunningScripts(const QStringList& list) {
ui->noRunningScriptsLabel->setVisible(list.isEmpty());
ui->runningScriptsList->setVisible(!list.isEmpty());
ui->reloadAllButton->setVisible(!list.isEmpty());
ui->stopAllButton->setVisible(!list.isEmpty());

View file

@ -6,8 +6,8 @@
<rect>
<x>0</x>
<y>0</y>
<width>324</width>
<height>643</height>
<width>319</width>
<height>481</height>
</rect>
</property>
<property name="windowTitle">
@ -29,57 +29,12 @@
<property name="bottomMargin">
<number>20</number>
</property>
<item>
<widget class="QWidget" name="header" native="true">
<layout class="QHBoxLayout" name="horizontalLayout">
<property name="leftMargin">
<number>6</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>6</number>
</property>
<item>
<widget class="QLabel" name="widgetTitle">
<property name="styleSheet">
<string notr="true">color: #0e7077;
font-size: 20px;</string>
</property>
<property name="text">
<string>Running Scripts</string>
</property>
<property name="margin">
<number>0</number>
</property>
<property name="indent">
<number>-1</number>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QWidget" name="runningScriptsArea" native="true">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
<sizepolicy hsizetype="Preferred" vsizetype="Expanding">
<horstretch>0</horstretch>
<verstretch>1</verstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
@ -157,7 +112,7 @@ font: bold 16px;
<property name="sizeHint" stdset="0">
<size>
<width>0</width>
<height>8</height>
<height>4</height>
</size>
</property>
</spacer>
@ -218,6 +173,12 @@ font: bold 16px;
</item>
<item>
<widget class="QWidget" name="widget_4" native="true">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Expanding">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<layout class="QVBoxLayout" name="verticalLayout_8">
<property name="spacing">
<number>0</number>
@ -236,6 +197,12 @@ font: bold 16px;
</property>
<item>
<widget class="QScrollArea" name="runningScriptsList">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="font">
<font>
<family>Helvetica,Arial,sans-serif</family>
@ -252,10 +219,13 @@ font: bold 16px;
<number>0</number>
</property>
<property name="verticalScrollBarPolicy">
<enum>Qt::ScrollBarAsNeeded</enum>
<enum>Qt::ScrollBarAlwaysOn</enum>
</property>
<property name="horizontalScrollBarPolicy">
<enum>Qt::ScrollBarAlwaysOff</enum>
<enum>Qt::ScrollBarAsNeeded</enum>
</property>
<property name="sizeAdjustPolicy">
<enum>QAbstractScrollArea::AdjustToContents</enum>
</property>
<property name="widgetResizable">
<bool>true</bool>
@ -268,7 +238,7 @@ font: bold 16px;
<rect>
<x>0</x>
<y>0</y>
<width>284</width>
<width>264</width>
<height>16</height>
</rect>
</property>
@ -278,6 +248,9 @@ font: bold 16px;
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="layoutDirection">
<enum>Qt::LeftToRight</enum>
</property>
<property name="styleSheet">
<string notr="true">font-size: 14px;</string>
</property>
@ -303,14 +276,20 @@ font: bold 16px;
</item>
<item>
<widget class="QLabel" name="noRunningScriptsLabel">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Minimum">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="styleSheet">
<string notr="true">font: 14px;</string>
<string notr="true">font: 14px; color: #5f5f5f;</string>
</property>
<property name="text">
<string>There are no scripts currently running.</string>
<string>There are no scripts running.</string>
</property>
<property name="alignment">
<set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter</set>
<set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop</set>
</property>
</widget>
</item>
@ -325,15 +304,9 @@ font: bold 16px;
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>2</verstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>0</width>
<height>300</height>
</size>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<property name="spacing">
<number>0</number>
@ -352,6 +325,12 @@ font: bold 16px;
</property>
<item>
<widget class="QWidget" name="widget" native="true">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Maximum">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<layout class="QHBoxLayout" name="horizontalLayout_2">
<property name="spacing">
<number>0</number>
@ -432,6 +411,12 @@ font: bold 16px;</string>
</item>
<item>
<widget class="QListView" name="scriptListView">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Expanding">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="verticalScrollBarPolicy">
<enum>Qt::ScrollBarAlwaysOn</enum>
</property>

View file

@ -21,11 +21,6 @@ AvatarHashMap::AvatarHashMap() :
connect(NodeList::getInstance(), &NodeList::uuidChanged, this, &AvatarHashMap::sessionUUIDChanged);
}
void AvatarHashMap::insert(const QUuid& sessionUUID, AvatarSharedPointer avatar) {
_avatarHash.insert(sessionUUID, avatar);
avatar->setSessionUUID(sessionUUID);
}
AvatarHash::iterator AvatarHashMap::erase(const AvatarHash::iterator& iterator) {
qDebug() << "Removing Avatar with UUID" << iterator.key() << "from AvatarHashMap.";
return _avatarHash.erase(iterator);
@ -95,9 +90,11 @@ AvatarSharedPointer AvatarHashMap::matchingOrNewAvatar(const QUuid& sessionUUID,
matchingAvatar = newSharedAvatar();
qDebug() << "Adding avatar with sessionUUID " << sessionUUID << "to AvatarHashMap.";
_avatarHash.insert(sessionUUID, matchingAvatar);
matchingAvatar->setSessionUUID(sessionUUID);
matchingAvatar->setOwningAvatarMixer(mixerWeakPointer);
_avatarHash.insert(sessionUUID, matchingAvatar);
}
return matchingAvatar;

View file

@ -30,8 +30,6 @@ public:
const AvatarHash& getAvatarHash() { return _avatarHash; }
int size() const { return _avatarHash.size(); }
virtual void insert(const QUuid& sessionUUID, AvatarSharedPointer avatar);
public slots:
void processAvatarMixerDatagram(const QByteArray& datagram, const QWeakPointer<Node>& mixerWeakPointer);

View file

@ -32,7 +32,7 @@
static const int MAGIC_NUMBER_SIZE = 8;
static const char MAGIC_NUMBER[MAGIC_NUMBER_SIZE] = {17, 72, 70, 82, 13, 10, 26, 10};
// Version (Major, Minor)
static const QPair<quint8, quint8> VERSION(0, 1);
static const QPair<quint8, quint8> VERSION(0, 2);
int SCALE_RADIX = 10;
int BLENDSHAPE_RADIX = 15;
@ -118,12 +118,6 @@ bool readQuat(QDataStream& stream, glm::quat& value) {
return true;
}
void writeFloat(QDataStream& stream, float value, int radix) {
unsigned char buffer[256];
int writtenToBuffer = packFloatScalarToSignedTwoByteFixed(buffer, value, radix);
stream.writeRawData(reinterpret_cast<char*>(buffer), writtenToBuffer);
}
bool readFloat(QDataStream& stream, float& value, int radix) {
int floatByteSize = 2; // 1 floats * 2 bytes
int16_t buffer[256];
@ -185,7 +179,7 @@ void writeRecordingToFile(RecordingPointer recording, const QString& filename) {
// Orientation
writeQuat(fileStream, context.orientation);
// Scale
writeFloat(fileStream, context.scale, SCALE_RADIX);
fileStream << context.scale;
// Head model
fileStream << context.headModel;
// Skeleton model
@ -204,7 +198,7 @@ void writeRecordingToFile(RecordingPointer recording, const QString& filename) {
// Orientation
writeQuat(fileStream, data.rotation);
// Scale
writeFloat(fileStream, data.scale, SCALE_RADIX);
fileStream << data.scale;
}
// RECORDING
@ -231,7 +225,7 @@ void writeRecordingToFile(RecordingPointer recording, const QString& filename) {
for (quint32 j = 0; j < numBlendshapes; ++j) {
if (i == 0 ||
frame._blendshapeCoefficients[j] != previousFrame._blendshapeCoefficients[j]) {
writeFloat(stream, frame.getBlendshapeCoefficients()[j], BLENDSHAPE_RADIX);
stream << frame.getBlendshapeCoefficients()[j];
mask.setBit(maskIndex);
}
++maskIndex;
@ -277,7 +271,7 @@ void writeRecordingToFile(RecordingPointer recording, const QString& filename) {
mask.resize(mask.size() + 1);
}
if (i == 0 || frame._scale != previousFrame._scale) {
writeFloat(stream, frame._scale, SCALE_RADIX);
stream << frame._scale;
mask.setBit(maskIndex);
}
maskIndex++;
@ -297,7 +291,7 @@ void writeRecordingToFile(RecordingPointer recording, const QString& filename) {
mask.resize(mask.size() + 1);
}
if (i == 0 || frame._leanSideways != previousFrame._leanSideways) {
writeFloat(stream, frame._leanSideways, LEAN_RADIX);
stream << frame._leanSideways;
mask.setBit(maskIndex);
}
maskIndex++;
@ -307,7 +301,7 @@ void writeRecordingToFile(RecordingPointer recording, const QString& filename) {
mask.resize(mask.size() + 1);
}
if (i == 0 || frame._leanForward != previousFrame._leanForward) {
writeFloat(stream, frame._leanForward, LEAN_RADIX);
stream << frame._leanForward;
mask.setBit(maskIndex);
}
maskIndex++;
@ -438,7 +432,7 @@ RecordingPointer readRecordingFromFile(RecordingPointer recording, const QString
QPair<quint8, quint8> version;
fileStream >> version; // File format version
if (version != VERSION) {
if (version != VERSION && version != QPair<quint8, quint8>(0,1)) {
qDebug() << "ERROR: This file format version is not supported.";
return recording;
}
@ -484,10 +478,10 @@ RecordingPointer readRecordingFromFile(RecordingPointer recording, const QString
}
// Scale
if (!readFloat(fileStream, context.scale, SCALE_RADIX)) {
qDebug() << "Couldn't read file correctly. (Invalid float)";
recording.clear();
return recording;
if (version == QPair<quint8, quint8>(0,1)) {
readFloat(fileStream, context.scale, SCALE_RADIX);
} else {
fileStream >> context.scale;
}
// Head model
fileStream >> context.headModel;
@ -519,9 +513,10 @@ RecordingPointer readRecordingFromFile(RecordingPointer recording, const QString
}
// Scale
if (!readFloat(fileStream, data.scale, SCALE_RADIX)) {
qDebug() << "Couldn't read attachment correctly. (Invalid float)";
continue;
if (version == QPair<quint8, quint8>(0,1)) {
readFloat(fileStream, data.scale, SCALE_RADIX);
} else {
fileStream >> data.scale;
}
context.attachments << data;
}
@ -548,8 +543,12 @@ RecordingPointer readRecordingFromFile(RecordingPointer recording, const QString
}
frame._blendshapeCoefficients.resize(numBlendshapes);
for (quint32 j = 0; j < numBlendshapes; ++j) {
if (!mask[maskIndex++] || !readFloat(stream, frame._blendshapeCoefficients[j], BLENDSHAPE_RADIX)) {
if (!mask[maskIndex++]) {
frame._blendshapeCoefficients[j] = previousFrame._blendshapeCoefficients[j];
} else if (version == QPair<quint8, quint8>(0,1)) {
readFloat(stream, frame._blendshapeCoefficients[j], BLENDSHAPE_RADIX);
} else {
stream >> frame._blendshapeCoefficients[j];
}
}
// Joint Rotations
@ -571,20 +570,32 @@ RecordingPointer readRecordingFromFile(RecordingPointer recording, const QString
frame._rotation = previousFrame._rotation;
}
if (!mask[maskIndex++] || !readFloat(stream, frame._scale, SCALE_RADIX)) {
if (!mask[maskIndex++]) {
frame._scale = previousFrame._scale;
} else if (version == QPair<quint8, quint8>(0,1)) {
readFloat(stream, frame._scale, SCALE_RADIX);
} else {
stream >> frame._scale;
}
if (!mask[maskIndex++] || !readQuat(stream, frame._headRotation)) {
frame._headRotation = previousFrame._headRotation;
}
if (!mask[maskIndex++] || !readFloat(stream, frame._leanSideways, LEAN_RADIX)) {
if (!mask[maskIndex++]) {
frame._leanSideways = previousFrame._leanSideways;
} else if (version == QPair<quint8, quint8>(0,1)) {
readFloat(stream, frame._leanSideways, LEAN_RADIX);
} else {
stream >> frame._leanSideways;
}
if (!mask[maskIndex++] || !readFloat(stream, frame._leanForward, LEAN_RADIX)) {
if (!mask[maskIndex++]) {
frame._leanForward = previousFrame._leanForward;
} else if (version == QPair<quint8, quint8>(0,1)) {
readFloat(stream, frame._leanForward, LEAN_RADIX);
} else {
stream >> frame._leanForward;
}
if (!mask[maskIndex++] || !readVec3(stream, frame._lookAtPosition)) {

View file

@ -0,0 +1,259 @@
//
// EntityCollisionSystem.cpp
// libraries/entities/src
//
// Created by Brad Hefta-Gaub on 9/23/14.
// Copyright 2013-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 <algorithm>
#include <AbstractAudioInterface.h>
#include <VoxelTree.h>
#include <AvatarData.h>
#include <HeadData.h>
#include <HandData.h>
#include "EntityItem.h"
#include "EntityCollisionSystem.h"
#include "EntityEditPacketSender.h"
#include "EntityTree.h"
#include "EntityTreeElement.h"
const int MAX_COLLISIONS_PER_Entity = 16;
EntityCollisionSystem::EntityCollisionSystem(EntityEditPacketSender* packetSender,
EntityTree* Entities, VoxelTree* voxels, AbstractAudioInterface* audio,
AvatarHashMap* avatars) : _collisions(MAX_COLLISIONS_PER_Entity) {
init(packetSender, Entities, voxels, audio, avatars);
}
void EntityCollisionSystem::init(EntityEditPacketSender* packetSender,
EntityTree* Entities, VoxelTree* voxels, AbstractAudioInterface* audio,
AvatarHashMap* avatars) {
_packetSender = packetSender;
_entities = Entities;
_voxels = voxels;
_audio = audio;
_avatars = avatars;
}
EntityCollisionSystem::~EntityCollisionSystem() {
}
void EntityCollisionSystem::update() {
// update all Entities
if (_entities->tryLockForRead()) {
QList<EntityItem*>& movingEntities = _entities->getMovingEntities();
foreach (EntityItem* entity, movingEntities) {
checkEntity(entity);
}
_entities->unlock();
}
}
void EntityCollisionSystem::checkEntity(EntityItem* entity) {
updateCollisionWithVoxels(entity);
updateCollisionWithEntities(entity);
updateCollisionWithAvatars(entity);
}
void EntityCollisionSystem::emitGlobalEntityCollisionWithVoxel(EntityItem* entity,
VoxelDetail* voxelDetails, const CollisionInfo& collision) {
EntityItemID entityItemID = entity->getEntityItemID();
emit EntityCollisionWithVoxel(entityItemID, *voxelDetails, collision);
}
void EntityCollisionSystem::emitGlobalEntityCollisionWithEntity(EntityItem* entityA,
EntityItem* entityB, const CollisionInfo& collision) {
EntityItemID idA = entityA->getEntityItemID();
EntityItemID idB = entityB->getEntityItemID();
emit EntityCollisionWithEntity(idA, idB, collision);
}
void EntityCollisionSystem::updateCollisionWithVoxels(EntityItem* entity) {
glm::vec3 center = entity->getPosition() * (float)(TREE_SCALE);
float radius = entity->getRadius() * (float)(TREE_SCALE);
const float ELASTICITY = 0.4f;
const float DAMPING = 0.05f;
CollisionInfo collisionInfo;
collisionInfo._damping = DAMPING;
collisionInfo._elasticity = ELASTICITY;
VoxelDetail* voxelDetails = NULL;
if (_voxels->findSpherePenetration(center, radius, collisionInfo._penetration, (void**)&voxelDetails)) {
// findSpherePenetration() only computes the penetration but we also want some other collision info
// so we compute it ourselves here. Note that we must multiply scale by TREE_SCALE when feeding
// the results to systems outside of this octree reference frame.
collisionInfo._contactPoint = (float)TREE_SCALE * (entity->getPosition() + entity->getRadius() * glm::normalize(collisionInfo._penetration));
// let the global script run their collision scripts for Entities if they have them
emitGlobalEntityCollisionWithVoxel(entity, voxelDetails, collisionInfo);
// we must scale back down to the octree reference frame before updating the Entity properties
collisionInfo._penetration /= (float)(TREE_SCALE);
collisionInfo._contactPoint /= (float)(TREE_SCALE);
applyHardCollision(entity, collisionInfo);
delete voxelDetails; // cleanup returned details
}
}
void EntityCollisionSystem::updateCollisionWithEntities(EntityItem* entityA) {
glm::vec3 center = entityA->getPosition() * (float)(TREE_SCALE);
float radius = entityA->getRadius() * (float)(TREE_SCALE);
glm::vec3 penetration;
EntityItem* entityB;
if (_entities->findSpherePenetration(center, radius, penetration, (void**)&entityB, Octree::NoLock)) {
// NOTE: 'penetration' is the depth that 'entityA' overlaps 'entityB'. It points from A into B.
glm::vec3 penetrationInTreeUnits = penetration / (float)(TREE_SCALE);
// Even if the Entities overlap... when the Entities are already moving appart
// we don't want to count this as a collision.
glm::vec3 relativeVelocity = entityA->getVelocity() - entityB->getVelocity();
bool movingTowardEachOther = glm::dot(relativeVelocity, penetrationInTreeUnits) > 0.0f;
bool doCollisions = movingTowardEachOther; // don't do collisions if the entities are moving away from each other
if (doCollisions) {
quint64 now = usecTimestampNow();
CollisionInfo collision;
collision._penetration = penetration;
// for now the contactPoint is the average between the the two paricle centers
collision._contactPoint = (0.5f * (float)TREE_SCALE) * (entityA->getPosition() + entityB->getPosition());
emitGlobalEntityCollisionWithEntity(entityA, entityB, collision);
glm::vec3 axis = glm::normalize(penetration);
glm::vec3 axialVelocity = glm::dot(relativeVelocity, axis) * axis;
float massA = entityA->getMass();
float massB = entityB->getMass();
float totalMass = massA + massB;
// handle Entity A
glm::vec3 newVelocityA = entityA->getVelocity() - axialVelocity * (2.0f * massB / totalMass);
glm::vec3 newPositionA = entityA->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);
glm::vec3 newVelocityB = entityB->getVelocity() + axialVelocity * (2.0f * massA / totalMass);
glm::vec3 newPositionB = entityB->getPosition() + 0.5f * penetrationInTreeUnits;
EntityItemProperties propertiesB = entityB->getProperties();
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);
// TODO: Do we need this?
//_packetSender->releaseQueuedMessages();
}
}
}
void EntityCollisionSystem::updateCollisionWithAvatars(EntityItem* entity) {
// Entities that are in hand, don't collide with avatars
if (!_avatars) {
return;
}
glm::vec3 center = entity->getPosition() * (float)(TREE_SCALE);
float radius = entity->getRadius() * (float)(TREE_SCALE);
const float ELASTICITY = 0.9f;
const float DAMPING = 0.1f;
glm::vec3 penetration;
_collisions.clear();
foreach (const AvatarSharedPointer& avatarPointer, _avatars->getAvatarHash()) {
AvatarData* avatar = avatarPointer.data();
float totalRadius = avatar->getBoundingRadius() + radius;
glm::vec3 relativePosition = center - avatar->getPosition();
if (glm::dot(relativePosition, relativePosition) > (totalRadius * totalRadius)) {
continue;
}
if (avatar->findSphereCollisions(center, radius, _collisions)) {
int numCollisions = _collisions.size();
for (int i = 0; i < numCollisions; ++i) {
CollisionInfo* collision = _collisions.getCollision(i);
collision->_damping = DAMPING;
collision->_elasticity = ELASTICITY;
collision->_addedVelocity /= (float)(TREE_SCALE);
glm::vec3 relativeVelocity = collision->_addedVelocity - entity->getVelocity();
if (glm::dot(relativeVelocity, collision->_penetration) <= 0.f) {
// only collide when Entity and collision point are moving toward each other
// (doing this prevents some "collision snagging" when Entity penetrates the object)
collision->_penetration /= (float)(TREE_SCALE);
applyHardCollision(entity, *collision);
}
}
}
}
}
void EntityCollisionSystem::applyHardCollision(EntityItem* entity, const CollisionInfo& collisionInfo) {
// HALTING_* params are determined using expected acceleration of gravity over some timescale.
// This is a HACK for entities that bounce in a 1.0 gravitational field and should eventually be made more universal.
const float HALTING_ENTITY_PERIOD = 0.0167f; // ~1/60th of a second
const float HALTING_ENTITY_SPEED = 9.8 * HALTING_ENTITY_PERIOD / (float)(TREE_SCALE);
//
// Update the entity in response to a hard collision. Position will be reset exactly
// to outside the colliding surface. Velocity will be modified according to elasticity.
//
// if elasticity = 0.0, collision is inelastic (vel normal to collision is lost)
// if elasticity = 1.0, collision is 100% elastic.
//
glm::vec3 position = entity->getPosition();
glm::vec3 velocity = entity->getVelocity();
const float EPSILON = 0.0f;
glm::vec3 relativeVelocity = collisionInfo._addedVelocity - velocity;
float velocityDotPenetration = glm::dot(relativeVelocity, collisionInfo._penetration);
if (velocityDotPenetration < EPSILON) {
// entity is moving into collision surface
//
// TODO: do something smarter here by comparing the mass of the entity vs that of the other thing
// (other's mass could be stored in the Collision Info). The smaller mass should surrender more
// position offset and should slave more to the other's velocity in the static-friction case.
position -= collisionInfo._penetration;
if (glm::length(relativeVelocity) < HALTING_ENTITY_SPEED) {
// static friction kicks in and entities moves with colliding object
velocity = collisionInfo._addedVelocity;
} else {
glm::vec3 direction = glm::normalize(collisionInfo._penetration);
velocity += glm::dot(relativeVelocity, direction) * (1.0f + collisionInfo._elasticity) * direction; // dynamic reflection
velocity += glm::clamp(collisionInfo._damping, 0.0f, 1.0f) * (relativeVelocity - glm::dot(relativeVelocity, direction) * direction); // dynamic friction
}
}
EntityItemProperties properties = entity->getProperties();
EntityItemID entityItemID(entity->getID());
properties.setPosition(position * (float)TREE_SCALE);
properties.setVelocity(velocity * (float)TREE_SCALE);
properties.setLastEdited(usecTimestampNow());
_entities->updateEntity(entityItemID, properties);
_packetSender->queueEditEntityMessage(PacketTypeEntityAddOrEdit, entityItemID, properties);
}

View file

@ -0,0 +1,75 @@
//
// EntityCollisionSystem.h
// libraries/entities/src
//
// Created by Brad Hefta-Gaub on 9/23/14.
// Copyright 2013-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_EntityCollisionSystem_h
#define hifi_EntityCollisionSystem_h
#include <glm/glm.hpp>
#include <stdint.h>
#include <QtScript/QScriptEngine>
#include <QtCore/QObject>
#include <AvatarHashMap.h>
#include <CollisionInfo.h>
#include <SharedUtil.h>
#include <OctreePacketData.h>
#include <VoxelDetail.h>
#include "EntityItem.h"
class AbstractAudioInterface;
class AvatarData;
class EntityEditPacketSender;
class EntityTree;
class VoxelTree;
class EntityCollisionSystem : public QObject {
Q_OBJECT
public:
EntityCollisionSystem(EntityEditPacketSender* packetSender = NULL, EntityTree* Entitys = NULL,
VoxelTree* voxels = NULL, AbstractAudioInterface* audio = NULL,
AvatarHashMap* avatars = NULL);
void init(EntityEditPacketSender* packetSender, EntityTree* Entitys, VoxelTree* voxels,
AbstractAudioInterface* audio = NULL, AvatarHashMap* _avatars = NULL);
~EntityCollisionSystem();
void update();
void checkEntity(EntityItem* Entity);
void updateCollisionWithVoxels(EntityItem* Entity);
void updateCollisionWithEntities(EntityItem* Entity);
void updateCollisionWithAvatars(EntityItem* Entity);
void queueEntityPropertiesUpdate(EntityItem* Entity);
void updateCollisionSound(EntityItem* Entity, const glm::vec3 &penetration, float frequency);
signals:
void EntityCollisionWithVoxel(const EntityItemID& entityItemID, const VoxelDetail& voxel, const CollisionInfo& penetration);
void EntityCollisionWithEntity(const EntityItemID& idA, const EntityItemID& idB, const CollisionInfo& penetration);
private:
void applyHardCollision(EntityItem* entity, const CollisionInfo& collisionInfo);
static bool updateOperation(OctreeElement* element, void* extraData);
void emitGlobalEntityCollisionWithVoxel(EntityItem* Entity, VoxelDetail* voxelDetails, const CollisionInfo& penetration);
void emitGlobalEntityCollisionWithEntity(EntityItem* entityA, EntityItem* entityB, const CollisionInfo& penetration);
EntityEditPacketSender* _packetSender;
EntityTree* _entities;
VoxelTree* _voxels;
AbstractAudioInterface* _audio;
AvatarHashMap* _avatars;
CollisionList _collisions;
};
#endif // hifi_EntityCollisionSystem_h

View file

@ -535,6 +535,10 @@ bool EntityItem::isRestingOnSurface() const {
void EntityItem::update(const quint64& updateTime) {
bool wantDebug = false;
if (_lastUpdated == 0) {
_lastUpdated = updateTime;
}
float timeElapsed = (float)(updateTime - _lastUpdated) / (float)(USECS_PER_SECOND);
@ -578,7 +582,7 @@ void EntityItem::update(const quint64& updateTime) {
_lastUpdated = updateTime;
if (wantDebug) {
qDebug() << "********** EntityItem::update() .... SETTING _lastUpdated=" << _lastUpdated;
qDebug() << " ********** EntityItem::update() .... SETTING _lastUpdated=" << _lastUpdated;
}
if (hasAngularVelocity()) {
@ -614,7 +618,7 @@ void EntityItem::update(const quint64& updateTime) {
glm::vec3 newPosition = position + (velocity * timeElapsed);
if (wantDebug) {
qDebug() << "EntityItem::update()....";
qDebug() << " EntityItem::update()....";
qDebug() << " timeElapsed:" << timeElapsed;
qDebug() << " old AACube:" << getMaximumAACube();
qDebug() << " old position:" << position;

View file

@ -20,6 +20,7 @@
#include <Octree.h> // for EncodeBitstreamParams class
#include <OctreeElement.h> // for OctreeElement::AppendState
#include <OctreePacketData.h>
#include <VoxelDetail.h>
#include "EntityItemID.h"
#include "EntityItemProperties.h"
@ -227,6 +228,7 @@ public:
// TODO: We need to get rid of these users of getRadius()...
float getRadius() const;
void applyHardCollision(const CollisionInfo& collisionInfo);
protected:
virtual void initFromEntityItemID(const EntityItemID& entityItemID); // maybe useful to allow subclasses to init

View file

@ -24,7 +24,7 @@ EntityItemProperties::EntityItemProperties() :
_id(UNKNOWN_ENTITY_ID),
_idSet(false),
_lastEdited(0),
_lastEdited(0), // ????
_created(UNKNOWN_CREATED_TIME),
_type(EntityTypes::Unknown),

View file

@ -90,6 +90,8 @@ public:
// editing related features supported by all entities
quint64 getLastEdited() const { return _lastEdited; }
float getEditedAgo() const /// Elapsed seconds since this entity was last edited
{ return (float)(usecTimestampNow() - getLastEdited()) / (float)USECS_PER_SECOND; }
EntityPropertyFlags getChangedProperties() const;
/// used by EntityScriptingInterface to return EntityItemProperties for unknown models
@ -121,14 +123,14 @@ public:
float getMass() const { return _mass; }
void setMass(float value) { _mass = value; _massChanged = true; }
/// velocity in domain scale units (0.0-1.0) per second
/// velocity in meters (0.0-1.0) per second
const glm::vec3& getVelocity() const { return _velocity; }
/// velocity in domain scale units (0.0-1.0) per second
/// velocity in meters (0.0-1.0) per second
void setVelocity(const glm::vec3& value) { _velocity = value; _velocityChanged = true; }
/// gravity in domain scale units (0.0-1.0) per second squared
/// gravity in meters (0.0-TREE_SCALE) per second squared
const glm::vec3& getGravity() const { return _gravity; }
/// gravity in domain scale units (0.0-1.0) per second squared
/// gravity in meters (0.0-TREE_SCALE) per second squared
void setGravity(const glm::vec3& value) { _gravity = value; _gravityChanged = true; }
float getDamping() const { return _damping; }
@ -219,9 +221,10 @@ public:
bool getVisible() const { return _visible; }
void setVisible(bool value) { _visible = value; _visibleChanged = true; }
private:
void setLastEdited(quint64 usecTime) { _lastEdited = usecTime; }
private:
QUuid _id;
bool _idSet;
quint64 _lastEdited;
@ -285,4 +288,16 @@ Q_DECLARE_METATYPE(EntityItemProperties);
QScriptValue EntityItemPropertiesToScriptValue(QScriptEngine* engine, const EntityItemProperties& properties);
void EntityItemPropertiesFromScriptValue(const QScriptValue &object, EntityItemProperties& properties);
inline QDebug operator<<(QDebug debug, const EntityItemProperties& properties) {
debug << "EntityItemProperties[" << "\n"
<< " position:" << properties.getPosition() << "in meters" << "\n"
<< " velocity:" << properties.getVelocity() << "in meters" << "\n"
<< " last edited:" << properties.getLastEdited() << "\n"
<< " edited ago:" << properties.getEditedAgo() << "\n"
<< "]";
return debug;
}
#endif // hifi_EntityItemProperties_h

View file

@ -139,6 +139,8 @@ public:
void trackDeletedEntity(const EntityItemID& entityID);
QList<EntityItem*>& getMovingEntities() { return _movingEntities; }
private:
void updateChangingEntities(quint64 now, QSet<EntityItemID>& entitiesToDelete);

View file

@ -732,9 +732,6 @@ bool findSpherePenetrationOp(OctreeElement* element, void* extraData) {
if (!box.expandedContains(args->center, args->radius)) {
return false;
}
if (!element->isLeaf()) {
return true; // recurse on children
}
if (element->hasContent()) {
glm::vec3 elementPenetration;
if (element->findSpherePenetration(args->center, args->radius, elementPenetration, &args->penetratedObject)) {
@ -744,6 +741,9 @@ bool findSpherePenetrationOp(OctreeElement* element, void* extraData) {
args->found = true;
}
}
if (!element->isLeaf()) {
return true; // recurse on children
}
return false;
}

View file

@ -930,6 +930,9 @@ void Particle::applyHardCollision(const CollisionInfo& collisionInfo) {
}
void Particle::update(const quint64& now) {
if (_lastUpdated == 0) {
_lastUpdated = now;
}
float timeElapsed = (float)(now - _lastUpdated) / (float)(USECS_PER_SECOND);
_lastUpdated = now;

View file

@ -72,11 +72,11 @@ void injectorFromScriptValue(const QScriptValue &object, AudioInjector* &out) {
out = qobject_cast<AudioInjector*>(object.toQObject());
}
QScriptValue injectorToScriptValueInputController(QScriptEngine *engine, AbstractInputController* const &in) {
QScriptValue inputControllerToScriptValue(QScriptEngine *engine, AbstractInputController* const &in) {
return engine->newQObject(in);
}
void injectorFromScriptValueInputController(const QScriptValue &object, AbstractInputController* &out) {
void inputControllerFromScriptValue(const QScriptValue &object, AbstractInputController* &out) {
out = qobject_cast<AbstractInputController*>(object.toQObject());
}
@ -277,7 +277,7 @@ void ScriptEngine::init() {
globalObject().setProperty("LocalVoxels", localVoxelsValue);
qScriptRegisterMetaType(this, injectorToScriptValue, injectorFromScriptValue);
qScriptRegisterMetaType( this, injectorToScriptValueInputController, injectorFromScriptValueInputController);
qScriptRegisterMetaType(this, inputControllerToScriptValue, inputControllerFromScriptValue);
qScriptRegisterMetaType(this, animationDetailsToScriptValue, animationDetailsFromScriptValue);

View file

@ -51,4 +51,19 @@ Q_DECLARE_METATYPE(RayToVoxelIntersectionResult)
QScriptValue rayToVoxelIntersectionResultToScriptValue(QScriptEngine* engine, const RayToVoxelIntersectionResult& results);
void rayToVoxelIntersectionResultFromScriptValue(const QScriptValue& object, RayToVoxelIntersectionResult& results);
inline QDebug operator<<(QDebug debug, const VoxelDetail& details) {
const int TREE_SCALE = 16384; // ~10 miles.. This is the number of meters of the 0.0 to 1.0 voxel universe
debug << "VoxelDetail[ ("
<< details.x * (float)TREE_SCALE << "," << details.y * (float)TREE_SCALE << "," << details.z * (float)TREE_SCALE
<< " ) to ("
<< details.x + details.s * (float)TREE_SCALE << "," << details.y + details.s * (float)TREE_SCALE
<< "," << details.z + details.s * (float)TREE_SCALE << ") size: ("
<< details.s * (float)TREE_SCALE << "," << details.s * (float)TREE_SCALE << "," << details.s * (float)TREE_SCALE << ")"
<< " in meters]";
return debug;
}
#endif // hifi_VoxelDetail_h

View file

@ -5,6 +5,6 @@ setup_hifi_project(Script Network)
include_glm()
# link in the shared libraries
link_hifi_libraries(animation fbx entities networking octree shared)
link_hifi_libraries(shared octree voxels fbx metavoxels networking particles entities avatars audio animation script-engine)
link_shared_dependencies()