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

This commit is contained in:
Atlante45 2014-02-18 09:37:54 -08:00
commit 710a960a35
24 changed files with 466 additions and 216 deletions

View file

@ -48,6 +48,8 @@
#include <QXmlStreamReader>
#include <QXmlStreamAttributes>
#include <QMediaPlayer>
#include <QMimeData>
#include <QMessageBox>
#include <AudioInjector.h>
#include <Logging.h>
@ -199,7 +201,7 @@ Application::Application(int& argc, char** argv, timeval &startup_time) :
connect(audioThread, SIGNAL(started()), &_audio, SLOT(start()));
audioThread->start();
connect(nodeList, SIGNAL(domainChanged(const QString&)), SLOT(domainChanged(const QString&)));
connect(nodeList, &NodeList::nodeAdded, this, &Application::nodeAdded);
connect(nodeList, &NodeList::nodeKilled, this, &Application::nodeKilled);
@ -1415,6 +1417,32 @@ void Application::wheelEvent(QWheelEvent* event) {
}
}
void Application::dropEvent(QDropEvent *event) {
QString snapshotPath;
const QMimeData *mimeData = event->mimeData();
foreach (QUrl url, mimeData->urls()) {
if (url.url().toLower().endsWith(SNAPSHOT_EXTENSION)) {
snapshotPath = url.url().remove("file://");
break;
}
}
SnapshotMetaData* snapshotData = Snapshot::parseSnapshotData(snapshotPath);
if (snapshotData != NULL) {
if (!snapshotData->getDomain().isEmpty()) {
Menu::getInstance()->goToDomain(snapshotData->getDomain());
}
_myAvatar->setPosition(snapshotData->getLocation());
_myAvatar->setOrientation(snapshotData->getOrientation());
} else {
QMessageBox msgBox;
msgBox.setText("No location details were found in this JPG, try dragging in an authentic Hifi snapshot.");
msgBox.setStandardButtons(QMessageBox::Ok);
msgBox.exec();
}
}
void Application::sendPingPackets() {
QByteArray pingPacket = NodeList::getInstance()->constructPingPacket();
controlledBroadcastToNodes(pingPacket, NodeSet() << NodeType::VoxelServer
@ -3830,7 +3858,7 @@ void Application::updateWindowTitle(){
QString title = QString() + _profile.getUsername() + " " + nodeList->getSessionUUID().toString()
+ " @ " + nodeList->getDomainHostname() + buildVersion;
qDebug("Application title set to: %s", title.toStdString().c_str());
_window->setWindowTitle(title);
}
@ -4234,6 +4262,6 @@ void Application::takeSnapshot() {
player->setMedia(QUrl::fromLocalFile(inf.absoluteFilePath()));
player->play();
Snapshot::saveSnapshot(_glWidget, _profile.getUsername(), _myAvatar->getPosition());
Snapshot::saveSnapshot(_glWidget, &_profile, _myAvatar);
}

View file

@ -93,6 +93,8 @@ static const float NODE_KILLED_RED = 1.0f;
static const float NODE_KILLED_GREEN = 0.0f;
static const float NODE_KILLED_BLUE = 0.0f;
static const QString SNAPSHOT_EXTENSION = ".jpg";
class Application : public QApplication {
Q_OBJECT
@ -127,6 +129,7 @@ public:
void touchUpdateEvent(QTouchEvent* event);
void wheelEvent(QWheelEvent* event);
void dropEvent(QDropEvent *event);
bool event(QEvent* event);

View file

@ -9,6 +9,8 @@
#include "Application.h"
#include "GLCanvas.h"
#include <QMimeData>
#include <QUrl>
GLCanvas::GLCanvas() : QGLWidget(QGLFormat(QGL::NoDepthBuffer, QGL::NoStencilBuffer)) {
}
@ -16,6 +18,7 @@ GLCanvas::GLCanvas() : QGLWidget(QGLFormat(QGL::NoDepthBuffer, QGL::NoStencilBuf
void GLCanvas::initializeGL() {
Application::getInstance()->initializeGL();
setAttribute(Qt::WA_AcceptTouchEvents);
setAcceptDrops(true);
}
void GLCanvas::paintGL() {
@ -67,4 +70,18 @@ bool GLCanvas::event(QEvent* event) {
void GLCanvas::wheelEvent(QWheelEvent* event) {
Application::getInstance()->wheelEvent(event);
}
}
void GLCanvas::dragEnterEvent(QDragEnterEvent* event) {
const QMimeData *mimeData = event->mimeData();
foreach (QUrl url, mimeData->urls()) {
if (url.url().toLower().endsWith(SNAPSHOT_EXTENSION)) {
event->acceptProposedAction();
break;
}
}
}
void GLCanvas::dropEvent(QDropEvent* event) {
Application::getInstance()->dropEvent(event);
}

View file

@ -31,6 +31,9 @@ protected:
virtual bool event(QEvent* event);
virtual void wheelEvent(QWheelEvent* event);
virtual void dragEnterEvent(QDragEnterEvent *event);
virtual void dropEvent(QDropEvent* event);
};
#endif /* defined(__hifi__GLCanvas__) */

View file

@ -112,7 +112,7 @@ Menu::Menu() :
MenuOption::GoToDomain,
Qt::CTRL | Qt::Key_D,
this,
SLOT(goToDomain()));
SLOT(goToDomainDialog()));
addActionToQMenuAndActionHash(fileMenu,
MenuOption::GoToLocation,
Qt::CTRL | Qt::SHIFT | Qt::Key_L,
@ -169,14 +169,7 @@ Menu::Menu() :
addCheckableActionToQMenuAndActionHash(editMenu, MenuOption::ClickToFly);
QMenu* collisionsOptionsMenu = editMenu->addMenu("Collision Options");
QObject* avatar = appInstance->getAvatar();
addCheckableActionToQMenuAndActionHash(collisionsOptionsMenu, MenuOption::CollideWithEnvironment, 0, false, avatar, SLOT(updateCollisionFlags()));
addCheckableActionToQMenuAndActionHash(collisionsOptionsMenu, MenuOption::CollideWithAvatars, 0, false, avatar, SLOT(updateCollisionFlags()));
addCheckableActionToQMenuAndActionHash(collisionsOptionsMenu, MenuOption::CollideWithVoxels, 0, false, avatar, SLOT(updateCollisionFlags()));
// TODO: make this option work
//addCheckableActionToQMenuAndActionHash(collisionsOptionsMenu, MenuOption::CollideWithParticles, 0, false, avatar, SLOT(updateCollisionFlags()));
addAvatarCollisionSubMenu(editMenu);
QMenu* toolsMenu = addMenu("Tools");
@ -345,6 +338,8 @@ Menu::Menu() :
SLOT(setTCPEnabled(bool)));
addCheckableActionToQMenuAndActionHash(avatarOptionsMenu, MenuOption::ChatCircling, 0, false);
addAvatarCollisionSubMenu(avatarOptionsMenu);
QMenu* handOptionsMenu = developerMenu->addMenu("Hand Options");
addCheckableActionToQMenuAndActionHash(handOptionsMenu,
@ -519,6 +514,11 @@ void Menu::loadSettings(QSettings* settings) {
Application::getInstance()->getProfile()->loadData(settings);
Application::getInstance()->updateWindowTitle();
NodeList::getInstance()->loadData(settings);
// MyAvatar caches some menu options, so we have to update them whenever we load settings.
// TODO: cache more settings in MyAvatar that are checked with very high frequency.
MyAvatar* myAvatar = Application::getInstance()->getAvatar();
myAvatar->updateCollisionFlags();
}
void Menu::saveSettings(QSettings* settings) {
@ -897,7 +897,7 @@ void Menu::goToDomain(const QString newDomain) {
}
}
void Menu::goToDomain() {
void Menu::goToDomainDialog() {
QString currentDomainHostname = NodeList::getInstance()->getDomainHostname();
@ -1232,6 +1232,22 @@ void Menu::updateFrustumRenderModeAction() {
}
}
void Menu::addAvatarCollisionSubMenu(QMenu* overMenu) {
// add avatar collisions subMenu to overMenu
QMenu* subMenu = overMenu->addMenu("Collision Options");
Application* appInstance = Application::getInstance();
QObject* avatar = appInstance->getAvatar();
addCheckableActionToQMenuAndActionHash(subMenu, MenuOption::CollideWithEnvironment,
0, false, avatar, SLOT(updateCollisionFlags()));
addCheckableActionToQMenuAndActionHash(subMenu, MenuOption::CollideWithAvatars,
0, true, avatar, SLOT(updateCollisionFlags()));
addCheckableActionToQMenuAndActionHash(subMenu, MenuOption::CollideWithVoxels,
0, false, avatar, SLOT(updateCollisionFlags()));
addCheckableActionToQMenuAndActionHash(subMenu, MenuOption::CollideWithParticles,
0, true, avatar, SLOT(updateCollisionFlags()));
}
QString Menu::replaceLastOccurrence(QChar search, QChar replace, QString string) {
int lastIndex;
lastIndex = string.lastIndexOf(search);
@ -1242,4 +1258,3 @@ QString Menu::replaceLastOccurrence(QChar search, QChar replace, QString string)
return string;
}

View file

@ -116,7 +116,7 @@ private slots:
void aboutApp();
void login();
void editPreferences();
void goToDomain();
void goToDomainDialog();
void goToLocation();
void bandwidthDetailsClosed();
void voxelStatsDetailsClosed();
@ -152,6 +152,8 @@ private:
void updateFrustumRenderModeAction();
void addAvatarCollisionSubMenu(QMenu* overMenu);
QHash<QString, QAction*> _actionHash;
int _audioJitterBufferSamples; /// number of extra samples to wait before starting audio playback
BandwidthDialog* _bandwidthDialog;

View file

@ -273,27 +273,19 @@ bool Avatar::findRayIntersection(const glm::vec3& origin, const glm::vec3& direc
}
bool Avatar::findSphereCollisions(const glm::vec3& penetratorCenter, float penetratorRadius,
ModelCollisionList& collisions, int skeletonSkipIndex) {
bool didPenetrate = false;
glm::vec3 skeletonPenetration;
ModelCollisionInfo collisionInfo;
/* Temporarily disabling collisions against the skeleton because the collision proxies up
* near the neck are bad and prevent the hand from hitting the face.
if (_skeletonModel.findSphereCollision(penetratorCenter, penetratorRadius, collisionInfo, 1.0f, skeletonSkipIndex)) {
collisionInfo._model = &_skeletonModel;
collisions.push_back(collisionInfo);
didPenetrate = true;
}
*/
if (_head.getFaceModel().findSphereCollision(penetratorCenter, penetratorRadius, collisionInfo)) {
collisionInfo._model = &(_head.getFaceModel());
collisions.push_back(collisionInfo);
didPenetrate = true;
}
return didPenetrate;
CollisionList& collisions, int skeletonSkipIndex) {
// Temporarily disabling collisions against the skeleton because the collision proxies up
// near the neck are bad and prevent the hand from hitting the face.
//return _skeletonModel.findSphereCollisions(penetratorCenter, penetratorRadius, collisions, 1.0f, skeletonSkipIndex);
return _head.getFaceModel().findSphereCollisions(penetratorCenter, penetratorRadius, collisions);
}
bool Avatar::findSphereCollisionWithHands(const glm::vec3& sphereCenter, float sphereRadius, CollisionInfo& collision) {
bool Avatar::findParticleCollisions(const glm::vec3& particleCenter, float particleRadius, CollisionList& collisions) {
if (_collisionFlags & COLLISION_GROUP_PARTICLES) {
return false;
}
bool collided = false;
// first do the hand collisions
const HandData* handData = getHandData();
if (handData) {
for (int i = 0; i < NUM_HANDS; i++) {
@ -311,41 +303,55 @@ bool Avatar::findSphereCollisionWithHands(const glm::vec3& sphereCenter, float s
break;
}
}
int jointIndex = -1;
glm::vec3 handPosition;
if (i == 0) {
_skeletonModel.getLeftHandPosition(handPosition);
jointIndex = _skeletonModel.getLeftHandJointIndex();
}
else {
_skeletonModel.getRightHandPosition(handPosition);
jointIndex = _skeletonModel.getRightHandJointIndex();
}
glm::vec3 diskCenter = handPosition + HAND_PADDLE_OFFSET * fingerAxis;
glm::vec3 diskNormal = palm->getNormal();
float diskThickness = 0.08f;
const float DISK_THICKNESS = 0.08f;
// collide against the disk
if (findSphereDiskPenetration(sphereCenter, sphereRadius,
diskCenter, HAND_PADDLE_RADIUS, diskThickness, diskNormal,
collision._penetration)) {
collision._addedVelocity = palm->getVelocity();
return true;
glm::vec3 penetration;
if (findSphereDiskPenetration(particleCenter, particleRadius,
diskCenter, HAND_PADDLE_RADIUS, DISK_THICKNESS, diskNormal,
penetration)) {
CollisionInfo* collision = collisions.getNewCollision();
if (collision) {
collision->_type = PADDLE_HAND_COLLISION;
collision->_flags = jointIndex;
collision->_penetration = penetration;
collision->_addedVelocity = palm->getVelocity();
collided = true;
} else {
// collisions are full, so we might as well bail now
return collided;
}
}
}
}
}
return false;
}
/* adebug TODO: make this work again
bool Avatar::findSphereCollisionWithSkeleton(const glm::vec3& sphereCenter, float sphereRadius, CollisionInfo& collision) {
int jointIndex = _skeletonModel.findSphereCollision(sphereCenter, sphereRadius, collision._penetration);
if (jointIndex != -1) {
collision._penetration /= (float)(TREE_SCALE);
collision._addedVelocity = getVelocity();
return true;
// then collide against the models
int preNumCollisions = collisions.size();
if (_skeletonModel.findSphereCollisions(particleCenter, particleRadius, collisions)) {
// the Model doesn't have velocity info, so we have to set it for each new collision
int postNumCollisions = collisions.size();
for (int i = preNumCollisions; i < postNumCollisions; ++i) {
CollisionInfo* collision = collisions.getCollision(i);
collision->_penetration /= (float)(TREE_SCALE);
collision->_addedVelocity = getVelocity();
}
collided = true;
}
return false;
return collided;
}
*/
void Avatar::setFaceModelURL(const QUrl &faceModelURL) {
AvatarData::setFaceModelURL(faceModelURL);
@ -430,9 +436,9 @@ void Avatar::updateCollisionFlags() {
if (Menu::getInstance()->isOptionChecked(MenuOption::CollideWithVoxels)) {
_collisionFlags |= COLLISION_GROUP_VOXELS;
}
//if (Menu::getInstance()->isOptionChecked(MenuOption::CollideWithParticles)) {
// _collisionFlags |= COLLISION_GROUP_PARTICLES;
//}
if (Menu::getInstance()->isOptionChecked(MenuOption::CollideWithParticles)) {
_collisionFlags |= COLLISION_GROUP_PARTICLES;
}
}
void Avatar::setScale(float scale) {
@ -449,34 +455,34 @@ float Avatar::getHeight() const {
return extents.maximum.y - extents.minimum.y;
}
bool Avatar::collisionWouldMoveAvatar(ModelCollisionInfo& collision) const {
// ATM only the Skeleton is pokeable
// TODO: make poke affect head
if (!collision._model) {
bool Avatar::collisionWouldMoveAvatar(CollisionInfo& collision) const {
if (!collision._data || collision._type != MODEL_COLLISION) {
return false;
}
if (collision._model == &_skeletonModel && collision._jointIndex != -1) {
Model* model = static_cast<Model*>(collision._data);
int jointIndex = collision._flags;
if (model == &(_skeletonModel) && jointIndex != -1) {
// collision response of skeleton is temporarily disabled
return false;
//return _skeletonModel.collisionHitsMoveableJoint(collision);
}
if (collision._model == &(_head.getFaceModel())) {
if (model == &(_head.getFaceModel())) {
// ATM we always handle MODEL_COLLISIONS against the face.
return true;
}
return false;
}
void Avatar::applyCollision(ModelCollisionInfo& collision) {
if (!collision._model) {
void Avatar::applyCollision(CollisionInfo& collision) {
if (!collision._data || collision._type != MODEL_COLLISION) {
return;
}
if (collision._model == &(_head.getFaceModel())) {
// TODO: make skeleton also respond to collisions
Model* model = static_cast<Model*>(collision._data);
if (model == &(_head.getFaceModel())) {
_head.applyCollision(collision);
}
// TODO: make skeleton respond to collisions
//if (collision._model == &_skeletonModel && collision._jointIndex != -1) {
// _skeletonModel.applyCollision(collision);
//}
}
float Avatar::getPelvisFloatingHeight() const {

View file

@ -57,8 +57,6 @@ enum ScreenTintLayer {
NUM_SCREEN_TINT_LAYERS
};
typedef QVector<ModelCollisionInfo> ModelCollisionList;
// Where one's own Avatar begins in the world (will be overwritten if avatar data file is found)
// this is basically in the center of the ground plane. Slightly adjusted. This was asked for by
// Grayson as he's building a street around here for demo dinner 2
@ -97,26 +95,19 @@ public:
/// Checks for penetration between the described sphere and the avatar.
/// \param penetratorCenter the center of the penetration test sphere
/// \param penetratorRadius the radius of the penetration test sphere
/// \param collisions[out] a list of collisions
/// \param collisions[out] a list to which collisions get appended
/// \param skeletonSkipIndex if not -1, the index of a joint to skip (along with its descendents) in the skeleton model
/// \return whether or not the sphere penetrated
bool findSphereCollisions(const glm::vec3& penetratorCenter, float penetratorRadius,
ModelCollisionList& collisions, int skeletonSkipIndex = -1);
CollisionList& collisions, int skeletonSkipIndex = -1);
/// Checks for collision between the a sphere and the avatar's (paddle) hands.
/// \param collisionCenter the center of the penetration test sphere
/// \param collisionRadius the radius of the penetration test sphere
/// \param collision[out] the details of the collision point
/// \return whether or not the sphere collided
bool findSphereCollisionWithHands(const glm::vec3& sphereCenter, float sphereRadius, CollisionInfo& collision);
/// Checks for collision between the a spherical particle and the avatar (including paddle hands)
/// \param collisionCenter the center of particle's bounding sphere
/// \param collisionRadius the radius of particle's bounding sphere
/// \param collisions[out] a list to which collisions get appended
/// \return whether or not the particle collided
bool findParticleCollisions(const glm::vec3& particleCenter, float particleRadius, CollisionList& collisions);
/// Checks for collision between the a sphere and the avatar's skeleton (including hand capsules).
/// \param collisionCenter the center of the penetration test sphere
/// \param collisionRadius the radius of the penetration test sphere
/// \param collision[out] the details of the collision point
/// \return whether or not the sphere collided
//bool findSphereCollisionWithSkeleton(const glm::vec3& sphereCenter, float sphereRadius, CollisionInfo& collision);
virtual bool isMyAvatar() { return false; }
virtual void setFaceModelURL(const QUrl& faceModelURL);
@ -126,13 +117,13 @@ public:
static void renderJointConnectingCone(glm::vec3 position1, glm::vec3 position2, float radius1, float radius2);
float getHeight() const;
/// \return true if we expect the avatar would move as a result of the collision
bool collisionWouldMoveAvatar(ModelCollisionInfo& collision) const;
bool collisionWouldMoveAvatar(CollisionInfo& collision) const;
/// \param collision a data structure for storing info about collisions against Models
void applyCollision(ModelCollisionInfo& collision);
void applyCollision(CollisionInfo& collision);
float getBoundingRadius() const { return 0.5f * getHeight(); }
public slots:
void updateCollisionFlags();
@ -164,6 +155,7 @@ protected:
glm::quat computeRotationFromBodyToWorldUp(float proportion = 1.0f) const;
void setScale(float scale);
float getHeight() const;
float getPelvisFloatingHeight() const;
float getPelvisToHeadLength() const;

View file

@ -125,6 +125,10 @@ void Hand::simulate(float deltaTime, bool isMine) {
}
}
// We create a static CollisionList that is recycled for each collision test.
const float MAX_COLLISIONS_PER_AVATAR = 32;
static CollisionList handCollisions(MAX_COLLISIONS_PER_AVATAR);
void Hand::collideAgainstAvatar(Avatar* avatar, bool isMyHand) {
if (!avatar || avatar == _owningAvatar) {
// don't collide with our own hands (that is done elsewhere)
@ -137,7 +141,6 @@ void Hand::collideAgainstAvatar(Avatar* avatar, bool isMyHand) {
continue;
}
glm::vec3 totalPenetration;
ModelCollisionList collisions;
if (isMyHand && Menu::getInstance()->isOptionChecked(MenuOption::PlaySlaps)) {
// Check for palm collisions
glm::vec3 myPalmPosition = palm.getPosition();
@ -171,20 +174,22 @@ void Hand::collideAgainstAvatar(Avatar* avatar, bool isMyHand) {
}
}
}
if (avatar->findSphereCollisions(palm.getPosition(), scaledPalmRadius, collisions)) {
for (int j = 0; j < collisions.size(); ++j) {
handCollisions.clear();
if (avatar->findSphereCollisions(palm.getPosition(), scaledPalmRadius, handCollisions)) {
for (int j = 0; j < handCollisions.size(); ++j) {
CollisionInfo* collision = handCollisions.getCollision(j);
if (isMyHand) {
if (!avatar->collisionWouldMoveAvatar(collisions[j])) {
if (!avatar->collisionWouldMoveAvatar(*collision)) {
// we resolve the hand from collision when it belongs to MyAvatar AND the other Avatar is
// not expected to respond to the collision (hand hit unmovable part of their Avatar)
totalPenetration = addPenetrations(totalPenetration, collisions[j]._penetration);
totalPenetration = addPenetrations(totalPenetration, collision->_penetration);
}
} else {
// when !isMyHand then avatar is MyAvatar and we apply the collision
// which might not do anything (hand hit unmovable part of MyAvatar) however
// we don't resolve the hand's penetration because we expect the remote
// simulation to do the right thing.
avatar->applyCollision(collisions[j]);
avatar->applyCollision(*collision);
}
}
}
@ -200,7 +205,6 @@ void Hand::collideAgainstOurself() {
return;
}
ModelCollisionList collisions;
int leftPalmIndex, rightPalmIndex;
getLeftRightPalmIndices(leftPalmIndex, rightPalmIndex);
float scaledPalmRadius = PALM_COLLISION_RADIUS * _owningAvatar->getScale();
@ -210,16 +214,18 @@ void Hand::collideAgainstOurself() {
if (!palm.isActive()) {
continue;
}
glm::vec3 totalPenetration;
// and the current avatar (ignoring everything below the parent of the parent of the last free joint)
collisions.clear();
const Model& skeletonModel = _owningAvatar->getSkeletonModel();
// ignoring everything below the parent of the parent of the last free joint
int skipIndex = skeletonModel.getParentJointIndex(skeletonModel.getParentJointIndex(
skeletonModel.getLastFreeJointIndex((i == leftPalmIndex) ? skeletonModel.getLeftHandJointIndex() :
(i == rightPalmIndex) ? skeletonModel.getRightHandJointIndex() : -1)));
if (_owningAvatar->findSphereCollisions(palm.getPosition(), scaledPalmRadius, collisions, skipIndex)) {
for (int j = 0; j < collisions.size(); ++j) {
totalPenetration = addPenetrations(totalPenetration, collisions[j]._penetration);
handCollisions.clear();
glm::vec3 totalPenetration;
if (_owningAvatar->findSphereCollisions(palm.getPosition(), scaledPalmRadius, handCollisions, skipIndex)) {
for (int j = 0; j < handCollisions.size(); ++j) {
CollisionInfo* collision = handCollisions.getCollision(j);
totalPenetration = addPenetrations(totalPenetration, collision->_penetration);
}
}
// resolve penetration

View file

@ -219,7 +219,7 @@ float Head::getTweakedRoll() const {
return glm::clamp(_roll + _tweakedRoll, MIN_HEAD_ROLL, MAX_HEAD_ROLL);
}
void Head::applyCollision(ModelCollisionInfo& collisionInfo) {
void Head::applyCollision(CollisionInfo& collision) {
// HACK: the collision proxies for the FaceModel are bad. As a temporary workaround
// we collide against a hard coded collision proxy.
// TODO: get a better collision proxy here.
@ -229,7 +229,7 @@ void Head::applyCollision(ModelCollisionInfo& collisionInfo) {
// collide the contactPoint against the collision proxy to obtain a new penetration
// NOTE: that penetration is in opposite direction (points the way out for the point, not the sphere)
glm::vec3 penetration;
if (findPointSpherePenetration(collisionInfo._contactPoint, HEAD_CENTER, HEAD_RADIUS, penetration)) {
if (findPointSpherePenetration(collision._contactPoint, HEAD_CENTER, HEAD_RADIUS, penetration)) {
// compute lean angles
Avatar* owningAvatar = static_cast<Avatar*>(_owningAvatar);
glm::quat bodyRotation = owningAvatar->getOrientation();
@ -239,8 +239,8 @@ void Head::applyCollision(ModelCollisionInfo& collisionInfo) {
glm::vec3 zAxis = bodyRotation * glm::vec3(0.f, 0.f, 1.f);
float neckLength = glm::length(_position - neckPosition);
if (neckLength > 0.f) {
float forward = glm::dot(collisionInfo._penetration, zAxis) / neckLength;
float sideways = - glm::dot(collisionInfo._penetration, xAxis) / neckLength;
float forward = glm::dot(collision._penetration, zAxis) / neckLength;
float sideways = - glm::dot(collision._penetration, xAxis) / neckLength;
addLean(sideways, forward);
}
}

View file

@ -80,7 +80,7 @@ public:
float getTweakedYaw() const;
float getTweakedRoll() const;
void applyCollision(ModelCollisionInfo& collisionInfo);
void applyCollision(CollisionInfo& collisionInfo);
private:
// disallow copies of the Head, copy of owning Avatar is disallowed too

View file

@ -955,7 +955,7 @@ void MyAvatar::updateCollisionWithAvatars(float deltaTime) {
// no need to compute a bunch of stuff if we have one or fewer avatars
return;
}
float myBoundingRadius = 0.5f * getHeight();
float myBoundingRadius = getBoundingRadius();
// HACK: body-body collision uses two coaxial capsules with axes parallel to y-axis
// TODO: make the collision work without assuming avatar orientation
@ -975,7 +975,7 @@ void MyAvatar::updateCollisionWithAvatars(float deltaTime) {
if (_distanceToNearestAvatar > distance) {
_distanceToNearestAvatar = distance;
}
float theirBoundingRadius = 0.5f * avatar->getHeight();
float theirBoundingRadius = avatar->getBoundingRadius();
if (distance < myBoundingRadius + theirBoundingRadius) {
Extents theirStaticExtents = _skeletonModel.getStaticExtents();
glm::vec3 staticScale = theirStaticExtents.maximum - theirStaticExtents.minimum;

View file

@ -45,5 +45,6 @@ private:
TouchState _touchState;
timeval* _lastReceivedPacket;
#endif /* defined(__hifi__Transmitter__) */
};
#endif /* defined(__hifi__Transmitter__) */

View file

@ -457,9 +457,9 @@ bool Model::findRayIntersection(const glm::vec3& origin, const glm::vec3& direct
return false;
}
bool Model::findSphereCollision(const glm::vec3& penetratorCenter, float penetratorRadius,
ModelCollisionInfo& collisionInfo, float boneScale, int skipIndex) const {
int jointIndex = -1;
bool Model::findSphereCollisions(const glm::vec3& penetratorCenter, float penetratorRadius,
CollisionList& collisions, float boneScale, int skipIndex) const {
bool collided = false;
const glm::vec3 relativeCenter = penetratorCenter - _translation;
const FBXGeometry& geometry = _geometry->getFBXGeometry();
glm::vec3 totalPenetration;
@ -488,22 +488,22 @@ bool Model::findSphereCollision(const glm::vec3& penetratorCenter, float penetra
if (findSphereCapsuleConePenetration(relativeCenter, penetratorRadius, start, end,
startRadius, endRadius, bonePenetration)) {
totalPenetration = addPenetrations(totalPenetration, bonePenetration);
// BUG: we currently overwrite the jointIndex with the last one found
// which can cause incorrect collisions when colliding against more than
// one joint.
// TODO: fix this.
jointIndex = i;
CollisionInfo* collision = collisions.getNewCollision();
if (collision) {
collision->_type = MODEL_COLLISION;
collision->_data = (void*)(this);
collision->_flags = i;
collision->_contactPoint = penetratorCenter + penetratorRadius * glm::normalize(totalPenetration);
collision->_penetration = totalPenetration;
collided = true;
} else {
// collisions are full, so we might as well break
break;
}
}
outerContinue: ;
}
if (jointIndex != -1) {
// don't store collisionInfo._model at this stage, let the outer context do that
collisionInfo._penetration = totalPenetration;
collisionInfo._jointIndex = jointIndex;
collisionInfo._contactPoint = penetratorCenter + penetratorRadius * glm::normalize(totalPenetration);
return true;
}
return false;
return collided;
}
void Model::updateJointState(int index) {
@ -736,24 +736,30 @@ void Model::renderCollisionProxies(float alpha) {
glPopMatrix();
}
bool Model::collisionHitsMoveableJoint(ModelCollisionInfo& collision) const {
// the joint is pokable by a collision if it exists and is free to move
const FBXJoint& joint = _geometry->getFBXGeometry().joints[collision._jointIndex];
if (joint.parentIndex == -1 || _jointStates.isEmpty()) {
return false;
bool Model::collisionHitsMoveableJoint(CollisionInfo& collision) const {
if (collision._type == MODEL_COLLISION) {
// the joint is pokable by a collision if it exists and is free to move
const FBXJoint& joint = _geometry->getFBXGeometry().joints[collision._flags];
if (joint.parentIndex == -1 || _jointStates.isEmpty()) {
return false;
}
// an empty freeLineage means the joint can't move
const FBXGeometry& geometry = _geometry->getFBXGeometry();
int jointIndex = collision._flags;
const QVector<int>& freeLineage = geometry.joints.at(jointIndex).freeLineage;
return !freeLineage.isEmpty();
}
// an empty freeLineage means the joint can't move
const FBXGeometry& geometry = _geometry->getFBXGeometry();
const QVector<int>& freeLineage = geometry.joints.at(collision._jointIndex).freeLineage;
return !freeLineage.isEmpty();
return false;
}
void Model::applyCollision(ModelCollisionInfo& collision) {
// This needs work. At the moment it can wiggle joints that are free to move (such as arms)
// but unmovable joints (such as torso) cannot be influenced at all.
void Model::applyCollision(CollisionInfo& collision) {
if (collision._type != MODEL_COLLISION) {
return;
}
glm::vec3 jointPosition(0.f);
if (getJointPosition(collision._jointIndex, jointPosition)) {
int jointIndex = collision._jointIndex;
int jointIndex = collision._flags;
if (getJointPosition(jointIndex, jointPosition)) {
const FBXJoint& joint = _geometry->getFBXGeometry().joints[jointIndex];
if (joint.parentIndex != -1) {
// compute the approximate distance (travel) that the joint needs to move

View file

@ -17,16 +17,6 @@
#include "ProgramObject.h"
#include "TextureCache.h"
class Model;
// TODO: Andrew to move this into its own file
class ModelCollisionInfo : public CollisionInfo {
public:
ModelCollisionInfo() : CollisionInfo(), _model(NULL), _jointIndex(-1) {}
Model* _model;
int _jointIndex;
};
/// A generic 3D model displaying geometry loaded from a URL.
class Model : public QObject {
Q_OBJECT
@ -162,17 +152,18 @@ public:
bool findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, float& distance) const;
bool findSphereCollision(const glm::vec3& penetratorCenter, float penetratorRadius,
ModelCollisionInfo& collision, float boneScale = 1.0f, int skipIndex = -1) const;
bool findSphereCollisions(const glm::vec3& penetratorCenter, float penetratorRadius,
CollisionList& collisions, float boneScale = 1.0f, int skipIndex = -1) const;
void renderCollisionProxies(float alpha);
/// \param collision details about the collisions
/// \return true if the collision is against a moveable joint
bool collisionHitsMoveableJoint(ModelCollisionInfo& collision) const;
bool collisionHitsMoveableJoint(CollisionInfo& collision) const;
/// \param collisionInfo info about the collision
/// Use the collisionInfo to affect the model
void applyCollision(ModelCollisionInfo& collisionInfo);
/// \param collision details about the collision
/// Use the collision to affect the model
void applyCollision(CollisionInfo& collision);
protected:

View file

@ -12,7 +12,6 @@
#include <QDateTime>
#include <QFileInfo>
#include <QDebug>
// filename format: hifi-snap-by-%username%-on-%date%_%time%_@-%location%.jpg
// %1 <= username, %2 <= date and time, %3 <= current location
@ -21,18 +20,69 @@ const QString FILENAME_PATH_FORMAT = "hifi-snap-by-%1-on-%2@%3.jpg";
const QString DATETIME_FORMAT = "yyyy-MM-dd_hh-mm-ss";
const QString SNAPSHOTS_DIRECTORY = "Snapshots";
void Snapshot::saveSnapshot(QGLWidget* widget, QString username, glm::vec3 location) {
QImage shot = widget->grabFrameBuffer();
const QString LOCATION_X = "location-x";
const QString LOCATION_Y = "location-y";
const QString LOCATION_Z = "location-z";
const QString ORIENTATION_X = "orientation-x";
const QString ORIENTATION_Y = "orientation-y";
const QString ORIENTATION_Z = "orientation-z";
const QString ORIENTATION_W = "orientation-w";
const QString DOMAIN_KEY = "domain";
SnapshotMetaData* Snapshot::parseSnapshotData(QString snapshotPath) {
if (!QFile(snapshotPath).exists()) {
return NULL;
}
QImage shot(snapshotPath);
// no location data stored
if (shot.text(LOCATION_X).isEmpty() || shot.text(LOCATION_Y).isEmpty() || shot.text(LOCATION_Z).isEmpty()) {
return NULL;
}
SnapshotMetaData* data = new SnapshotMetaData();
data->setLocation(glm::vec3(shot.text(LOCATION_X).toFloat(),
shot.text(LOCATION_Y).toFloat(),
shot.text(LOCATION_Z).toFloat()));
data->setOrientation(glm::quat(shot.text(ORIENTATION_W).toFloat(),
shot.text(ORIENTATION_X).toFloat(),
shot.text(ORIENTATION_Y).toFloat(),
shot.text(ORIENTATION_Z).toFloat()));
data->setDomain(shot.text(DOMAIN_KEY));
return data;
}
void Snapshot::saveSnapshot(QGLWidget* widget, Profile* profile, Avatar* avatar) {
QImage shot = widget->grabFrameBuffer();
glm::vec3 location = avatar->getPosition();
glm::quat orientation = avatar->getHead().getOrientation();
// add metadata
shot.setText("location-x", QString::number(location.x));
shot.setText("location-y", QString::number(location.y));
shot.setText("location-z", QString::number(location.z));
shot.setText(LOCATION_X, QString::number(location.x));
shot.setText(LOCATION_Y, QString::number(location.y));
shot.setText(LOCATION_Z, QString::number(location.z));
shot.setText(ORIENTATION_X, QString::number(orientation.x));
shot.setText(ORIENTATION_Y, QString::number(orientation.y));
shot.setText(ORIENTATION_Z, QString::number(orientation.z));
shot.setText(ORIENTATION_W, QString::number(orientation.w));
shot.setText(DOMAIN_KEY, profile->getLastDomain());
QString formattedLocation = QString("%1_%2_%3").arg(location.x).arg(location.y).arg(location.z);
// replace decimal . with '-'
formattedLocation.replace('.', '-');
QString username = profile->getUsername();
// normalize username, replace all non alphanumeric with '-'
username.replace(QRegExp("[^A-Za-z0-9_]"), "-");

View file

@ -9,19 +9,38 @@
#ifndef __hifi__Snapshot__
#define __hifi__Snapshot__
#include "InterfaceConfig.h"
#include <QString>
#include <QImage>
#include <QGLWidget>
#include <glm/glm.hpp>
#include "avatar/Avatar.h"
#include "avatar/Profile.h"
class SnapshotMetaData {
public:
QString getDomain() { return _domain; }
void setDomain(QString domain) { _domain = domain; }
glm::vec3 getLocation() { return _location; }
void setLocation(glm::vec3 location) { _location = location; }
glm::quat getOrientation() { return _orientation; }
void setOrientation(glm::quat orientation) { _orientation = orientation; }
private:
QString _domain;
glm::vec3 _location;
glm::quat _orientation;;
};
class Snapshot {
public:
static void saveSnapshot(QGLWidget* widget, QString username, glm::vec3 location);
private:
QString _username;
static void saveSnapshot(QGLWidget* widget, Profile* profile, Avatar* avatar);
static SnapshotMetaData* parseSnapshotData(QString snapshotPath);
};
#endif /* defined(__hifi__Snapshot__) */

View file

@ -135,14 +135,10 @@ public:
virtual const glm::vec3& getVelocity() const { return vec3Zero; }
virtual bool findSphereCollisionWithHands(const glm::vec3& sphereCenter, float sphereRadius, CollisionInfo& collision) {
virtual bool findParticleCollisions(const glm::vec3& particleCenter, float particleRadius, CollisionList& collisions) {
return false;
}
virtual bool findSphereCollisionWithSkeleton(const glm::vec3& sphereCenter, float sphereRadius, CollisionInfo& collision) {
return false;
}
bool hasIdentityChangedAfterParsing(const QByteArray& packet);
QByteArray identityByteArray();
@ -150,6 +146,8 @@ public:
const QUrl& getSkeletonModelURL() const { return _skeletonModelURL; }
virtual void setFaceModelURL(const QUrl& faceModelURL);
virtual void setSkeletonModelURL(const QUrl& skeletonModelURL);
virtual float getBoundingRadius() const { return 1.f; }
protected:
glm::vec3 _position;

View file

@ -791,7 +791,6 @@ const char* OctreeSceneStats::getItemValue(Item item) {
break;
}
default:
sprintf(_itemValueBuffer, "");
break;
}
return _itemValueBuffer;

View file

@ -19,9 +19,11 @@
#include "ParticleEditPacketSender.h"
#include "ParticleTree.h"
const int MAX_COLLISIONS_PER_PARTICLE = 16;
ParticleCollisionSystem::ParticleCollisionSystem(ParticleEditPacketSender* packetSender,
ParticleTree* particles, VoxelTree* voxels, AbstractAudioInterface* audio,
AvatarHashMap* avatars) {
AvatarHashMap* avatars) : _collisions(MAX_COLLISIONS_PER_PARTICLE) {
init(packetSender, particles, voxels, audio, avatars);
}
@ -181,39 +183,53 @@ void ParticleCollisionSystem::updateCollisionWithAvatars(Particle* particle) {
const float COLLISION_FREQUENCY = 0.5f;
glm::vec3 penetration;
_collisions.clear();
foreach (const AvatarSharedPointer& avatarPointer, _avatars->getAvatarHash()) {
AvatarData* avatar = avatarPointer.data();
CollisionInfo collisionInfo;
collisionInfo._damping = DAMPING;
collisionInfo._elasticity = ELASTICITY;
if (avatar->findSphereCollisionWithHands(center, radius, collisionInfo)) {
// TODO: Andrew to resurrect particles-vs-avatar body collisions
//avatar->findSphereCollisionWithSkeleton(center, radius, collisionInfo)) {
collisionInfo._addedVelocity /= (float)(TREE_SCALE);
glm::vec3 relativeVelocity = collisionInfo._addedVelocity - particle->getVelocity();
if (glm::dot(relativeVelocity, collisionInfo._penetration) < 0.f) {
// only collide when particle and collision point are moving toward each other
// (doing this prevents some "collision snagging" when particle penetrates the object)
// HACK BEGIN: to allow paddle hands to "hold" particles we attenuate soft collisions against the avatar.
// NOTE: the physics are wrong (particles cannot roll) but it IS possible to catch a slow moving particle.
// TODO: make this less hacky when we have more per-collision details
float elasticity = ELASTICITY;
float attenuationFactor = glm::length(collisionInfo._addedVelocity) / HALTING_SPEED;
float damping = DAMPING;
if (attenuationFactor < 1.f) {
collisionInfo._addedVelocity *= attenuationFactor;
elasticity *= attenuationFactor;
// NOTE: the math below keeps the damping piecewise continuous,
// while ramping it up to 1.0 when attenuationFactor = 0
damping = DAMPING + (1.f - attenuationFactor) * (1.f - DAMPING);
// use a very generous bounding radius since the arms can stretch
float totalRadius = 2.f * avatar->getBoundingRadius() + radius;
glm::vec3 relativePosition = center - avatar->getPosition();
if (glm::dot(relativePosition, relativePosition) > (totalRadius * totalRadius)) {
continue;
}
if (avatar->findParticleCollisions(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 - particle->getVelocity();
if (glm::dot(relativeVelocity, collision->_penetration) <= 0.f) {
// only collide when particle and collision point are moving toward each other
// (doing this prevents some "collision snagging" when particle penetrates the object)
// HACK BEGIN: to allow paddle hands to "hold" particles we attenuate soft collisions against them.
if (collision->_type == PADDLE_HAND_COLLISION) {
// NOTE: the physics are wrong (particles cannot roll) but it IS possible to catch a slow moving particle.
// TODO: make this less hacky when we have more per-collision details
float elasticity = ELASTICITY;
float attenuationFactor = glm::length(collision->_addedVelocity) / HALTING_SPEED;
float damping = DAMPING;
if (attenuationFactor < 1.f) {
collision->_addedVelocity *= attenuationFactor;
elasticity *= attenuationFactor;
// NOTE: the math below keeps the damping piecewise continuous,
// while ramping it up to 1 when attenuationFactor = 0
damping = DAMPING + (1.f - attenuationFactor) * (1.f - DAMPING);
}
}
// HACK END
updateCollisionSound(particle, collision->_penetration, COLLISION_FREQUENCY);
collision->_penetration /= (float)(TREE_SCALE);
particle->applyHardCollision(*collision);
queueParticlePropertiesUpdate(particle);
}
// HACK END
updateCollisionSound(particle, collisionInfo._penetration, COLLISION_FREQUENCY);
collisionInfo._penetration /= (float)(TREE_SCALE);
particle->applyHardCollision(collisionInfo);
queueParticlePropertiesUpdate(particle);
}
}
}

View file

@ -66,6 +66,7 @@ private:
VoxelTree* _voxels;
AbstractAudioInterface* _audio;
AvatarHashMap* _avatars;
CollisionList _collisions;
};
#endif /* defined(__hifi__ParticleCollisionSystem__) */

View file

@ -0,0 +1,42 @@
//
// CollisionInfo.cpp
// hifi
//
// Created by Andrew Meadows on 2014.02.14
// Copyright (c) 2014 High Fidelity, Inc. All rights reserved.
//
#include "CollisionInfo.h"
CollisionList::CollisionList(int maxSize) :
_maxSize(maxSize),
_size(0) {
_collisions.resize(_maxSize);
}
CollisionInfo* CollisionList::getNewCollision() {
// return pointer to existing CollisionInfo, or NULL of list is full
return (_size < _maxSize) ? &(_collisions[++_size]) : NULL;
}
CollisionInfo* CollisionList::getCollision(int index) {
return (index > -1 && index < _size) ? &(_collisions[index]) : NULL;
}
void CollisionList::clear() {
for (int i = 0; i < _size; ++i) {
// we only clear the important stuff
CollisionInfo& collision = _collisions[i];
collision._type = BASE_COLLISION;
collision._data = NULL; // CollisionInfo does not own whatever this points to.
collision._flags = 0;
// we rely on the consumer to properly overwrite these fields when the collision is "created"
//collision._damping;
//collision._elasticity;
//collision._contactPoint;
//collision._penetration;
//collision._addedVelocity;
}
_size = 0;
}

View file

@ -11,15 +11,42 @@
#include <glm/glm.hpp>
const uint32_t COLLISION_GROUP_ENVIRONMENT = 1U << 0;
const uint32_t COLLISION_GROUP_AVATARS = 1U << 1;
const uint32_t COLLISION_GROUP_VOXELS = 1U << 2;
const uint32_t COLLISION_GROUP_PARTICLES = 1U << 3;
#include <QVector>
enum CollisionType {
BASE_COLLISION = 0,
PADDLE_HAND_COLLISION,
MODEL_COLLISION,
};
const quint32 COLLISION_GROUP_ENVIRONMENT = 1U << 0;
const quint32 COLLISION_GROUP_AVATARS = 1U << 1;
const quint32 COLLISION_GROUP_VOXELS = 1U << 2;
const quint32 COLLISION_GROUP_PARTICLES = 1U << 3;
// CollisionInfo contains details about the collision between two things: BodyA and BodyB.
// The assumption is that the context that analyzes the collision knows about BodyA but
// does not necessarily know about BodyB. Hence the data storred in the CollisionInfo
// is expected to be relative to BodyA (for example the penetration points from A into B).
class CollisionInfo {
public:
CollisionInfo()
: _damping(0.f),
: _type(0),
_data(NULL),
_flags(0),
_damping(0.f),
_elasticity(1.f),
_contactPoint(0.f),
_penetration(0.f),
_addedVelocity(0.f) {
}
CollisionInfo(qint32 type)
: _type(type),
_data(NULL),
_flags(0),
_damping(0.f),
_elasticity(1.f),
_contactPoint(0.f),
_penetration(0.f),
@ -28,13 +55,40 @@ public:
~CollisionInfo() {}
//glm::vec3 _normal;
float _damping;
float _elasticity;
glm::vec3 _contactPoint; // world-frame point on bodyA that is deepest into bodyB
glm::vec3 _penetration; // depth that bodyA penetrates into bodyB
glm::vec3 _addedVelocity;
qint32 _type; // type of Collision (will determine what is supposed to be in _data and _flags)
void* _data; // pointer to user supplied data
quint32 _flags; // 32 bits for whatever
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
glm::vec3 _penetration; // depth that BodyA penetrates into BodyB
glm::vec3 _addedVelocity; // velocity of BodyB
};
// CollisionList is intended to be a recycled container. Fill the CollisionInfo's,
// use them, and then clear them for the next frame or context.
class CollisionList {
public:
CollisionList(int maxSize);
/// \return pointer to next collision. NULL if list is full.
CollisionInfo* getNewCollision();
/// \return pointer to collision by index. NULL if index out of bounds.
CollisionInfo* getCollision(int index);
/// \return number of valid collisions
int size() const { return _size; }
/// Clear valid collisions.
void clear();
private:
int _maxSize;
int _size;
QVector<CollisionInfo> _collisions;
};
#endif /* defined(__hifi__CollisionInfo__) */

View file

@ -807,7 +807,8 @@ void NodeList::loadData(QSettings *settings) {
} else {
_domainHostname = DEFAULT_DOMAIN_HOSTNAME;
}
emit domainChanged(_domainHostname);
settings->endGroup();
}