Merge pull request #1722 from AndrewMeadows/fix-particle-avatar-collisions

Fix particle avatar collisions
This commit is contained in:
ZappoMan 2014-01-28 14:57:25 -08:00
commit 50f36c6b74
20 changed files with 386 additions and 207 deletions

View file

@ -1754,6 +1754,7 @@ void Application::init() {
_myCamera.setMode(CAMERA_MODE_FIRST_PERSON);
_myCamera.setModeShiftRate(1.0f);
_myAvatar.setDisplayingLookatVectors(false);
_avatarManager.setMyAvatar(&_myAvatar);
_mirrorCamera.setMode(CAMERA_MODE_MIRROR);
_mirrorCamera.setAspectRatio((float)MIRROR_VIEW_WIDTH / (float)MIRROR_VIEW_HEIGHT);
@ -1800,7 +1801,7 @@ void Application::init() {
_metavoxels.init();
_particleCollisionSystem.init(&_particleEditSender, _particles.getTree(), _voxels.getTree(), &_audio, &_myAvatar);
_particleCollisionSystem.init(&_particleEditSender, _particles.getTree(), _voxels.getTree(), &_audio, &_avatarManager);
_palette.init(_glWidget->width(), _glWidget->height());
_palette.addAction(Menu::getInstance()->getActionForOption(MenuOption::VoxelAddMode), 0, 0);
@ -2314,7 +2315,7 @@ void Application::update(float deltaTime) {
updateCursor(deltaTime); // Handle cursor updates
_particles.update(); // update the particles...
_particleCollisionSystem.update(); // handle collisions for the particles...
_particleCollisionSystem.update(); // collide the particles...
}
void Application::updateAvatar(float deltaTime) {

View file

@ -16,17 +16,30 @@
#include "AvatarManager.h"
// We add _myAvatar into the hash with all the other AvatarData, and we use the default NULL QUid as the key.
const QUuid MY_AVATAR_KEY; // NULL key
AvatarManager::AvatarManager(QObject* parent) :
_lookAtTargetAvatar(),
_lookAtOtherPosition(),
_lookAtIndicatorScale(1.0f),
_avatarHash(),
_avatarFades()
_avatarFades(),
_myAvatar(NULL)
{
// register a meta type for the weak pointer we'll use for the owning avatar mixer for each avatar
qRegisterMetaType<QWeakPointer<Node> >("NodeWeakPointer");
}
void AvatarManager::setMyAvatar(MyAvatar* myAvatar) {
if (!_myAvatar) {
// can only ever set this once
_myAvatar = myAvatar;
// add _myAvatar to the list
AvatarSharedPointer myPointer = AvatarSharedPointer(_myAvatar);
_avatarHash.insert(MY_AVATAR_KEY, myPointer);
}
}
void AvatarManager::updateLookAtTargetAvatar(glm::vec3 &eyePosition) {
bool showWarnings = Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings);
PerformanceWarning warn(showWarnings, "Application::updateLookatTargetAvatar()");
@ -34,22 +47,26 @@ void AvatarManager::updateLookAtTargetAvatar(glm::vec3 &eyePosition) {
Application* applicationInstance = Application::getInstance();
if (!applicationInstance->isMousePressed()) {
foreach (const AvatarSharedPointer& avatar, _avatarHash) {
float distance;
if (avatar->findRayIntersection(applicationInstance->getMouseRayOrigin(),
applicationInstance->getMouseRayDirection(), distance)) {
// rescale to compensate for head embiggening
eyePosition = (avatar->getHead().calculateAverageEyePosition() - avatar->getHead().getScalePivot()) *
(avatar->getScale() / avatar->getHead().getScale()) + avatar->getHead().getScalePivot();
_lookAtIndicatorScale = avatar->getHead().getScale();
_lookAtOtherPosition = avatar->getHead().getPosition();
_lookAtTargetAvatar = avatar;
// found the look at target avatar, return
return;
glm::vec3 mouseOrigin = applicationInstance->getMouseRayOrigin();
glm::vec3 mouseDirection = applicationInstance->getMouseRayDirection();
foreach (const AvatarSharedPointer& avatarPointer, _avatarHash) {
Avatar* avatar = static_cast<Avatar*>(avatarPointer.data());
if (avatar != static_cast<Avatar*>(_myAvatar)) {
float distance;
if (avatar->findRayIntersection(mouseOrigin, mouseDirection, distance)) {
// rescale to compensate for head embiggening
eyePosition = (avatar->getHead().calculateAverageEyePosition() - avatar->getHead().getScalePivot()) *
(avatar->getScale() / avatar->getHead().getScale()) + avatar->getHead().getScalePivot();
_lookAtIndicatorScale = avatar->getHead().getScale();
_lookAtOtherPosition = avatar->getHead().getPosition();
_lookAtTargetAvatar = avatarPointer;
// found the look at target avatar, return
return;
}
}
}
@ -61,21 +78,28 @@ void AvatarManager::updateAvatars(float deltaTime) {
bool showWarnings = Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings);
PerformanceWarning warn(showWarnings, "Application::updateAvatars()");
Application* applicationInstance = Application::getInstance();
glm::vec3 mouseOrigin = applicationInstance->getMouseRayOrigin();
glm::vec3 mouseDirection = applicationInstance->getMouseRayDirection();
// simulate avatars
AvatarHash::iterator avatar = _avatarHash.begin();
while (avatar != _avatarHash.end()) {
if (avatar->data()->getOwningAvatarMixer()) {
AvatarHash::iterator avatarIterator = _avatarHash.begin();
while (avatarIterator != _avatarHash.end()) {
Avatar* avatar = static_cast<Avatar*>(avatarIterator.value().data());
if (avatar == static_cast<Avatar*>(_myAvatar)) {
// for now skip updates to _myAvatar because it is done explicitly in Application
// TODO: update _myAvatar in this context
++avatarIterator;
continue;
}
if (avatar->getOwningAvatarMixer()) {
// this avatar's mixer is still around, go ahead and simulate it
avatar->data()->simulate(deltaTime, NULL);
Application* applicationInstance = Application::getInstance();
avatar->data()->setMouseRay(applicationInstance->getMouseRayOrigin(),
applicationInstance->getMouseRayDirection());
avatar++;
avatar->simulate(deltaTime, NULL);
avatar->setMouseRay(mouseOrigin, mouseDirection);
++avatarIterator;
} else {
// the mixer that owned this avatar is gone, give it to the vector of fades and kill it
avatar = removeAvatarAtHashIterator(avatar);
avatarIterator = erase(avatarIterator);
}
}
@ -89,41 +113,44 @@ void AvatarManager::renderAvatars(bool forceRenderHead, bool selfAvatarOnly) {
}
PerformanceWarning warn(Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings),
"Application::renderAvatars()");
bool renderLookAtVectors = Menu::getInstance()->isOptionChecked(MenuOption::LookAtVectors);
if (!selfAvatarOnly) {
// Render avatars of other nodes
foreach (const AvatarSharedPointer& avatar, _avatarHash) {
foreach (const AvatarSharedPointer& avatarPointer, _avatarHash) {
Avatar* avatar = static_cast<Avatar*>(avatarPointer.data());
if (!avatar->isInitialized()) {
avatar->init();
}
avatar->render(false);
avatar->setDisplayingLookatVectors(Menu::getInstance()->isOptionChecked(MenuOption::LookAtVectors));
if (avatar == static_cast<Avatar*>(_myAvatar)) {
avatar->render(forceRenderHead);
} else {
avatar->render(false);
}
avatar->setDisplayingLookatVectors(renderLookAtVectors);
}
renderAvatarFades();
} else if (_myAvatar) {
// Render my own Avatar
_myAvatar->render(forceRenderHead);
_myAvatar->setDisplayingLookatVectors(renderLookAtVectors);
}
// Render my own Avatar
Avatar* myAvatar = Application::getInstance()->getAvatar();
myAvatar->render(forceRenderHead);
myAvatar->setDisplayingLookatVectors(Menu::getInstance()->isOptionChecked(MenuOption::LookAtVectors));
}
void AvatarManager::simulateAvatarFades(float deltaTime) {
QVector<AvatarSharedPointer>::iterator fadingAvatar = _avatarFades.begin();
QVector<AvatarSharedPointer>::iterator fadingIterator = _avatarFades.begin();
while (fadingAvatar != _avatarFades.end()) {
const float SHRINK_RATE = 0.9f;
fadingAvatar->data()->setTargetScale(fadingAvatar->data()->getScale() * SHRINK_RATE);
const float MIN_FADE_SCALE = 0.001f;
if (fadingAvatar->data()->getTargetScale() < MIN_FADE_SCALE) {
fadingAvatar = _avatarFades.erase(fadingAvatar);
const float SHRINK_RATE = 0.9f;
const float MIN_FADE_SCALE = 0.001f;
while (fadingIterator != _avatarFades.end()) {
Avatar* avatar = static_cast<Avatar*>(fadingIterator->data());
avatar->setTargetScale(avatar->getScale() * SHRINK_RATE);
if (avatar->getTargetScale() < MIN_FADE_SCALE) {
fadingIterator = _avatarFades.erase(fadingIterator);
} else {
fadingAvatar->data()->simulate(deltaTime, NULL);
avatar->simulate(deltaTime, NULL);
++fadingIterator;
}
}
}
@ -133,29 +160,36 @@ void AvatarManager::renderAvatarFades() {
Glower glower;
foreach(const AvatarSharedPointer& fadingAvatar, _avatarFades) {
fadingAvatar->render(false);
Avatar* avatar = static_cast<Avatar*>(fadingAvatar.data());
avatar->render(false);
}
}
void AvatarManager::processDataServerResponse(const QString& userString, const QStringList& keyList,
const QStringList &valueList) {
QUuid avatarKey = QUuid(userString);
if (avatarKey == MY_AVATAR_KEY) {
// ignore updates to our own mesh
return;
}
for (int i = 0; i < keyList.size(); i++) {
if (valueList[i] != " ") {
if (keyList[i] == DataServerKey::FaceMeshURL || keyList[i] == DataServerKey::SkeletonURL) {
// mesh URL for a UUID, find avatar in our list
AvatarSharedPointer matchingAvatar = _avatarHash.value(QUuid(userString));
AvatarSharedPointer matchingAvatar = _avatarHash.value(avatarKey);
if (matchingAvatar) {
Avatar* avatar = static_cast<Avatar*>(matchingAvatar.data());
if (keyList[i] == DataServerKey::FaceMeshURL) {
qDebug() << "Changing mesh to" << valueList[i] << "for avatar with UUID"
<< uuidStringWithoutCurlyBraces(QUuid(userString));
<< uuidStringWithoutCurlyBraces(avatarKey);
QMetaObject::invokeMethod(&matchingAvatar->getHead().getFaceModel(),
QMetaObject::invokeMethod(&(avatar->getHead().getFaceModel()),
"setURL", Q_ARG(QUrl, QUrl(valueList[i])));
} else if (keyList[i] == DataServerKey::SkeletonURL) {
qDebug() << "Changing skeleton to" << valueList[i] << "for avatar with UUID"
<< uuidStringWithoutCurlyBraces(QString(userString));
<< uuidStringWithoutCurlyBraces(avatarKey.toString());
QMetaObject::invokeMethod(&matchingAvatar->getSkeletonModel(),
QMetaObject::invokeMethod(&(avatar->getSkeletonModel()),
"setURL", Q_ARG(QUrl, QUrl(valueList[i])));
}
}
@ -179,15 +213,17 @@ void AvatarManager::processAvatarMixerDatagram(const QByteArray& datagram, const
// only add them if mixerWeakPointer points to something (meaning that mixer is still around)
while (bytesRead < datagram.size() && mixerWeakPointer.data()) {
QUuid nodeUUID = QUuid::fromRfc4122(datagram.mid(bytesRead, NUM_BYTES_RFC4122_UUID));
// TODO: skip the data if nodeUUID is same as MY_AVATAR_KEY
AvatarSharedPointer matchingAvatar = _avatarHash.value(nodeUUID);
if (!matchingAvatar) {
// construct a new Avatar for this node
matchingAvatar = AvatarSharedPointer(new Avatar());
matchingAvatar->setOwningAvatarMixer(mixerWeakPointer);
Avatar* avatar = new Avatar();
avatar->setOwningAvatarMixer(mixerWeakPointer);
// insert the new avatar into our hash
matchingAvatar = AvatarSharedPointer(avatar);
_avatarHash.insert(nodeUUID, matchingAvatar);
// new UUID requires mesh and skeleton request to data-server
@ -214,21 +250,26 @@ void AvatarManager::processKillAvatar(const QByteArray& datagram) {
// remove the avatar with that UUID from our hash, if it exists
AvatarHash::iterator matchedAvatar = _avatarHash.find(nodeUUID);
if (matchedAvatar != _avatarHash.end()) {
removeAvatarAtHashIterator(matchedAvatar);
erase(matchedAvatar);
}
}
AvatarHash::iterator AvatarManager::removeAvatarAtHashIterator(const AvatarHash::iterator& iterator) {
qDebug() << "Removing Avatar with UUID" << iterator.key() << "from AvatarManager hash.";
_avatarFades.push_back(iterator.value());
return _avatarHash.erase(iterator);
AvatarHash::iterator AvatarManager::erase(const AvatarHash::iterator& iterator) {
if (iterator.key() != MY_AVATAR_KEY) {
qDebug() << "Removing Avatar with UUID" << iterator.key() << "from AvatarManager hash.";
_avatarFades.push_back(iterator.value());
return AvatarHashMap::erase(iterator);
} else {
// never remove _myAvatar from the list
AvatarHash::iterator returnIterator = iterator;
return ++returnIterator;
}
}
void AvatarManager::clearHash() {
// clear the AvatarManager hash - typically happens on the removal of the avatar-mixer
AvatarHash::iterator removeAvatar = _avatarHash.begin();
while (removeAvatar != _avatarHash.end()) {
removeAvatar = removeAvatarAtHashIterator(removeAvatar);
removeAvatar = erase(removeAvatar);
}
}

View file

@ -13,30 +13,30 @@
#include <QtCore/QObject>
#include <QtCore/QSharedPointer>
#include <AvatarHashMap.h>
#include <DataServerClient.h>
#include "Avatar.h"
typedef QSharedPointer<Avatar> AvatarSharedPointer;
typedef QHash<QUuid, AvatarSharedPointer> AvatarHash;
class MyAvatar;
class AvatarManager : public QObject, public DataServerCallbackObject {
class AvatarManager : public QObject, public DataServerCallbackObject, public AvatarHashMap {
Q_OBJECT
public:
AvatarManager(QObject* parent = 0);
void setMyAvatar(MyAvatar* myAvatar);
const AvatarHash& getAvatarHash() { return _avatarHash; }
int size() const { return _avatarHash.size(); }
Avatar* getLookAtTargetAvatar() const { return _lookAtTargetAvatar.data(); }
AvatarData* getLookAtTargetAvatar() const { return _lookAtTargetAvatar.data(); }
void updateLookAtTargetAvatar(glm::vec3& eyePosition);
void updateAvatars(float deltaTime);
void renderAvatars(bool forceRenderHead, bool selfAvatarOnly = false);
// virtual override
void clearHash();
public slots:
void processDataServerResponse(const QString& userString, const QStringList& keyList, const QStringList& valueList);
@ -44,17 +44,20 @@ public slots:
void processKillAvatar(const QByteArray& datagram);
private:
AvatarManager(const AvatarManager& other);
void simulateAvatarFades(float deltaTime);
void renderAvatarFades();
AvatarHash::iterator removeAvatarAtHashIterator(const AvatarHash::iterator& iterator);
// virtual override
AvatarHash::iterator erase(const AvatarHash::iterator& iterator);
QWeakPointer<Avatar> _lookAtTargetAvatar;
QWeakPointer<AvatarData> _lookAtTargetAvatar;
glm::vec3 _lookAtOtherPosition;
float _lookAtIndicatorScale;
AvatarHash _avatarHash;
QVector<AvatarSharedPointer> _avatarFades;
MyAvatar* _myAvatar;
};
#endif /* defined(__hifi__AvatarManager__) */

View file

@ -183,7 +183,12 @@ void Hand::updateCollisions() {
glm::vec3 totalPenetration;
// check other avatars
foreach (const AvatarSharedPointer& avatar, Application::getInstance()->getAvatarManager().getAvatarHash()) {
foreach (const AvatarSharedPointer& avatarPointer, Application::getInstance()->getAvatarManager().getAvatarHash()) {
Avatar* avatar = static_cast<Avatar*>(avatarPointer.data());
if (avatar == _owningAvatar) {
// don't collid with our own hands
continue;
}
if (Menu::getInstance()->isOptionChecked(MenuOption::PlaySlaps)) {
// Check for palm collisions
glm::vec3 myPalmPosition = palm.getPosition();
@ -205,9 +210,9 @@ void Hand::updateCollisions() {
const float PALM_COLLIDE_DURATION_MAX = 0.75f;
const float PALM_COLLIDE_DECAY_PER_SAMPLE = 0.01f;
Application::getInstance()->getAudio()->startDrumSound(PALM_COLLIDE_VOLUME,
PALM_COLLIDE_FREQUENCY,
PALM_COLLIDE_DURATION_MAX,
PALM_COLLIDE_DECAY_PER_SAMPLE);
PALM_COLLIDE_FREQUENCY,
PALM_COLLIDE_DURATION_MAX,
PALM_COLLIDE_DECAY_PER_SAMPLE);
// If the other person's palm is in motion, move mine downward to show I was hit
const float MIN_VELOCITY_FOR_SLAP = 0.05f;
if (glm::length(otherPalm.getVelocity()) > MIN_VELOCITY_FOR_SLAP) {

View file

@ -791,14 +791,15 @@ void MyAvatar::updateChatCircle(float deltaTime) {
// find all circle-enabled members and sort by distance
QVector<SortedAvatar> sortedAvatars;
foreach (const AvatarSharedPointer& avatar, Application::getInstance()->getAvatarManager().getAvatarHash()) {
SortedAvatar sortedAvatar;
sortedAvatar.avatar = avatar.data();
if (!sortedAvatar.avatar->isChatCirclingEnabled()) {
foreach (const AvatarSharedPointer& avatarPointer, Application::getInstance()->getAvatarManager().getAvatarHash()) {
Avatar* avatar = static_cast<Avatar*>(avatarPointer.data());
if ( ! avatar->isChatCirclingEnabled() ||
avatar == static_cast<Avatar*>(this)) {
continue;
}
SortedAvatar sortedAvatar;
sortedAvatar.avatar = avatar;
sortedAvatar.distance = glm::distance(_position, sortedAvatar.avatar->getPosition());
sortedAvatars.append(sortedAvatar);
}

View file

@ -9,9 +9,9 @@
#ifndef __hifi__Snapshot__
#define __hifi__Snapshot__
#import <QString>
#import <QImage>
#import <QGLWidget>
#include <QString>
#include <QImage>
#include <QGLWidget>
#include <glm/glm.hpp>

View file

@ -0,0 +1,30 @@
//
// AvatarHashMap.cpp
// hifi
//
// Created by Stephen AndrewMeadows on 1/28/2014.
// Copyright (c) 2014 HighFidelity, Inc. All rights reserved.
//
#include "AvatarHashMap.h"
AvatarHashMap::AvatarHashMap() :
_avatarHash()
{
}
void AvatarHashMap::insert(const QUuid& id, AvatarSharedPointer avatar) {
_avatarHash.insert(id, avatar);
}
void AvatarHashMap::clearHash() {
AvatarHash::iterator removeAvatar = _avatarHash.begin();
while (removeAvatar != _avatarHash.end()) {
removeAvatar = erase(removeAvatar);
}
}
AvatarHash::iterator AvatarHashMap::erase(const AvatarHash::iterator& iterator) {
return _avatarHash.erase(iterator);
}

View file

@ -0,0 +1,37 @@
//
// AvatarHashMap.h
// hifi
//
// Created by Stephen AndrewMeadows on 1/28/2014.
// Copyright (c) 2014 HighFidelity, Inc. All rights reserved.
//
#ifndef __hifi__AvatarHashMap__
#define __hifi__AvatarHashMap__
#include <QtCore/QHash>
#include <QtCore/QSharedPointer>
#include <QtCore/QUuid>
#include "AvatarData.h"
typedef QSharedPointer<AvatarData> AvatarSharedPointer;
typedef QHash<QUuid, AvatarSharedPointer> AvatarHash;
class AvatarHashMap {
public:
AvatarHashMap();
const AvatarHash& getAvatarHash() { return _avatarHash; }
int size() const { return _avatarHash.size(); }
virtual void insert(const QUuid& id, AvatarSharedPointer avatar);
virtual void clearHash();
protected:
virtual AvatarHash::iterator erase(const AvatarHash::iterator& iterator);
AvatarHash _avatarHash;
};
#endif /* defined(__hifi__AvatarHashMap__) */

View file

@ -117,6 +117,13 @@ bool AABox::contains(const AABox& otherBox) const {
return true;
}
bool AABox::touches(const AABox& otherBox) const {
glm::vec3 relativeCenter = _corner - otherBox._corner + (glm::vec3(_scale - otherBox._scale) * 0.5f);
float totalHalfScale = 0.5f * (_scale + otherBox._scale);
return fabs(relativeCenter.x) <= totalHalfScale &&
fabs(relativeCenter.y) <= totalHalfScale &&
fabs(relativeCenter.z) <= totalHalfScale;
}
// determines whether a value is within the expanded extents
static bool isWithinExpanded(float value, float corner, float size, float expansion) {

View file

@ -61,6 +61,7 @@ public:
bool contains(const glm::vec3& point) const;
bool contains(const AABox& otherBox) const;
bool touches(const AABox& otherBox) const;
bool expandedContains(const glm::vec3& point, float expansion) const;
bool expandedIntersectsSegment(const glm::vec3& start, const glm::vec3& end, float expansion) const;
bool findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, float& distance, BoxFace& face) const;

View file

@ -661,7 +661,50 @@ void Particle::adjustEditPacketForClockSkew(unsigned char* codeColorBuffer, ssiz
}
}
// HALTING_* params are determined using expected acceleration of gravity over some timescale.
// This is a HACK for particles that bounce in a 1.0 gravitational field and should eventually be made more universal.
const float HALTING_PARTICLE_PERIOD = 0.0167f; // ~1/60th of a second
const float HALTING_PARTICLE_SPEED = 9.8 * HALTING_PARTICLE_PERIOD / (float)(TREE_SCALE);
void Particle::applyHardCollision(const CollisionInfo& collisionInfo) {
//
// Update the particle 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 = getPosition();
glm::vec3 velocity = getVelocity();
const float EPSILON = 0.0f;
glm::vec3 relativeVelocity = collisionInfo._addedVelocity - velocity;
float velocityDotPenetration = glm::dot(relativeVelocity, collisionInfo._penetration);
if (velocityDotPenetration < EPSILON) {
// particle is moving into collision surface
//
// TODO: do something smarter here by comparing the mass of the particle 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_PARTICLE_SPEED) {
// static friction kicks in and particle 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
}
}
// change the local particle too...
setPosition(position);
setVelocity(velocity);
}
// MIN_VALID_SPEED is obtained by computing speed gained at one gravity during the shortest expected frame period
// This is a HACK for particles that bounce in a 1.0 gravitational field and should eventually be made more universal.
const float MIN_EXPECTED_FRAME_PERIOD = 0.005f; // 1/200th of a second
const float MIN_VALID_SPEED = 9.8 * MIN_EXPECTED_FRAME_PERIOD / (float)(TREE_SCALE);

View file

@ -16,6 +16,7 @@
#include <QtScript/QScriptEngine>
#include <QtCore/QObject>
#include <CollisionInfo.h>
#include <SharedUtil.h>
#include <OctreePacketData.h>
@ -246,6 +247,8 @@ public:
static void adjustEditPacketForClockSkew(unsigned char* codeColorBuffer, ssize_t length, int clockSkew);
void applyHardCollision(const CollisionInfo& collisionInfo);
void update(const uint64_t& now);
void collisionWithParticle(Particle* other);
void collisionWithVoxel(VoxelDetail* voxel);

View file

@ -20,17 +20,19 @@
#include "ParticleTree.h"
ParticleCollisionSystem::ParticleCollisionSystem(ParticleEditPacketSender* packetSender,
ParticleTree* particles, VoxelTree* voxels, AbstractAudioInterface* audio, AvatarData* selfAvatar) {
init(packetSender, particles, voxels, audio, selfAvatar);
ParticleTree* particles, VoxelTree* voxels, AbstractAudioInterface* audio,
AvatarHashMap* avatars) {
init(packetSender, particles, voxels, audio, avatars);
}
void ParticleCollisionSystem::init(ParticleEditPacketSender* packetSender,
ParticleTree* particles, VoxelTree* voxels, AbstractAudioInterface* audio, AvatarData* selfAvatar) {
ParticleTree* particles, VoxelTree* voxels, AbstractAudioInterface* audio,
AvatarHashMap* avatars) {
_packetSender = packetSender;
_particles = particles;
_voxels = voxels;
_audio = audio;
_selfAvatar = selfAvatar;
_avatars = avatars;
}
ParticleCollisionSystem::~ParticleCollisionSystem() {
@ -73,6 +75,8 @@ void ParticleCollisionSystem::updateCollisionWithVoxels(Particle* particle) {
const float DAMPING = 0.05f;
const float COLLISION_FREQUENCY = 0.5f;
CollisionInfo collisionInfo;
collisionInfo._damping = DAMPING;
collisionInfo._elasticity = ELASTICITY;
VoxelDetail* voxelDetails = NULL;
if (_voxels->findSpherePenetration(center, radius, collisionInfo._penetration, (void**)&voxelDetails)) {
@ -81,7 +85,8 @@ void ParticleCollisionSystem::updateCollisionWithVoxels(Particle* particle) {
updateCollisionSound(particle, collisionInfo._penetration, COLLISION_FREQUENCY);
collisionInfo._penetration /= (float)(TREE_SCALE);
applyHardCollision(particle, ELASTICITY, DAMPING, collisionInfo);
particle->applyHardCollision(collisionInfo);
queueParticlePropertiesUpdate(particle);
delete voxelDetails; // cleanup returned details
}
@ -144,9 +149,8 @@ const float MIN_EXPECTED_FRAME_PERIOD = 0.0167f; // 1/60th of a second
const float HALTING_SPEED = 9.8 * MIN_EXPECTED_FRAME_PERIOD / (float)(TREE_SCALE);
void ParticleCollisionSystem::updateCollisionWithAvatars(Particle* particle) {
// particles that are in hand, don't collide with avatars
if (particle->getInHand()) {
if (!_avatars || particle->getInHand()) {
return;
}
@ -157,10 +161,11 @@ void ParticleCollisionSystem::updateCollisionWithAvatars(Particle* particle) {
const float COLLISION_FREQUENCY = 0.5f;
glm::vec3 penetration;
// first check the selfAvatar if set...
if (_selfAvatar) {
AvatarData* avatar = (AvatarData*)_selfAvatar;
foreach (const AvatarSharedPointer& avatarPointer, _avatars->getAvatarHash()) {
AvatarData* avatar = avatarPointer.data();
CollisionInfo collisionInfo;
collisionInfo._damping = DAMPING;
collisionInfo._elasticity = ELASTICITY;
if (avatar->findSphereCollision(center, radius, collisionInfo)) {
collisionInfo._addedVelocity /= (float)(TREE_SCALE);
glm::vec3 relativeVelocity = collisionInfo._addedVelocity - particle->getVelocity();
@ -185,90 +190,21 @@ void ParticleCollisionSystem::updateCollisionWithAvatars(Particle* particle) {
updateCollisionSound(particle, collisionInfo._penetration, COLLISION_FREQUENCY);
collisionInfo._penetration /= (float)(TREE_SCALE);
applyHardCollision(particle, elasticity, damping, collisionInfo);
particle->applyHardCollision(collisionInfo);
queueParticlePropertiesUpdate(particle);
}
}
}
// loop through all the other avatars for potential interactions...
// foreach (const SharedNodePointer& node, NodeList::getInstance()->getNodeHash()) {
// //qDebug() << "updateCollisionWithAvatars()... node:" << *node << "\n";
// if (node->getLinkedData() && node->getType() == NODE_TYPE_AGENT) {
// AvatarData* avatar = static_cast<AvatarData*>(node->getLinkedData());
// CollisionInfo collisionInfo;
// if (avatar->findSphereCollision(center, radius, collisionInfo)) {
// collisionInfo._addedVelocity /= (float)(TREE_SCALE);
// glm::vec3 relativeVelocity = collisionInfo._addedVelocity - particle->getVelocity();
// if (glm::dot(relativeVelocity, collisionInfo._penetration) < 0.f) {
// // 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);
// }
// // HACK END
//
// updateCollisionSound(particle, collisionInfo._penetration, COLLISION_FREQUENCY);
// collisionInfo._penetration /= (float)(TREE_SCALE);
// applyHardCollision(particle, ELASTICITY, damping, collisionInfo);
// }
// }
// }
// }
}
// TODO: convert applyHardCollision() to take a CollisionInfo& instead of penetration + addedVelocity
void ParticleCollisionSystem::applyHardCollision(Particle* particle, float elasticity, float damping, const CollisionInfo& collisionInfo) {
//
// Update the particle 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 = particle->getPosition();
glm::vec3 velocity = particle->getVelocity();
const float EPSILON = 0.0f;
glm::vec3 relativeVelocity = collisionInfo._addedVelocity - velocity;
float velocityDotPenetration = glm::dot(relativeVelocity, collisionInfo._penetration);
if (velocityDotPenetration < EPSILON) {
// particle is moving into collision surface
position -= collisionInfo._penetration;
if (glm::length(relativeVelocity) < HALTING_SPEED) {
// static friction kicks in and particle moves with colliding object
velocity = collisionInfo._addedVelocity;
} else {
glm::vec3 direction = glm::normalize(collisionInfo._penetration);
velocity += glm::dot(relativeVelocity, direction) * (1.0f + elasticity) * direction; // dynamic reflection
velocity += glm::clamp(damping, 0.0f, 1.0f) * (relativeVelocity - glm::dot(relativeVelocity, direction) * direction); // dynamic friction
}
}
const bool wantDebug = false;
if (wantDebug) {
printf("ParticleCollisionSystem::applyHardCollision() particle id:%d new velocity:%f,%f,%f inHand:%s\n",
particle->getID(), velocity.x, velocity.y, velocity.z, debug::valueOf(particle->getInHand()));
}
// send off the result to the particle server
void ParticleCollisionSystem::queueParticlePropertiesUpdate(Particle* particle) {
// queue the result for sending to the particle server
ParticleProperties properties;
ParticleID particleID(particle->getID());
properties.copyFromParticle(*particle);
properties.setPosition(position * (float)TREE_SCALE);
properties.setVelocity(velocity * (float)TREE_SCALE);
properties.setPosition(particle->getPosition() * (float)TREE_SCALE);
properties.setVelocity(particle->getVelocity() * (float)TREE_SCALE);
_packetSender->queueParticleEditMessage(PACKET_TYPE_PARTICLE_ADD_OR_EDIT, particleID, properties);
// change the local particle too...
particle->setPosition(position);
particle->setVelocity(velocity);
}

View file

@ -16,6 +16,7 @@
#include <QtScript/QScriptEngine>
#include <QtCore/QObject>
#include <AvatarHashMap.h>
#include <CollisionInfo.h>
#include <SharedUtil.h>
#include <OctreePacketData.h>
@ -33,21 +34,21 @@ const glm::vec3 NO_ADDED_VELOCITY = glm::vec3(0);
class ParticleCollisionSystem {
public:
ParticleCollisionSystem(ParticleEditPacketSender* packetSender = NULL, ParticleTree* particles = NULL,
VoxelTree* voxels = NULL,
AbstractAudioInterface* audio = NULL,
AvatarData* selfAvatar = NULL);
VoxelTree* voxels = NULL, AbstractAudioInterface* audio = NULL,
AvatarHashMap* avatars = NULL);
void init(ParticleEditPacketSender* packetSender, ParticleTree* particles, VoxelTree* voxels,
AbstractAudioInterface* audio = NULL, AvatarData* selfAvatar = NULL);
AbstractAudioInterface* audio = NULL, AvatarHashMap* _avatars = NULL);
~ParticleCollisionSystem();
void update();
void checkParticle(Particle* particle);
void updateCollisionWithVoxels(Particle* particle);
void updateCollisionWithParticles(Particle* particle);
void updateCollisionWithAvatars(Particle* particle);
void applyHardCollision(Particle* particle, float elasticity, float damping, const CollisionInfo& collisionInfo);
void queueParticlePropertiesUpdate(Particle* particle);
void updateCollisionSound(Particle* particle, const glm::vec3 &penetration, float frequency);
private:
@ -58,7 +59,7 @@ private:
ParticleTree* _particles;
VoxelTree* _voxels;
AbstractAudioInterface* _audio;
AvatarData* _selfAvatar;
AvatarHashMap* _avatars;
};
#endif /* defined(__hifi__ParticleCollisionSystem__) */

View file

@ -170,16 +170,14 @@ public:
bool ParticleTree::findInSphereOperation(OctreeElement* element, void* extraData) {
FindAllNearPointArgs* args = static_cast<FindAllNearPointArgs*>(extraData);
ParticleTreeElement* particleTreeElement = static_cast<ParticleTreeElement*>(element);
glm::vec3 penetration;
bool sphereIntersection = particleTreeElement->getAABox().findSpherePenetration(args->position,
bool sphereIntersection = element->getAABox().findSpherePenetration(args->position,
args->targetRadius, penetration);
// If this particleTreeElement contains the point, then search it...
// If this element contains the point, then search it...
if (sphereIntersection) {
QVector<const Particle*> moreParticles = particleTreeElement->getParticles(args->position, args->targetRadius);
args->particles << moreParticles;
ParticleTreeElement* particleTreeElement = static_cast<ParticleTreeElement*>(element);
particleTreeElement->getParticles(args->position, args->targetRadius, args->particles);
return true; // keep searching in case children have closer particles
}
@ -187,13 +185,43 @@ bool ParticleTree::findInSphereOperation(OctreeElement* element, void* extraData
return false;
}
QVector<const Particle*> ParticleTree::findParticles(const glm::vec3& center, float radius) {
QVector<Particle*> result;
void ParticleTree::findParticles(const glm::vec3& center, float radius, QVector<const Particle*>& foundParticles) {
FindAllNearPointArgs args = { center, radius };
lockForRead();
recurseTreeWithOperation(findInSphereOperation, &args);
unlock();
return args.particles;
// swap the two lists of particle pointers instead of copy
foundParticles.swap(args.particles);
}
class FindParticlesInBoxArgs {
public:
FindParticlesInBoxArgs(const AABox& box)
: _box(box), _foundParticles() {
}
AABox _box;
QVector<Particle*> _foundParticles;
};
bool findInBoxForUpdateOperation(OctreeElement* element, void* extraData) {
FindParticlesInBoxArgs* args = static_cast< FindParticlesInBoxArgs*>(extraData);
const AABox& elementBox = element->getAABox();
if (elementBox.touches(args->_box)) {
ParticleTreeElement* particleTreeElement = static_cast<ParticleTreeElement*>(element);
particleTreeElement->getParticlesForUpdate(args->_box, args->_foundParticles);
return true;
}
return false;
}
void ParticleTree::findParticlesForUpdate(const AABox& box, QVector<Particle*> foundParticles) {
FindParticlesInBoxArgs args(box);
lockForRead();
recurseTreeWithOperation(findInBoxForUpdateOperation, &args);
unlock();
// swap the two lists of particle pointers instead of copy
foundParticles.swap(args._foundParticles);
}
class FindByIDArgs {

View file

@ -42,7 +42,19 @@ public:
void storeParticle(const Particle& particle, Node* senderNode = NULL);
const Particle* findClosestParticle(glm::vec3 position, float targetRadius);
const Particle* findParticleByID(uint32_t id, bool alreadyLocked = false);
QVector<const Particle*> findParticles(const glm::vec3& center, float radius);
/// finds all particles that touch a sphere
/// \param center the center of the sphere
/// \param radius the radius of the sphere
/// \param foundParticles[out] vector of const Particle*
/// \remark Side effect: any initial contents in foundParticles will be lost
void findParticles(const glm::vec3& center, float radius, QVector<const Particle*>& foundParticles);
/// finds all particles that touch a box
/// \param box the query box
/// \param foundParticles[out] vector of non-const Particle*
/// \remark Side effect: any initial contents in particles will be lost
void findParticlesForUpdate(const AABox& box, QVector<Particle*> foundParticles);
void addNewlyCreatedHook(NewlyCreatedParticleHook* hook);
void removeNewlyCreatedHook(NewlyCreatedParticleHook* hook);

View file

@ -185,21 +185,34 @@ const Particle* ParticleTreeElement::getClosestParticle(glm::vec3 position) cons
return closestParticle;
}
QVector<const Particle*> ParticleTreeElement::getParticles(glm::vec3 searchPosition, float searchRadius) const {
QVector<const Particle*> results;
void ParticleTreeElement::getParticles(const glm::vec3& searchPosition, float searchRadius, QVector<const Particle*>& foundParticles) const {
uint16_t numberOfParticles = _particles->size();
for (uint16_t i = 0; i < numberOfParticles; i++) {
const Particle* particle = &(*_particles)[i];
glm::vec3 particlePosition = particle->getPosition();
float particleRadius = particle->getRadius();
glm::vec3 penetration;
// check to see that the particle (penetrator) penetrates the search area
if (findSphereSpherePenetration(particlePosition, particleRadius, searchPosition, searchRadius, penetration)) {
results << particle;
float distance = glm::length(particle->getPosition() - searchPosition);
if (distance < searchRadius + particle->getRadius()) {
foundParticles.push_back(particle);
}
}
return results;
}
void ParticleTreeElement::getParticlesForUpdate(const AABox& box, QVector<Particle*>& foundParticles) {
QList<Particle>::iterator particleItr = _particles->begin();
QList<Particle>::iterator particleEnd = _particles->end();
AABox particleBox;
while(particleItr != particleEnd) {
Particle* particle = &(*particleItr);
float radius = particle->getRadius();
// NOTE: we actually do box-box collision queries here, which is sloppy but good enough for now
// TODO: decide whether to replace particleBox-box query with sphere-box (requires a square root
// but will be slightly more accurate).
particleBox.setBox(particle->getPosition() - glm::vec3(radius), 2.f * radius);
if (particleBox.touches(_box)) {
foundParticles.push_back(particle);
}
++particleItr;
}
}
const Particle* ParticleTreeElement::getParticleWithID(uint32_t id) const {
@ -228,8 +241,6 @@ bool ParticleTreeElement::removeParticleWithID(uint32_t id) {
return foundParticle;
}
int ParticleTreeElement::readElementDataFromBuffer(const unsigned char* data, int bytesLeftToRead,
ReadBitstreamToTreeParams& args) {

View file

@ -89,12 +89,22 @@ public:
bool containsParticle(const Particle& particle) const;
bool updateParticle(const Particle& particle);
const Particle* getClosestParticle(glm::vec3 position) const;
QVector<const Particle*> getParticles(glm::vec3 position, float radius) const;
/// finds all particles that touch a sphere
/// \param position the center of the query sphere
/// \param radius the radius of the query sphere
/// \param particles[out] vector of const Particle*
void getParticles(const glm::vec3& position, float radius, QVector<const Particle*>& foundParticles) const;
/// finds all particles that touch a box
/// \param box the query box
/// \param particles[out] vector of non-const Particle*
void getParticlesForUpdate(const AABox& box, QVector<Particle*>& foundParticles);
const Particle* getParticleWithID(uint32_t id) const;
bool removeParticleWithID(uint32_t id);
protected:
virtual void init(unsigned char * octalCode);

View file

@ -150,7 +150,8 @@ ParticleID ParticlesScriptingInterface::findClosestParticle(const glm::vec3& cen
QVector<ParticleID> ParticlesScriptingInterface::findParticles(const glm::vec3& center, float radius) const {
QVector<ParticleID> result;
if (_particleTree) {
QVector<const Particle*> particles = _particleTree->findParticles(center/(float)TREE_SCALE, radius/(float)TREE_SCALE);
QVector<const Particle*> particles;
_particleTree->findParticles(center/(float)TREE_SCALE, radius/(float)TREE_SCALE, particles);
foreach (const Particle* particle, particles) {
ParticleID thisParticleID(particle->getID(), UNKNOWN_TOKEN, true);

View file

@ -13,11 +13,19 @@
class CollisionInfo {
public:
CollisionInfo() : _penetration(0.f), _addedVelocity(0.f) { }
CollisionInfo()
: _damping(0.f),
_elasticity(1.f),
_penetration(0.f),
_addedVelocity(0.f) {
}
~CollisionInfo() {}
//glm::vec3 _point;
//glm::vec3 _normal;
float _damping;
float _elasticity;
glm::vec3 _penetration; // depth that bodyA is penetrates bodyB
glm::vec3 _addedVelocity;
};