diff --git a/interface/resources/shaders/model.frag b/interface/resources/shaders/model.frag index 877cdca885..e035f9bfe9 100644 --- a/interface/resources/shaders/model.frag +++ b/interface/resources/shaders/model.frag @@ -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); } diff --git a/interface/resources/shaders/model_normal_map.frag b/interface/resources/shaders/model_normal_map.frag index 9740a4d4b1..9a0e964500 100644 --- a/interface/resources/shaders/model_normal_map.frag +++ b/interface/resources/shaders/model_normal_map.frag @@ -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); } diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 1a673da70c..a0d9c9617e 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -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) { diff --git a/interface/src/avatar/AvatarManager.cpp b/interface/src/avatar/AvatarManager.cpp index dd1686a0d2..fc3ac73044 100644 --- a/interface/src/avatar/AvatarManager.cpp +++ b/interface/src/avatar/AvatarManager.cpp @@ -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 >("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(avatarPointer.data()); + if (avatar != static_cast(_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(avatarIterator.value().data()); + if (avatar == static_cast(_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(avatarPointer.data()); if (!avatar->isInitialized()) { avatar->init(); } - avatar->render(false); - avatar->setDisplayingLookatVectors(Menu::getInstance()->isOptionChecked(MenuOption::LookAtVectors)); + if (avatar == static_cast(_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::iterator fadingAvatar = _avatarFades.begin(); + QVector::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(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(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(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); } -} \ No newline at end of file +} diff --git a/interface/src/avatar/AvatarManager.h b/interface/src/avatar/AvatarManager.h index 7d0f11646d..369444bcc7 100644 --- a/interface/src/avatar/AvatarManager.h +++ b/interface/src/avatar/AvatarManager.h @@ -13,30 +13,30 @@ #include #include +#include #include #include "Avatar.h" -typedef QSharedPointer AvatarSharedPointer; -typedef QHash 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 _lookAtTargetAvatar; + QWeakPointer _lookAtTargetAvatar; glm::vec3 _lookAtOtherPosition; float _lookAtIndicatorScale; - AvatarHash _avatarHash; QVector _avatarFades; + MyAvatar* _myAvatar; }; #endif /* defined(__hifi__AvatarManager__) */ diff --git a/interface/src/avatar/Hand.cpp b/interface/src/avatar/Hand.cpp index 18d6031f98..b441000cc1 100644 --- a/interface/src/avatar/Hand.cpp +++ b/interface/src/avatar/Hand.cpp @@ -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(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) { diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index ca7a3b863b..6910826524 100644 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -791,14 +791,15 @@ void MyAvatar::updateChatCircle(float deltaTime) { // find all circle-enabled members and sort by distance QVector 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(avatarPointer.data()); + if ( ! avatar->isChatCirclingEnabled() || + avatar == static_cast(this)) { continue; } - + + SortedAvatar sortedAvatar; + sortedAvatar.avatar = avatar; sortedAvatar.distance = glm::distance(_position, sortedAvatar.avatar->getPosition()); sortedAvatars.append(sortedAvatar); } diff --git a/interface/src/renderer/GeometryCache.cpp b/interface/src/renderer/GeometryCache.cpp index 73248b413f..63a0c51f0b 100644 --- a/interface/src/renderer/GeometryCache.cpp +++ b/interface/src/renderer/GeometryCache.cpp @@ -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; +} diff --git a/interface/src/renderer/GeometryCache.h b/interface/src/renderer/GeometryCache.h index 312d8bcd91..e65aed31d4 100644 --- a/interface/src/renderer/GeometryCache.h +++ b/interface/src/renderer/GeometryCache.h @@ -93,6 +93,8 @@ public: QSharedPointer diffuseTexture; QSharedPointer normalTexture; + + bool isTranslucent() const; }; /// The state associated with a single mesh. @@ -103,6 +105,8 @@ public: GLuint vertexBufferID; QVector parts; + + int getTranslucentPartCount() const; }; #endif /* defined(__interface__GeometryCache__) */ diff --git a/interface/src/renderer/Model.cpp b/interface/src/renderer/Model.cpp index c6c820a0f9..6d61b2df68 100644 --- a/interface/src/renderer/Model.cpp +++ b/interface/src/renderer/Model.cpp @@ -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& 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(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& 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(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(); + } +} diff --git a/interface/src/renderer/Model.h b/interface/src/renderer/Model.h index 3e8cd2be9c..389020d1b1 100644 --- a/interface/src/renderer/Model.h +++ b/interface/src/renderer/Model.h @@ -214,6 +214,7 @@ protected: private: void deleteGeometry(); + void renderMeshes(float alpha, bool translucent); float _pupilDilation; std::vector _blendshapeCoefficients; diff --git a/interface/src/renderer/TextureCache.cpp b/interface/src/renderer/TextureCache.cpp index 96fe84b305..dc6883a5d0 100644 --- a/interface/src/renderer/TextureCache.cpp +++ b/interface/src/renderer/TextureCache.cpp @@ -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()); diff --git a/interface/src/renderer/TextureCache.h b/interface/src/renderer/TextureCache.h index d3f138254f..e560acf6f7 100644 --- a/interface/src/renderer/TextureCache.h +++ b/interface/src/renderer/TextureCache.h @@ -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. diff --git a/interface/src/ui/Snapshot.h b/interface/src/ui/Snapshot.h index 39548cdff8..26315678f9 100644 --- a/interface/src/ui/Snapshot.h +++ b/interface/src/ui/Snapshot.h @@ -9,9 +9,9 @@ #ifndef __hifi__Snapshot__ #define __hifi__Snapshot__ -#import -#import -#import +#include +#include +#include #include diff --git a/libraries/avatars/src/AvatarHashMap.cpp b/libraries/avatars/src/AvatarHashMap.cpp new file mode 100644 index 0000000000..b1bb42edbf --- /dev/null +++ b/libraries/avatars/src/AvatarHashMap.cpp @@ -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); +} + diff --git a/libraries/avatars/src/AvatarHashMap.h b/libraries/avatars/src/AvatarHashMap.h new file mode 100644 index 0000000000..37e9004d6f --- /dev/null +++ b/libraries/avatars/src/AvatarHashMap.h @@ -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 +#include +#include + +#include "AvatarData.h" + +typedef QSharedPointer AvatarSharedPointer; +typedef QHash 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__) */ diff --git a/libraries/embedded-webserver/src/HTTPConnection.cpp b/libraries/embedded-webserver/src/HTTPConnection.cpp index 3ffc96c9f4..50ce72e0cd 100755 --- a/libraries/embedded-webserver/src/HTTPConnection.cpp +++ b/libraries/embedded-webserver/src/HTTPConnection.cpp @@ -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), diff --git a/libraries/embedded-webserver/src/HTTPConnection.h b/libraries/embedded-webserver/src/HTTPConnection.h index 9342c32849..dbf1e31f47 100644 --- a/libraries/embedded-webserver/src/HTTPConnection.h +++ b/libraries/embedded-webserver/src/HTTPConnection.h @@ -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: diff --git a/libraries/embedded-webserver/src/HTTPManager.cpp b/libraries/embedded-webserver/src/HTTPManager.cpp index 735300de7c..849bf593cd 100755 --- a/libraries/embedded-webserver/src/HTTPManager.cpp +++ b/libraries/embedded-webserver/src/HTTPManager.cpp @@ -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 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."); } diff --git a/libraries/octree/src/AABox.cpp b/libraries/octree/src/AABox.cpp index 670e2b8a6b..1313111765 100644 --- a/libraries/octree/src/AABox.cpp +++ b/libraries/octree/src/AABox.cpp @@ -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) { diff --git a/libraries/octree/src/AABox.h b/libraries/octree/src/AABox.h index aec0fff450..731b82be4c 100644 --- a/libraries/octree/src/AABox.h +++ b/libraries/octree/src/AABox.h @@ -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; diff --git a/libraries/particles/src/Particle.cpp b/libraries/particles/src/Particle.cpp index 2dbf59efbe..933bf36830 100644 --- a/libraries/particles/src/Particle.cpp +++ b/libraries/particles/src/Particle.cpp @@ -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); diff --git a/libraries/particles/src/Particle.h b/libraries/particles/src/Particle.h index fa9ea14d0e..456eb7ef4f 100644 --- a/libraries/particles/src/Particle.h +++ b/libraries/particles/src/Particle.h @@ -16,6 +16,7 @@ #include #include +#include #include #include @@ -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); diff --git a/libraries/particles/src/ParticleCollisionSystem.cpp b/libraries/particles/src/ParticleCollisionSystem.cpp index 7e8866e7d4..a2394935b1 100644 --- a/libraries/particles/src/ParticleCollisionSystem.cpp +++ b/libraries/particles/src/ParticleCollisionSystem.cpp @@ -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(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); } diff --git a/libraries/particles/src/ParticleCollisionSystem.h b/libraries/particles/src/ParticleCollisionSystem.h index cf52d01a7a..4a61693fa6 100644 --- a/libraries/particles/src/ParticleCollisionSystem.h +++ b/libraries/particles/src/ParticleCollisionSystem.h @@ -16,6 +16,7 @@ #include #include +#include #include #include #include @@ -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__) */ diff --git a/libraries/particles/src/ParticleTree.cpp b/libraries/particles/src/ParticleTree.cpp index 6cb46b3340..15caff2ccf 100644 --- a/libraries/particles/src/ParticleTree.cpp +++ b/libraries/particles/src/ParticleTree.cpp @@ -283,16 +283,14 @@ public: bool ParticleTree::findInSphereOperation(OctreeElement* element, void* extraData) { FindAllNearPointArgs* args = static_cast(extraData); - ParticleTreeElement* particleTreeElement = static_cast(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 moreParticles = particleTreeElement->getParticles(args->position, args->targetRadius); - args->particles << moreParticles; + ParticleTreeElement* particleTreeElement = static_cast(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 ParticleTree::findParticles(const glm::vec3& center, float radius) { - QVector result; +void ParticleTree::findParticles(const glm::vec3& center, float radius, QVector& 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 _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(element); + particleTreeElement->getParticlesForUpdate(args->_box, args->_foundParticles); + return true; + } + return false; +} + +void ParticleTree::findParticlesForUpdate(const AABox& box, QVector 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 { diff --git a/libraries/particles/src/ParticleTree.h b/libraries/particles/src/ParticleTree.h index 54286e7c4f..d17be3d33a 100644 --- a/libraries/particles/src/ParticleTree.h +++ b/libraries/particles/src/ParticleTree.h @@ -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 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& 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 foundParticles); void addNewlyCreatedHook(NewlyCreatedParticleHook* hook); void removeNewlyCreatedHook(NewlyCreatedParticleHook* hook); diff --git a/libraries/particles/src/ParticleTreeElement.cpp b/libraries/particles/src/ParticleTreeElement.cpp index 7e3f769daa..48e619da59 100644 --- a/libraries/particles/src/ParticleTreeElement.cpp +++ b/libraries/particles/src/ParticleTreeElement.cpp @@ -230,21 +230,34 @@ const Particle* ParticleTreeElement::getClosestParticle(glm::vec3 position) cons return closestParticle; } -QVector ParticleTreeElement::getParticles(glm::vec3 searchPosition, float searchRadius) const { - QVector results; +void ParticleTreeElement::getParticles(const glm::vec3& searchPosition, float searchRadius, QVector& 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& foundParticles) { + QList::iterator particleItr = _particles->begin(); + QList::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) { diff --git a/libraries/particles/src/ParticleTreeElement.h b/libraries/particles/src/ParticleTreeElement.h index b810efef97..59a03f7b41 100644 --- a/libraries/particles/src/ParticleTreeElement.h +++ b/libraries/particles/src/ParticleTreeElement.h @@ -102,12 +102,22 @@ public: void updateParticleID(FindAndUpdateParticleIDArgs* args); const Particle* getClosestParticle(glm::vec3 position) const; - QVector 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& 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& foundParticles); + const Particle* getParticleWithID(uint32_t id) const; bool removeParticleWithID(uint32_t id); - protected: virtual void init(unsigned char * octalCode); diff --git a/libraries/particles/src/ParticlesScriptingInterface.cpp b/libraries/particles/src/ParticlesScriptingInterface.cpp index d2f5edce26..0ef8f82b00 100644 --- a/libraries/particles/src/ParticlesScriptingInterface.cpp +++ b/libraries/particles/src/ParticlesScriptingInterface.cpp @@ -156,7 +156,8 @@ QVector ParticlesScriptingInterface::findParticles(const glm::vec3& QVector result; if (_particleTree) { _particleTree->lockForRead(); - QVector particles = _particleTree->findParticles(center/(float)TREE_SCALE, radius/(float)TREE_SCALE); + QVector particles; + _particleTree->findParticles(center/(float)TREE_SCALE, radius/(float)TREE_SCALE, particles); _particleTree->unlock(); foreach (const Particle* particle, particles) { diff --git a/libraries/shared/src/CollisionInfo.h b/libraries/shared/src/CollisionInfo.h index 1e4801788e..38ae64e30c 100644 --- a/libraries/shared/src/CollisionInfo.h +++ b/libraries/shared/src/CollisionInfo.h @@ -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; };