mirror of
https://github.com/overte-org/overte.git
synced 2025-04-21 17:03:58 +02:00
Merge branch 'master' of https://github.com/worklist/hifi into more_scripting
Conflicts: libraries/particles/src/ParticlesScriptingInterface.cpp
This commit is contained in:
commit
9d87d58723
31 changed files with 649 additions and 398 deletions
|
@ -25,5 +25,5 @@ void main(void) {
|
|||
|
||||
// modulate texture by base color and add specular contribution
|
||||
gl_FragColor = base * texture2D(diffuseMap, gl_TexCoord[0].st) +
|
||||
pow(specular, gl_FrontMaterial.shininess) * gl_FrontLightProduct[0].specular;
|
||||
vec4(pow(specular, gl_FrontMaterial.shininess) * gl_FrontLightProduct[0].specular.rgb, 0.0);
|
||||
}
|
||||
|
|
|
@ -37,5 +37,5 @@ void main(void) {
|
|||
|
||||
// modulate texture by base color and add specular contribution
|
||||
gl_FragColor = base * texture2D(diffuseMap, gl_TexCoord[0].st) +
|
||||
pow(specular, gl_FrontMaterial.shininess) * gl_FrontLightProduct[0].specular;
|
||||
vec4(pow(specular, gl_FrontMaterial.shininess) * gl_FrontLightProduct[0].specular.rgb, 0.0);
|
||||
}
|
||||
|
|
|
@ -1830,6 +1830,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);
|
||||
|
@ -1876,7 +1877,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);
|
||||
|
@ -2390,7 +2391,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) {
|
||||
|
|
|
@ -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,20 +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();
|
||||
if (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->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);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -88,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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -132,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])));
|
||||
}
|
||||
}
|
||||
|
@ -178,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
|
||||
|
@ -213,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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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__) */
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -508,3 +508,17 @@ void NetworkGeometry::maybeReadModelWithMapping() {
|
|||
_meshes.append(networkMesh);
|
||||
}
|
||||
}
|
||||
|
||||
bool NetworkMeshPart::isTranslucent() const {
|
||||
return diffuseTexture && diffuseTexture->isTranslucent();
|
||||
}
|
||||
|
||||
int NetworkMesh::getTranslucentPartCount() const {
|
||||
int count = 0;
|
||||
foreach (const NetworkMeshPart& part, parts) {
|
||||
if (part.isTranslucent()) {
|
||||
count++;
|
||||
}
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
|
|
@ -93,6 +93,8 @@ public:
|
|||
|
||||
QSharedPointer<NetworkTexture> diffuseTexture;
|
||||
QSharedPointer<NetworkTexture> normalTexture;
|
||||
|
||||
bool isTranslucent() const;
|
||||
};
|
||||
|
||||
/// The state associated with a single mesh.
|
||||
|
@ -103,6 +105,8 @@ public:
|
|||
GLuint vertexBufferID;
|
||||
|
||||
QVector<NetworkMeshPart> parts;
|
||||
|
||||
int getTranslucentPartCount() const;
|
||||
};
|
||||
|
||||
#endif /* defined(__interface__GeometryCache__) */
|
||||
|
|
|
@ -240,7 +240,6 @@ bool Model::render(float alpha) {
|
|||
|
||||
// set up blended buffer ids on first render after load/simulate
|
||||
const FBXGeometry& geometry = _geometry->getFBXGeometry();
|
||||
const QVector<NetworkMesh>& networkMeshes = _geometry->getMeshes();
|
||||
if (_blendedVertexBufferIDs.isEmpty()) {
|
||||
foreach (const FBXMesh& mesh, geometry.meshes) {
|
||||
GLuint id = 0;
|
||||
|
@ -264,191 +263,28 @@ bool Model::render(float alpha) {
|
|||
|
||||
glDisable(GL_COLOR_MATERIAL);
|
||||
|
||||
// render opaque meshes with alpha testing
|
||||
|
||||
glEnable(GL_ALPHA_TEST);
|
||||
glAlphaFunc(GL_GREATER, 0.5f);
|
||||
|
||||
for (int i = 0; i < networkMeshes.size(); i++) {
|
||||
const NetworkMesh& networkMesh = networkMeshes.at(i);
|
||||
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, networkMesh.indexBufferID);
|
||||
|
||||
const FBXMesh& mesh = geometry.meshes.at(i);
|
||||
int vertexCount = mesh.vertices.size();
|
||||
|
||||
glBindBuffer(GL_ARRAY_BUFFER, networkMesh.vertexBufferID);
|
||||
|
||||
ProgramObject* program = &_program;
|
||||
ProgramObject* skinProgram = &_skinProgram;
|
||||
SkinLocations* skinLocations = &_skinLocations;
|
||||
if (!mesh.tangents.isEmpty()) {
|
||||
program = &_normalMapProgram;
|
||||
skinProgram = &_skinNormalMapProgram;
|
||||
skinLocations = &_skinNormalMapLocations;
|
||||
}
|
||||
|
||||
const MeshState& state = _meshStates.at(i);
|
||||
ProgramObject* activeProgram = program;
|
||||
int tangentLocation = _normalMapTangentLocation;
|
||||
if (state.worldSpaceVertices.isEmpty()) {
|
||||
glPushMatrix();
|
||||
Application::getInstance()->loadTranslatedViewMatrix(_translation);
|
||||
|
||||
if (state.clusterMatrices.size() > 1) {
|
||||
skinProgram->bind();
|
||||
glUniformMatrix4fvARB(skinLocations->clusterMatrices, state.clusterMatrices.size(), false,
|
||||
(const float*)state.clusterMatrices.constData());
|
||||
int offset = (mesh.tangents.size() + mesh.colors.size()) * sizeof(glm::vec3) +
|
||||
mesh.texCoords.size() * sizeof(glm::vec2) +
|
||||
(mesh.blendshapes.isEmpty() ? vertexCount * 2 * sizeof(glm::vec3) : 0);
|
||||
skinProgram->setAttributeBuffer(skinLocations->clusterIndices, GL_FLOAT, offset, 4);
|
||||
skinProgram->setAttributeBuffer(skinLocations->clusterWeights, GL_FLOAT,
|
||||
offset + vertexCount * sizeof(glm::vec4), 4);
|
||||
skinProgram->enableAttributeArray(skinLocations->clusterIndices);
|
||||
skinProgram->enableAttributeArray(skinLocations->clusterWeights);
|
||||
activeProgram = skinProgram;
|
||||
tangentLocation = skinLocations->tangent;
|
||||
|
||||
} else {
|
||||
glMultMatrixf((const GLfloat*)&state.clusterMatrices[0]);
|
||||
program->bind();
|
||||
}
|
||||
} else {
|
||||
program->bind();
|
||||
}
|
||||
|
||||
if (mesh.blendshapes.isEmpty() && mesh.springiness == 0.0f) {
|
||||
if (!mesh.tangents.isEmpty()) {
|
||||
activeProgram->setAttributeBuffer(tangentLocation, GL_FLOAT, vertexCount * 2 * sizeof(glm::vec3), 3);
|
||||
activeProgram->enableAttributeArray(tangentLocation);
|
||||
}
|
||||
glColorPointer(3, GL_FLOAT, 0, (void*)(vertexCount * 2 * sizeof(glm::vec3) +
|
||||
mesh.tangents.size() * sizeof(glm::vec3)));
|
||||
glTexCoordPointer(2, GL_FLOAT, 0, (void*)(vertexCount * 2 * sizeof(glm::vec3) +
|
||||
(mesh.tangents.size() + mesh.colors.size()) * sizeof(glm::vec3)));
|
||||
|
||||
} else {
|
||||
if (!mesh.tangents.isEmpty()) {
|
||||
activeProgram->setAttributeBuffer(tangentLocation, GL_FLOAT, 0, 3);
|
||||
activeProgram->enableAttributeArray(tangentLocation);
|
||||
}
|
||||
glColorPointer(3, GL_FLOAT, 0, (void*)(mesh.tangents.size() * sizeof(glm::vec3)));
|
||||
glTexCoordPointer(2, GL_FLOAT, 0, (void*)((mesh.tangents.size() + mesh.colors.size()) * sizeof(glm::vec3)));
|
||||
glBindBuffer(GL_ARRAY_BUFFER, _blendedVertexBufferIDs.at(i));
|
||||
|
||||
if (!state.worldSpaceVertices.isEmpty()) {
|
||||
glBufferSubData(GL_ARRAY_BUFFER, 0, vertexCount * sizeof(glm::vec3), state.worldSpaceVertices.constData());
|
||||
glBufferSubData(GL_ARRAY_BUFFER, vertexCount * sizeof(glm::vec3),
|
||||
vertexCount * sizeof(glm::vec3), state.worldSpaceNormals.constData());
|
||||
|
||||
} else {
|
||||
_blendedVertices.resize(max(_blendedVertices.size(), vertexCount));
|
||||
_blendedNormals.resize(_blendedVertices.size());
|
||||
memcpy(_blendedVertices.data(), mesh.vertices.constData(), vertexCount * sizeof(glm::vec3));
|
||||
memcpy(_blendedNormals.data(), mesh.normals.constData(), vertexCount * sizeof(glm::vec3));
|
||||
|
||||
// blend in each coefficient
|
||||
for (unsigned int j = 0; j < _blendshapeCoefficients.size(); j++) {
|
||||
float coefficient = _blendshapeCoefficients[j];
|
||||
if (coefficient == 0.0f || j >= (unsigned int)mesh.blendshapes.size() || mesh.blendshapes[j].vertices.isEmpty()) {
|
||||
continue;
|
||||
}
|
||||
const float NORMAL_COEFFICIENT_SCALE = 0.01f;
|
||||
float normalCoefficient = coefficient * NORMAL_COEFFICIENT_SCALE;
|
||||
const glm::vec3* vertex = mesh.blendshapes[j].vertices.constData();
|
||||
const glm::vec3* normal = mesh.blendshapes[j].normals.constData();
|
||||
for (const int* index = mesh.blendshapes[j].indices.constData(),
|
||||
*end = index + mesh.blendshapes[j].indices.size(); index != end; index++, vertex++, normal++) {
|
||||
_blendedVertices[*index] += *vertex * coefficient;
|
||||
_blendedNormals[*index] += *normal * normalCoefficient;
|
||||
}
|
||||
}
|
||||
|
||||
glBufferSubData(GL_ARRAY_BUFFER, 0, vertexCount * sizeof(glm::vec3), _blendedVertices.constData());
|
||||
glBufferSubData(GL_ARRAY_BUFFER, vertexCount * sizeof(glm::vec3),
|
||||
vertexCount * sizeof(glm::vec3), _blendedNormals.constData());
|
||||
}
|
||||
}
|
||||
glVertexPointer(3, GL_FLOAT, 0, 0);
|
||||
glNormalPointer(GL_FLOAT, 0, (void*)(vertexCount * sizeof(glm::vec3)));
|
||||
|
||||
if (!mesh.colors.isEmpty()) {
|
||||
glEnableClientState(GL_COLOR_ARRAY);
|
||||
} else {
|
||||
glColor3f(1.0f, 1.0f, 1.0f);
|
||||
}
|
||||
if (!mesh.texCoords.isEmpty()) {
|
||||
glEnableClientState(GL_TEXTURE_COORD_ARRAY);
|
||||
}
|
||||
|
||||
qint64 offset = 0;
|
||||
for (int j = 0; j < networkMesh.parts.size(); j++) {
|
||||
const NetworkMeshPart& networkPart = networkMesh.parts.at(j);
|
||||
const FBXMeshPart& part = mesh.parts.at(j);
|
||||
|
||||
// apply material properties
|
||||
glm::vec4 diffuse = glm::vec4(part.diffuseColor, alpha);
|
||||
glm::vec4 specular = glm::vec4(part.specularColor, alpha);
|
||||
glMaterialfv(GL_FRONT, GL_AMBIENT, (const float*)&diffuse);
|
||||
glMaterialfv(GL_FRONT, GL_DIFFUSE, (const float*)&diffuse);
|
||||
glMaterialfv(GL_FRONT, GL_SPECULAR, (const float*)&specular);
|
||||
glMaterialf(GL_FRONT, GL_SHININESS, part.shininess);
|
||||
|
||||
Texture* diffuseMap = networkPart.diffuseTexture.data();
|
||||
if (mesh.isEye) {
|
||||
if (diffuseMap != NULL) {
|
||||
diffuseMap = (_dilatedTextures[i][j] =
|
||||
static_cast<DilatableNetworkTexture*>(diffuseMap)->getDilatedTexture(_pupilDilation)).data();
|
||||
}
|
||||
}
|
||||
glBindTexture(GL_TEXTURE_2D, diffuseMap == NULL ?
|
||||
Application::getInstance()->getTextureCache()->getWhiteTextureID() : diffuseMap->getID());
|
||||
|
||||
if (!mesh.tangents.isEmpty()) {
|
||||
glActiveTexture(GL_TEXTURE1);
|
||||
Texture* normalMap = networkPart.normalTexture.data();
|
||||
glBindTexture(GL_TEXTURE_2D, normalMap == NULL ?
|
||||
Application::getInstance()->getTextureCache()->getBlueTextureID() : normalMap->getID());
|
||||
glActiveTexture(GL_TEXTURE0);
|
||||
}
|
||||
|
||||
glDrawRangeElementsEXT(GL_QUADS, 0, vertexCount - 1, part.quadIndices.size(), GL_UNSIGNED_INT, (void*)offset);
|
||||
offset += part.quadIndices.size() * sizeof(int);
|
||||
glDrawRangeElementsEXT(GL_TRIANGLES, 0, vertexCount - 1, part.triangleIndices.size(),
|
||||
GL_UNSIGNED_INT, (void*)offset);
|
||||
offset += part.triangleIndices.size() * sizeof(int);
|
||||
}
|
||||
|
||||
if (!mesh.colors.isEmpty()) {
|
||||
glDisableClientState(GL_COLOR_ARRAY);
|
||||
}
|
||||
if (!mesh.texCoords.isEmpty()) {
|
||||
glDisableClientState(GL_TEXTURE_COORD_ARRAY);
|
||||
}
|
||||
|
||||
if (!mesh.tangents.isEmpty()) {
|
||||
glActiveTexture(GL_TEXTURE1);
|
||||
glBindTexture(GL_TEXTURE_2D, 0);
|
||||
glActiveTexture(GL_TEXTURE0);
|
||||
|
||||
activeProgram->disableAttributeArray(tangentLocation);
|
||||
}
|
||||
|
||||
if (state.worldSpaceVertices.isEmpty()) {
|
||||
if (state.clusterMatrices.size() > 1) {
|
||||
skinProgram->disableAttributeArray(skinLocations->clusterIndices);
|
||||
skinProgram->disableAttributeArray(skinLocations->clusterWeights);
|
||||
}
|
||||
glPopMatrix();
|
||||
}
|
||||
activeProgram->release();
|
||||
}
|
||||
renderMeshes(alpha, false);
|
||||
|
||||
glDisable(GL_ALPHA_TEST);
|
||||
|
||||
// render translucent meshes afterwards, with back face culling
|
||||
|
||||
glEnable(GL_CULL_FACE);
|
||||
|
||||
renderMeshes(alpha, true);
|
||||
|
||||
glDisable(GL_CULL_FACE);
|
||||
|
||||
// deactivate vertex arrays after drawing
|
||||
glDisableClientState(GL_NORMAL_ARRAY);
|
||||
glDisableClientState(GL_VERTEX_ARRAY);
|
||||
glDisableClientState(GL_TEXTURE_COORD_ARRAY);
|
||||
|
||||
glDisable(GL_ALPHA_TEST);
|
||||
|
||||
// bind with 0 to switch back to normal operation
|
||||
glBindBuffer(GL_ARRAY_BUFFER, 0);
|
||||
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
|
||||
|
@ -882,3 +718,191 @@ void Model::deleteGeometry() {
|
|||
_jointStates.clear();
|
||||
_meshStates.clear();
|
||||
}
|
||||
|
||||
void Model::renderMeshes(float alpha, bool translucent) {
|
||||
const FBXGeometry& geometry = _geometry->getFBXGeometry();
|
||||
const QVector<NetworkMesh>& networkMeshes = _geometry->getMeshes();
|
||||
|
||||
for (int i = 0; i < networkMeshes.size(); i++) {
|
||||
// exit early if the translucency doesn't match what we're drawing
|
||||
const NetworkMesh& networkMesh = networkMeshes.at(i);
|
||||
if (translucent ? (networkMesh.getTranslucentPartCount() == 0) :
|
||||
(networkMesh.getTranslucentPartCount() == networkMesh.parts.size())) {
|
||||
continue;
|
||||
}
|
||||
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, networkMesh.indexBufferID);
|
||||
|
||||
const FBXMesh& mesh = geometry.meshes.at(i);
|
||||
int vertexCount = mesh.vertices.size();
|
||||
|
||||
glBindBuffer(GL_ARRAY_BUFFER, networkMesh.vertexBufferID);
|
||||
|
||||
ProgramObject* program = &_program;
|
||||
ProgramObject* skinProgram = &_skinProgram;
|
||||
SkinLocations* skinLocations = &_skinLocations;
|
||||
if (!mesh.tangents.isEmpty()) {
|
||||
program = &_normalMapProgram;
|
||||
skinProgram = &_skinNormalMapProgram;
|
||||
skinLocations = &_skinNormalMapLocations;
|
||||
}
|
||||
|
||||
const MeshState& state = _meshStates.at(i);
|
||||
ProgramObject* activeProgram = program;
|
||||
int tangentLocation = _normalMapTangentLocation;
|
||||
if (state.worldSpaceVertices.isEmpty()) {
|
||||
glPushMatrix();
|
||||
Application::getInstance()->loadTranslatedViewMatrix(_translation);
|
||||
|
||||
if (state.clusterMatrices.size() > 1) {
|
||||
skinProgram->bind();
|
||||
glUniformMatrix4fvARB(skinLocations->clusterMatrices, state.clusterMatrices.size(), false,
|
||||
(const float*)state.clusterMatrices.constData());
|
||||
int offset = (mesh.tangents.size() + mesh.colors.size()) * sizeof(glm::vec3) +
|
||||
mesh.texCoords.size() * sizeof(glm::vec2) +
|
||||
(mesh.blendshapes.isEmpty() ? vertexCount * 2 * sizeof(glm::vec3) : 0);
|
||||
skinProgram->setAttributeBuffer(skinLocations->clusterIndices, GL_FLOAT, offset, 4);
|
||||
skinProgram->setAttributeBuffer(skinLocations->clusterWeights, GL_FLOAT,
|
||||
offset + vertexCount * sizeof(glm::vec4), 4);
|
||||
skinProgram->enableAttributeArray(skinLocations->clusterIndices);
|
||||
skinProgram->enableAttributeArray(skinLocations->clusterWeights);
|
||||
activeProgram = skinProgram;
|
||||
tangentLocation = skinLocations->tangent;
|
||||
|
||||
} else {
|
||||
glMultMatrixf((const GLfloat*)&state.clusterMatrices[0]);
|
||||
program->bind();
|
||||
}
|
||||
} else {
|
||||
program->bind();
|
||||
}
|
||||
|
||||
if (mesh.blendshapes.isEmpty() && mesh.springiness == 0.0f) {
|
||||
if (!mesh.tangents.isEmpty()) {
|
||||
activeProgram->setAttributeBuffer(tangentLocation, GL_FLOAT, vertexCount * 2 * sizeof(glm::vec3), 3);
|
||||
activeProgram->enableAttributeArray(tangentLocation);
|
||||
}
|
||||
glColorPointer(3, GL_FLOAT, 0, (void*)(vertexCount * 2 * sizeof(glm::vec3) +
|
||||
mesh.tangents.size() * sizeof(glm::vec3)));
|
||||
glTexCoordPointer(2, GL_FLOAT, 0, (void*)(vertexCount * 2 * sizeof(glm::vec3) +
|
||||
(mesh.tangents.size() + mesh.colors.size()) * sizeof(glm::vec3)));
|
||||
|
||||
} else {
|
||||
if (!mesh.tangents.isEmpty()) {
|
||||
activeProgram->setAttributeBuffer(tangentLocation, GL_FLOAT, 0, 3);
|
||||
activeProgram->enableAttributeArray(tangentLocation);
|
||||
}
|
||||
glColorPointer(3, GL_FLOAT, 0, (void*)(mesh.tangents.size() * sizeof(glm::vec3)));
|
||||
glTexCoordPointer(2, GL_FLOAT, 0, (void*)((mesh.tangents.size() + mesh.colors.size()) * sizeof(glm::vec3)));
|
||||
glBindBuffer(GL_ARRAY_BUFFER, _blendedVertexBufferIDs.at(i));
|
||||
|
||||
if (!state.worldSpaceVertices.isEmpty()) {
|
||||
glBufferSubData(GL_ARRAY_BUFFER, 0, vertexCount * sizeof(glm::vec3), state.worldSpaceVertices.constData());
|
||||
glBufferSubData(GL_ARRAY_BUFFER, vertexCount * sizeof(glm::vec3),
|
||||
vertexCount * sizeof(glm::vec3), state.worldSpaceNormals.constData());
|
||||
|
||||
} else {
|
||||
_blendedVertices.resize(max(_blendedVertices.size(), vertexCount));
|
||||
_blendedNormals.resize(_blendedVertices.size());
|
||||
memcpy(_blendedVertices.data(), mesh.vertices.constData(), vertexCount * sizeof(glm::vec3));
|
||||
memcpy(_blendedNormals.data(), mesh.normals.constData(), vertexCount * sizeof(glm::vec3));
|
||||
|
||||
// blend in each coefficient
|
||||
for (unsigned int j = 0; j < _blendshapeCoefficients.size(); j++) {
|
||||
float coefficient = _blendshapeCoefficients[j];
|
||||
if (coefficient == 0.0f || j >= (unsigned int)mesh.blendshapes.size() || mesh.blendshapes[j].vertices.isEmpty()) {
|
||||
continue;
|
||||
}
|
||||
const float NORMAL_COEFFICIENT_SCALE = 0.01f;
|
||||
float normalCoefficient = coefficient * NORMAL_COEFFICIENT_SCALE;
|
||||
const glm::vec3* vertex = mesh.blendshapes[j].vertices.constData();
|
||||
const glm::vec3* normal = mesh.blendshapes[j].normals.constData();
|
||||
for (const int* index = mesh.blendshapes[j].indices.constData(),
|
||||
*end = index + mesh.blendshapes[j].indices.size(); index != end; index++, vertex++, normal++) {
|
||||
_blendedVertices[*index] += *vertex * coefficient;
|
||||
_blendedNormals[*index] += *normal * normalCoefficient;
|
||||
}
|
||||
}
|
||||
|
||||
glBufferSubData(GL_ARRAY_BUFFER, 0, vertexCount * sizeof(glm::vec3), _blendedVertices.constData());
|
||||
glBufferSubData(GL_ARRAY_BUFFER, vertexCount * sizeof(glm::vec3),
|
||||
vertexCount * sizeof(glm::vec3), _blendedNormals.constData());
|
||||
}
|
||||
}
|
||||
glVertexPointer(3, GL_FLOAT, 0, 0);
|
||||
glNormalPointer(GL_FLOAT, 0, (void*)(vertexCount * sizeof(glm::vec3)));
|
||||
|
||||
if (!mesh.colors.isEmpty()) {
|
||||
glEnableClientState(GL_COLOR_ARRAY);
|
||||
} else {
|
||||
glColor3f(1.0f, 1.0f, 1.0f);
|
||||
}
|
||||
if (!mesh.texCoords.isEmpty()) {
|
||||
glEnableClientState(GL_TEXTURE_COORD_ARRAY);
|
||||
}
|
||||
|
||||
qint64 offset = 0;
|
||||
for (int j = 0; j < networkMesh.parts.size(); j++) {
|
||||
const NetworkMeshPart& networkPart = networkMesh.parts.at(j);
|
||||
if (networkPart.isTranslucent() != translucent) {
|
||||
continue;
|
||||
}
|
||||
const FBXMeshPart& part = mesh.parts.at(j);
|
||||
|
||||
// apply material properties
|
||||
glm::vec4 diffuse = glm::vec4(part.diffuseColor, alpha);
|
||||
glm::vec4 specular = glm::vec4(part.specularColor, alpha);
|
||||
glMaterialfv(GL_FRONT, GL_AMBIENT, (const float*)&diffuse);
|
||||
glMaterialfv(GL_FRONT, GL_DIFFUSE, (const float*)&diffuse);
|
||||
glMaterialfv(GL_FRONT, GL_SPECULAR, (const float*)&specular);
|
||||
glMaterialf(GL_FRONT, GL_SHININESS, part.shininess);
|
||||
|
||||
Texture* diffuseMap = networkPart.diffuseTexture.data();
|
||||
if (mesh.isEye) {
|
||||
if (diffuseMap != NULL) {
|
||||
diffuseMap = (_dilatedTextures[i][j] =
|
||||
static_cast<DilatableNetworkTexture*>(diffuseMap)->getDilatedTexture(_pupilDilation)).data();
|
||||
}
|
||||
}
|
||||
glBindTexture(GL_TEXTURE_2D, diffuseMap == NULL ?
|
||||
Application::getInstance()->getTextureCache()->getWhiteTextureID() : diffuseMap->getID());
|
||||
|
||||
if (!mesh.tangents.isEmpty()) {
|
||||
glActiveTexture(GL_TEXTURE1);
|
||||
Texture* normalMap = networkPart.normalTexture.data();
|
||||
glBindTexture(GL_TEXTURE_2D, normalMap == NULL ?
|
||||
Application::getInstance()->getTextureCache()->getBlueTextureID() : normalMap->getID());
|
||||
glActiveTexture(GL_TEXTURE0);
|
||||
}
|
||||
|
||||
glDrawRangeElementsEXT(GL_QUADS, 0, vertexCount - 1, part.quadIndices.size(), GL_UNSIGNED_INT, (void*)offset);
|
||||
offset += part.quadIndices.size() * sizeof(int);
|
||||
glDrawRangeElementsEXT(GL_TRIANGLES, 0, vertexCount - 1, part.triangleIndices.size(),
|
||||
GL_UNSIGNED_INT, (void*)offset);
|
||||
offset += part.triangleIndices.size() * sizeof(int);
|
||||
}
|
||||
|
||||
if (!mesh.colors.isEmpty()) {
|
||||
glDisableClientState(GL_COLOR_ARRAY);
|
||||
}
|
||||
if (!mesh.texCoords.isEmpty()) {
|
||||
glDisableClientState(GL_TEXTURE_COORD_ARRAY);
|
||||
}
|
||||
|
||||
if (!mesh.tangents.isEmpty()) {
|
||||
glActiveTexture(GL_TEXTURE1);
|
||||
glBindTexture(GL_TEXTURE_2D, 0);
|
||||
glActiveTexture(GL_TEXTURE0);
|
||||
|
||||
activeProgram->disableAttributeArray(tangentLocation);
|
||||
}
|
||||
|
||||
if (state.worldSpaceVertices.isEmpty()) {
|
||||
if (state.clusterMatrices.size() > 1) {
|
||||
skinProgram->disableAttributeArray(skinLocations->clusterIndices);
|
||||
skinProgram->disableAttributeArray(skinLocations->clusterWeights);
|
||||
}
|
||||
glPopMatrix();
|
||||
}
|
||||
activeProgram->release();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -214,6 +214,7 @@ protected:
|
|||
private:
|
||||
|
||||
void deleteGeometry();
|
||||
void renderMeshes(float alpha, bool translucent);
|
||||
|
||||
float _pupilDilation;
|
||||
std::vector<float> _blendshapeCoefficients;
|
||||
|
|
|
@ -257,7 +257,8 @@ NetworkTexture::NetworkTexture(const QUrl& url, bool normalMap) :
|
|||
_request(url),
|
||||
_reply(NULL),
|
||||
_attempts(0),
|
||||
_averageColor(1.0f, 1.0f, 1.0f, 1.0f) {
|
||||
_averageColor(1.0f, 1.0f, 1.0f, 1.0f),
|
||||
_translucent(false) {
|
||||
|
||||
if (!url.isValid()) {
|
||||
return;
|
||||
|
@ -300,19 +301,27 @@ void NetworkTexture::handleDownloadProgress(qint64 bytesReceived, qint64 bytesTo
|
|||
|
||||
QImage image = QImage::fromData(entirety).convertToFormat(QImage::Format_ARGB32);
|
||||
|
||||
// sum up the colors for the average
|
||||
// sum up the colors for the average and check for translucency
|
||||
glm::vec4 accumulated;
|
||||
int translucentPixels = 0;
|
||||
const int EIGHT_BIT_MAXIMUM = 255;
|
||||
for (int y = 0; y < image.height(); y++) {
|
||||
for (int x = 0; x < image.width(); x++) {
|
||||
QRgb pixel = image.pixel(x, y);
|
||||
accumulated.r += qRed(pixel);
|
||||
accumulated.g += qGreen(pixel);
|
||||
accumulated.b += qBlue(pixel);
|
||||
accumulated.a += qAlpha(pixel);
|
||||
|
||||
int alpha = qAlpha(pixel);
|
||||
if (alpha != 0 && alpha != EIGHT_BIT_MAXIMUM) {
|
||||
translucentPixels++;
|
||||
}
|
||||
accumulated.a += alpha;
|
||||
}
|
||||
}
|
||||
const float EIGHT_BIT_MAXIMUM = 255.0f;
|
||||
_averageColor = accumulated / (image.width() * image.height() * EIGHT_BIT_MAXIMUM);
|
||||
int imageArea = image.width() * image.height();
|
||||
_averageColor = accumulated / (imageArea * EIGHT_BIT_MAXIMUM);
|
||||
_translucent = (translucentPixels >= imageArea / 2);
|
||||
|
||||
imageLoaded(image);
|
||||
glBindTexture(GL_TEXTURE_2D, getID());
|
||||
|
|
|
@ -121,6 +121,10 @@ public:
|
|||
/// Returns the average color over the entire texture.
|
||||
const glm::vec4& getAverageColor() const { return _averageColor; }
|
||||
|
||||
/// Checks whether it "looks like" this texture is translucent
|
||||
/// (majority of pixels neither fully opaque or fully transparent).
|
||||
bool isTranslucent() const { return _translucent; }
|
||||
|
||||
protected:
|
||||
|
||||
virtual void imageLoaded(const QImage& image);
|
||||
|
@ -137,6 +141,7 @@ private:
|
|||
QNetworkReply* _reply;
|
||||
int _attempts;
|
||||
glm::vec4 _averageColor;
|
||||
bool _translucent;
|
||||
};
|
||||
|
||||
/// Caches derived, dilated textures.
|
||||
|
|
|
@ -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>
|
||||
|
||||
|
|
30
libraries/avatars/src/AvatarHashMap.cpp
Normal file
30
libraries/avatars/src/AvatarHashMap.cpp
Normal 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);
|
||||
}
|
||||
|
37
libraries/avatars/src/AvatarHashMap.h
Normal file
37
libraries/avatars/src/AvatarHashMap.h
Normal 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__) */
|
|
@ -15,8 +15,10 @@
|
|||
#include "HTTPManager.h"
|
||||
|
||||
const char* HTTPConnection::StatusCode200 = "200 OK";
|
||||
const char* HTTPConnection::StatusCode301 = "301 Moved Permanently";
|
||||
const char* HTTPConnection::StatusCode400 = "400 Bad Request";
|
||||
const char* HTTPConnection::StatusCode404 = "404 Not Found";
|
||||
const char* HTTPConnection::DefaultContentType = "text/plain; charset=ISO-8859-1";
|
||||
|
||||
HTTPConnection::HTTPConnection (QTcpSocket* socket, HTTPManager* parentManager) :
|
||||
QObject(parentManager),
|
||||
|
|
|
@ -40,8 +40,10 @@ class HTTPConnection : public QObject {
|
|||
|
||||
public:
|
||||
static const char* StatusCode200;
|
||||
static const char* StatusCode301;
|
||||
static const char* StatusCode400;
|
||||
static const char* StatusCode404;
|
||||
static const char* DefaultContentType;
|
||||
|
||||
/// WebSocket close status codes.
|
||||
enum ReasonCode { NoReason = 0, NormalClosure = 1000, GoingAway = 1001 };
|
||||
|
@ -72,7 +74,7 @@ public:
|
|||
|
||||
/// Sends a response and closes the connection.
|
||||
void respond (const char* code, const QByteArray& content = QByteArray(),
|
||||
const char* contentType = "text/plain; charset=ISO-8859-1",
|
||||
const char* contentType = DefaultContentType,
|
||||
const Headers& headers = Headers());
|
||||
|
||||
protected slots:
|
||||
|
|
|
@ -23,7 +23,6 @@ bool HTTPManager::handleHTTPRequest(HTTPConnection* connection, const QString& p
|
|||
}
|
||||
|
||||
// check to see if there is a file to serve from the document root for this path
|
||||
|
||||
QString subPath = path;
|
||||
|
||||
// remove any slash at the beginning of the path
|
||||
|
@ -33,6 +32,19 @@ bool HTTPManager::handleHTTPRequest(HTTPConnection* connection, const QString& p
|
|||
|
||||
QString filePath;
|
||||
|
||||
if (QFileInfo(_documentRoot + subPath).isFile()) {
|
||||
filePath = _documentRoot + subPath;
|
||||
} else if (subPath.size() > 0 && !subPath.endsWith('/')) {
|
||||
// this could be a directory with a trailing slash
|
||||
// send a redirect to the path with a slash so we can
|
||||
QString redirectLocation = '/' + subPath + '/';
|
||||
|
||||
QHash<QByteArray, QByteArray> redirectHeader;
|
||||
redirectHeader.insert(QByteArray("Location"), redirectLocation.toUtf8());
|
||||
|
||||
connection->respond(HTTPConnection::StatusCode301, "", HTTPConnection::DefaultContentType, redirectHeader);
|
||||
}
|
||||
|
||||
// if the last thing is a trailing slash then we want to look for index file
|
||||
if (subPath.endsWith('/') || subPath.size() == 0) {
|
||||
QStringList possibleIndexFiles = QStringList() << "index.html" << "index.shtml";
|
||||
|
@ -43,14 +55,10 @@ bool HTTPManager::handleHTTPRequest(HTTPConnection* connection, const QString& p
|
|||
break;
|
||||
}
|
||||
}
|
||||
} else if (QFileInfo(_documentRoot + subPath).isFile()) {
|
||||
filePath = _documentRoot + subPath;
|
||||
}
|
||||
|
||||
|
||||
if (!filePath.isEmpty()) {
|
||||
// file exists, serve it
|
||||
|
||||
static QMimeDatabase mimeDatabase;
|
||||
|
||||
QFile localFile(filePath);
|
||||
|
@ -99,8 +107,10 @@ bool HTTPManager::handleHTTPRequest(HTTPConnection* connection, const QString& p
|
|||
}
|
||||
}
|
||||
|
||||
connection->respond(HTTPConnection::StatusCode200, localFileString.toLocal8Bit(), qPrintable(mimeDatabase.mimeTypeForFile(filePath).name()));
|
||||
connection->respond(HTTPConnection::StatusCode200, localFileString.toLocal8Bit(),
|
||||
qPrintable(mimeDatabase.mimeTypeForFile(filePath).name()));
|
||||
} else {
|
||||
|
||||
// respond with a 404
|
||||
connection->respond(HTTPConnection::StatusCode404, "Resource not found.");
|
||||
}
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -771,7 +771,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);
|
||||
|
||||
|
|
|
@ -16,6 +16,7 @@
|
|||
#include <QtScript/QScriptEngine>
|
||||
#include <QtCore/QObject>
|
||||
|
||||
#include <CollisionInfo.h>
|
||||
#include <SharedUtil.h>
|
||||
#include <OctreePacketData.h>
|
||||
|
||||
|
@ -287,6 +288,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);
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -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__) */
|
||||
|
|
|
@ -283,16 +283,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
|
||||
}
|
||||
|
||||
|
@ -300,13 +298,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 {
|
||||
|
|
|
@ -45,7 +45,19 @@ public:
|
|||
void deleteParticle(const ParticleID& particleID);
|
||||
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);
|
||||
|
|
|
@ -230,21 +230,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 {
|
||||
|
@ -273,8 +286,6 @@ bool ParticleTreeElement::removeParticleWithID(uint32_t id) {
|
|||
return foundParticle;
|
||||
}
|
||||
|
||||
|
||||
|
||||
int ParticleTreeElement::readElementDataFromBuffer(const unsigned char* data, int bytesLeftToRead,
|
||||
ReadBitstreamToTreeParams& args) {
|
||||
|
||||
|
|
|
@ -102,12 +102,22 @@ public:
|
|||
void updateParticleID(FindAndUpdateParticleIDArgs* args);
|
||||
|
||||
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);
|
||||
|
||||
|
|
|
@ -156,7 +156,8 @@ QVector<ParticleID> ParticlesScriptingInterface::findParticles(const glm::vec3&
|
|||
QVector<ParticleID> result;
|
||||
if (_particleTree) {
|
||||
_particleTree->lockForRead();
|
||||
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);
|
||||
_particleTree->unlock();
|
||||
|
||||
foreach (const Particle* particle, particles) {
|
||||
|
|
|
@ -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;
|
||||
};
|
||||
|
|
Loading…
Reference in a new issue