From 7fc66392cce00da8f70fd6a1c2048e5275392a08 Mon Sep 17 00:00:00 2001 From: stojce Date: Sat, 8 Feb 2014 12:04:55 +0100 Subject: [PATCH 01/22] Interface OS X hifi URL scheme --- cmake/modules/MacOSXBundleInfo.plist.in | 47 +++++++++++++++++++++++++ interface/CMakeLists.txt | 4 +++ 2 files changed, 51 insertions(+) create mode 100644 cmake/modules/MacOSXBundleInfo.plist.in diff --git a/cmake/modules/MacOSXBundleInfo.plist.in b/cmake/modules/MacOSXBundleInfo.plist.in new file mode 100644 index 0000000000..1682b6c022 --- /dev/null +++ b/cmake/modules/MacOSXBundleInfo.plist.in @@ -0,0 +1,47 @@ + + + + + CFBundleDevelopmentRegion + English + CFBundleExecutable + ${MACOSX_BUNDLE_EXECUTABLE_NAME} + CFBundleGetInfoString + ${MACOSX_BUNDLE_INFO_STRING} + CFBundleIconFile + ${MACOSX_BUNDLE_ICON_FILE} + CFBundleIdentifier + ${MACOSX_BUNDLE_GUI_IDENTIFIER} + CFBundleInfoDictionaryVersion + 6.0 + CFBundleLongVersionString + ${MACOSX_BUNDLE_LONG_VERSION_STRING} + CFBundleName + ${MACOSX_BUNDLE_BUNDLE_NAME} + CFBundlePackageType + APPL + CFBundleShortVersionString + ${MACOSX_BUNDLE_SHORT_VERSION_STRING} + CFBundleSignature + ???? + CFBundleVersion + ${MACOSX_BUNDLE_BUNDLE_VERSION} + CSResourcesFileMapped + + LSRequiresCarbon + + NSHumanReadableCopyright + ${MACOSX_BUNDLE_COPYRIGHT} + CFBundleURLTypes + + + CFBundleURLName + ${MACOSX_BUNDLE_BUNDLE_NAME} URL + CFBundleURLSchemes + + hifi + + + + + diff --git a/interface/CMakeLists.txt b/interface/CMakeLists.txt index 6af6ed478d..846df5e493 100644 --- a/interface/CMakeLists.txt +++ b/interface/CMakeLists.txt @@ -90,6 +90,10 @@ qt5_wrap_ui(QT_UI_HEADERS ${QT_UI_FILES}) set(INTERFACE_SRCS ${INTERFACE_SRCS} ${QT_UI_HEADERS}) if (APPLE) + + # configure CMake to use a custom Info.plist + SET_TARGET_PROPERTIES( ${this_target} PROPERTIES MACOSX_BUNDLE_INFO_PLIST MacOSXBundleInfo.plist.in ) + set(MACOSX_BUNDLE_BUNDLE_NAME Interface) # set how the icon shows up in the Info.plist file SET(MACOSX_BUNDLE_ICON_FILE interface.icns) From b05967a5149ccff2a723a46581214861fcb54e8a Mon Sep 17 00:00:00 2001 From: stojce Date: Sun, 9 Feb 2014 17:47:46 +0100 Subject: [PATCH 02/22] Custom URL handler --- interface/src/Application.cpp | 17 +++++++ interface/src/Application.h | 4 +- interface/src/Menu.cpp | 84 +++++++++++++++-------------------- interface/src/Menu.h | 1 + 4 files changed, 57 insertions(+), 49 deletions(-) diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 3f32c6be09..9ba7699714 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -101,6 +101,8 @@ const QString SKIP_FILENAME = QStandardPaths::writableLocation(QStandardPaths::D const int STATS_PELS_PER_LINE = 20; +const QString CUSTOM_URL_SCHEME = "hifi:"; + void messageHandler(QtMsgType type, const QMessageLogContext& context, const QString& message) { if (message.size() > 0) { QString messageWithNewLine = message + "\n"; @@ -680,6 +682,21 @@ void Application::controlledBroadcastToNodes(const QByteArray& packet, const Nod } } +bool Application::event(QEvent* event) { + + // handle custom URL + if (event->type() == QEvent::FileOpen) { + QFileOpenEvent* fileEvent = static_cast(event); + if (!fileEvent->url().isEmpty() && fileEvent->url().toLocalFile().startsWith(CUSTOM_URL_SCHEME)) { + QString destination = fileEvent->url().toLocalFile().remove(QRegExp(CUSTOM_URL_SCHEME + "|/")); + Menu::getInstance()->goToDestination(destination); + } + + return false; + } + return QApplication::event(event); +} + void Application::keyPressEvent(QKeyEvent* event) { _controllerScriptingInterface.emitKeyPressEvent(event); // send events to any registered scripts diff --git a/interface/src/Application.h b/interface/src/Application.h index 94b5601797..c4550e2f0b 100644 --- a/interface/src/Application.h +++ b/interface/src/Application.h @@ -127,7 +127,9 @@ public: void touchUpdateEvent(QTouchEvent* event); void wheelEvent(QWheelEvent* event); - + + bool event(QEvent* event); + void makeVoxel(glm::vec3 position, float scale, unsigned char red, diff --git a/interface/src/Menu.cpp b/interface/src/Menu.cpp index 6640df3468..a447c6703a 100644 --- a/interface/src/Menu.cpp +++ b/interface/src/Menu.cpp @@ -913,6 +913,39 @@ void Menu::goToDomain() { sendFakeEnterEvent(); } +bool Menu::goToDestination(QString destination) { + + QStringList coordinateItems = destination.split(QRegExp("_|,"), QString::SkipEmptyParts); + + const int NUMBER_OF_COORDINATE_ITEMS = 3; + const int X_ITEM = 0; + const int Y_ITEM = 1; + const int Z_ITEM = 2; + if (coordinateItems.size() == NUMBER_OF_COORDINATE_ITEMS) { + + double x = replaceLastOccurrence('-', '.', coordinateItems[X_ITEM].trimmed()).toDouble(); + double y = replaceLastOccurrence('-', '.', coordinateItems[Y_ITEM].trimmed()).toDouble(); + double z = replaceLastOccurrence('-', '.', coordinateItems[Z_ITEM].trimmed()).toDouble(); + + glm::vec3 newAvatarPos(x, y, z); + + MyAvatar* myAvatar = Application::getInstance()->getAvatar(); + glm::vec3 avatarPos = myAvatar->getPosition(); + if (newAvatarPos != avatarPos) { + // send a node kill request, indicating to other clients that they should play the "disappeared" effect + MyAvatar::sendKillAvatar(); + + qDebug("Going To Location: %f, %f, %f...", x, y, z); + myAvatar->setPosition(newAvatarPos); + } + + return true; + } + + // no coordinates were parsed + return false; +} + void Menu::goTo() { QInputDialog gotoDialog(Application::getInstance()->getWindow()); @@ -928,31 +961,8 @@ void Menu::goTo() { destination = gotoDialog.textValue(); - QStringList coordinateItems = destination.split(QRegExp("_|,"), QString::SkipEmptyParts); - - const int NUMBER_OF_COORDINATE_ITEMS = 3; - const int X_ITEM = 0; - const int Y_ITEM = 1; - const int Z_ITEM = 2; - if (coordinateItems.size() == NUMBER_OF_COORDINATE_ITEMS) { - - double x = replaceLastOccurrence('-', '.', coordinateItems[X_ITEM].trimmed()).toDouble(); - double y = replaceLastOccurrence('-', '.', coordinateItems[Y_ITEM].trimmed()).toDouble(); - double z = replaceLastOccurrence('-', '.', coordinateItems[Z_ITEM].trimmed()).toDouble(); - - glm::vec3 newAvatarPos(x, y, z); - - MyAvatar* myAvatar = Application::getInstance()->getAvatar(); - glm::vec3 avatarPos = myAvatar->getPosition(); - if (newAvatarPos != avatarPos) { - // send a node kill request, indicating to other clients that they should play the "disappeared" effect - MyAvatar::sendKillAvatar(); - - qDebug("Going To Location: %f, %f, %f...", x, y, z); - myAvatar->setPosition(newAvatarPos); - } - - } else { + // go to coordinate destination or to Username + if (!goToDestination(destination)) { // there's a username entered by the user, make a request to the data-server DataServerClient::getValuesForKeysAndUserString( QStringList() @@ -983,29 +993,7 @@ void Menu::goToLocation() { int dialogReturn = coordinateDialog.exec(); if (dialogReturn == QDialog::Accepted && !coordinateDialog.textValue().isEmpty()) { - QByteArray newCoordinates; - - QString delimiterPattern(","); - QStringList coordinateItems = coordinateDialog.textValue().split(delimiterPattern); - - const int NUMBER_OF_COORDINATE_ITEMS = 3; - const int X_ITEM = 0; - const int Y_ITEM = 1; - const int Z_ITEM = 2; - if (coordinateItems.size() == NUMBER_OF_COORDINATE_ITEMS) { - double x = coordinateItems[X_ITEM].toDouble(); - double y = coordinateItems[Y_ITEM].toDouble(); - double z = coordinateItems[Z_ITEM].toDouble(); - glm::vec3 newAvatarPos(x, y, z); - - if (newAvatarPos != avatarPos) { - // send a node kill request, indicating to other clients that they should play the "disappeared" effect - MyAvatar::sendKillAvatar(); - - qDebug("Going To Location: %f, %f, %f...", x, y, z); - myAvatar->setPosition(newAvatarPos); - } - } + goToDestination(coordinateDialog.textValue()); } sendFakeEnterEvent(); diff --git a/interface/src/Menu.h b/interface/src/Menu.h index fcd2d74940..4e835146e6 100644 --- a/interface/src/Menu.h +++ b/interface/src/Menu.h @@ -84,6 +84,7 @@ public: const char* member = NULL, QAction::MenuRole role = QAction::NoRole); virtual void removeAction(QMenu* menu, const QString& actionName); + bool goToDestination(QString destination); public slots: void bandwidthDetails(); From 6e31f1692437095894dd4edc301cb158e467e01c Mon Sep 17 00:00:00 2001 From: stojce Date: Tue, 11 Feb 2014 19:25:03 +0100 Subject: [PATCH 03/22] fix for Firefox --- interface/CMakeLists.txt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/interface/CMakeLists.txt b/interface/CMakeLists.txt index 846df5e493..8e96006828 100644 --- a/interface/CMakeLists.txt +++ b/interface/CMakeLists.txt @@ -95,6 +95,8 @@ if (APPLE) SET_TARGET_PROPERTIES( ${this_target} PROPERTIES MACOSX_BUNDLE_INFO_PLIST MacOSXBundleInfo.plist.in ) set(MACOSX_BUNDLE_BUNDLE_NAME Interface) + set(MACOSX_BUNDLE_GUI_IDENTIFIER io.highfidelity.Interface) + # set how the icon shows up in the Info.plist file SET(MACOSX_BUNDLE_ICON_FILE interface.icns) From b6d77ec63767c17e59572b816c97e37e56c10224 Mon Sep 17 00:00:00 2001 From: Andrzej Kapolka Date: Tue, 11 Feb 2014 14:20:52 -0800 Subject: [PATCH 04/22] Switched from loading FBX and FST simultaneously to loading FST first, then using its filename and texdir properties (as Faceshift does) to locate the corresponding model. Also fixed a bug with the fallback models (they were invisible after second failure). --- .../body.fbx} | Bin .../meshes/{ => defaultAvatar}/body.jpg | Bin .../head.fbx} | Bin .../meshes/{ => defaultAvatar}/tail.jpg | Bin .../meshes/{ => defaultAvatar}/visor.png | Bin .../resources/meshes/defaultAvatar_body.fst | 2 + .../resources/meshes/defaultAvatar_head.fst | 4 +- interface/src/avatar/Avatar.cpp | 4 +- interface/src/renderer/FBXReader.cpp | 16 +- interface/src/renderer/FBXReader.h | 5 +- interface/src/renderer/GeometryCache.cpp | 325 +++++++++--------- interface/src/renderer/GeometryCache.h | 24 +- interface/src/renderer/Model.cpp | 8 +- 13 files changed, 191 insertions(+), 197 deletions(-) rename interface/resources/meshes/{defaultAvatar_body.fbx => defaultAvatar/body.fbx} (100%) rename interface/resources/meshes/{ => defaultAvatar}/body.jpg (100%) rename interface/resources/meshes/{defaultAvatar_head.fbx => defaultAvatar/head.fbx} (100%) rename interface/resources/meshes/{ => defaultAvatar}/tail.jpg (100%) rename interface/resources/meshes/{ => defaultAvatar}/visor.png (100%) diff --git a/interface/resources/meshes/defaultAvatar_body.fbx b/interface/resources/meshes/defaultAvatar/body.fbx similarity index 100% rename from interface/resources/meshes/defaultAvatar_body.fbx rename to interface/resources/meshes/defaultAvatar/body.fbx diff --git a/interface/resources/meshes/body.jpg b/interface/resources/meshes/defaultAvatar/body.jpg similarity index 100% rename from interface/resources/meshes/body.jpg rename to interface/resources/meshes/defaultAvatar/body.jpg diff --git a/interface/resources/meshes/defaultAvatar_head.fbx b/interface/resources/meshes/defaultAvatar/head.fbx similarity index 100% rename from interface/resources/meshes/defaultAvatar_head.fbx rename to interface/resources/meshes/defaultAvatar/head.fbx diff --git a/interface/resources/meshes/tail.jpg b/interface/resources/meshes/defaultAvatar/tail.jpg similarity index 100% rename from interface/resources/meshes/tail.jpg rename to interface/resources/meshes/defaultAvatar/tail.jpg diff --git a/interface/resources/meshes/visor.png b/interface/resources/meshes/defaultAvatar/visor.png similarity index 100% rename from interface/resources/meshes/visor.png rename to interface/resources/meshes/defaultAvatar/visor.png diff --git a/interface/resources/meshes/defaultAvatar_body.fst b/interface/resources/meshes/defaultAvatar_body.fst index 3e8fa3ef45..5874c206db 100644 --- a/interface/resources/meshes/defaultAvatar_body.fst +++ b/interface/resources/meshes/defaultAvatar_body.fst @@ -1,3 +1,5 @@ +filename=defaultAvatar/body.fbx +texdir=defaultAvatar scale=130 joint = jointRoot = jointRoot joint = jointLean = jointSpine diff --git a/interface/resources/meshes/defaultAvatar_head.fst b/interface/resources/meshes/defaultAvatar_head.fst index 1352652efc..34cf44f0e4 100644 --- a/interface/resources/meshes/defaultAvatar_head.fst +++ b/interface/resources/meshes/defaultAvatar_head.fst @@ -1,7 +1,7 @@ # faceshift target mapping file name= defaultAvatar_head -filename=../../../Avatars/Jelly/jellyrob_blue.fbx -texdir=../../../Avatars/Jelly +filename=defaultAvatar/head.fbx +texdir=defaultAvatar scale=80 rx=0 ry=0 diff --git a/interface/src/avatar/Avatar.cpp b/interface/src/avatar/Avatar.cpp index 761ed59db9..c56830de9f 100644 --- a/interface/src/avatar/Avatar.cpp +++ b/interface/src/avatar/Avatar.cpp @@ -345,13 +345,13 @@ bool Avatar::findSphereCollisionWithSkeleton(const glm::vec3& sphereCenter, floa void Avatar::setFaceModelURL(const QUrl &faceModelURL) { AvatarData::setFaceModelURL(faceModelURL); - const QUrl DEFAULT_FACE_MODEL_URL = QUrl::fromLocalFile("resources/meshes/defaultAvatar_head.fbx"); + const QUrl DEFAULT_FACE_MODEL_URL = QUrl::fromLocalFile("resources/meshes/defaultAvatar_head.fst"); _head.getFaceModel().setURL(_faceModelURL, DEFAULT_FACE_MODEL_URL); } void Avatar::setSkeletonModelURL(const QUrl &skeletonModelURL) { AvatarData::setSkeletonModelURL(skeletonModelURL); - const QUrl DEFAULT_SKELETON_MODEL_URL = QUrl::fromLocalFile("resources/meshes/defaultAvatar_body.fbx"); + const QUrl DEFAULT_SKELETON_MODEL_URL = QUrl::fromLocalFile("resources/meshes/defaultAvatar_body.fst"); _skeletonModel.setURL(_skeletonModelURL, DEFAULT_SKELETON_MODEL_URL); } diff --git a/interface/src/renderer/FBXReader.cpp b/interface/src/renderer/FBXReader.cpp index b4e7c4abc5..a0d5f03ae9 100644 --- a/interface/src/renderer/FBXReader.cpp +++ b/interface/src/renderer/FBXReader.cpp @@ -1568,14 +1568,16 @@ FBXGeometry extractFBXGeometry(const FBXNode& node, const QVariantHash& mapping) return geometry; } -FBXGeometry readFBX(const QByteArray& model, const QByteArray& mapping) { - QBuffer modelBuffer(const_cast(&model)); - modelBuffer.open(QIODevice::ReadOnly); +QVariantHash readMapping(const QByteArray& data) { + QBuffer buffer(const_cast(&data)); + buffer.open(QIODevice::ReadOnly); + return parseMapping(&buffer); +} - QBuffer mappingBuffer(const_cast(&mapping)); - mappingBuffer.open(QIODevice::ReadOnly); - - return extractFBXGeometry(parseFBX(&modelBuffer), parseMapping(&mappingBuffer)); +FBXGeometry readFBX(const QByteArray& model, const QVariantHash& mapping) { + QBuffer buffer(const_cast(&model)); + buffer.open(QIODevice::ReadOnly); + return extractFBXGeometry(parseFBX(&buffer), mapping); } bool addMeshVoxelsOperation(OctreeElement* element, void* extraData) { diff --git a/interface/src/renderer/FBXReader.h b/interface/src/renderer/FBXReader.h index d700439460..5e2b77035f 100644 --- a/interface/src/renderer/FBXReader.h +++ b/interface/src/renderer/FBXReader.h @@ -163,9 +163,12 @@ public: QVector attachments; }; +/// Reads an FST mapping from the supplied data. +QVariantHash readMapping(const QByteArray& data); + /// Reads FBX geometry from the supplied model and mapping data. /// \exception QString if an error occurs in parsing -FBXGeometry readFBX(const QByteArray& model, const QByteArray& mapping); +FBXGeometry readFBX(const QByteArray& model, const QVariantHash& mapping); /// Reads SVO geometry from the supplied model data. FBXGeometry readSVO(const QByteArray& model); diff --git a/interface/src/renderer/GeometryCache.cpp b/interface/src/renderer/GeometryCache.cpp index 3526fa5050..dfe6949438 100644 --- a/interface/src/renderer/GeometryCache.cpp +++ b/interface/src/renderer/GeometryCache.cpp @@ -7,11 +7,7 @@ #include -// include this before QOpenGLBuffer, which includes an earlier version of OpenGL -#include "InterfaceConfig.h" - #include -#include #include #include "Application.h" @@ -304,40 +300,23 @@ QSharedPointer GeometryCache::getGeometry(const QUrl& url, cons } NetworkGeometry::NetworkGeometry(const QUrl& url, const QSharedPointer& fallback) : - _modelRequest(url), - _modelReply(NULL), - _mappingReply(NULL), + _request(url), + _reply(NULL), + _textureBase(url), _fallback(fallback), - _attempts(0) -{ + _attempts(0) { + if (!url.isValid()) { return; } - _modelRequest.setAttribute(QNetworkRequest::CacheLoadControlAttribute, QNetworkRequest::PreferCache); - makeModelRequest(); - - QUrl mappingURL = url; - QString path = url.path(); - mappingURL.setPath(path.left(path.lastIndexOf('.')) + ".fst"); - QNetworkRequest mappingRequest(mappingURL); - mappingRequest.setAttribute(QNetworkRequest::CacheLoadControlAttribute, QNetworkRequest::PreferCache); - _mappingReply = Application::getInstance()->getNetworkAccessManager()->get(mappingRequest); - - connect(_mappingReply, SIGNAL(downloadProgress(qint64,qint64)), SLOT(maybeReadModelWithMapping())); - connect(_mappingReply, SIGNAL(error(QNetworkReply::NetworkError)), SLOT(handleMappingReplyError())); + _request.setAttribute(QNetworkRequest::CacheLoadControlAttribute, QNetworkRequest::PreferCache); + makeRequest(); } NetworkGeometry::~NetworkGeometry() { - if (_modelReply != NULL) { - delete _modelReply; + if (_reply != NULL) { + delete _reply; } - if (_mappingReply != NULL) { - delete _mappingReply; - } - foreach (const NetworkMesh& mesh, _meshes) { - glDeleteBuffers(1, &mesh.indexBufferID); - glDeleteBuffers(1, &mesh.vertexBufferID); - } } glm::vec4 NetworkGeometry::computeAverageColor() const { @@ -364,20 +343,155 @@ glm::vec4 NetworkGeometry::computeAverageColor() const { return (totalTriangles == 0) ? glm::vec4(1.0f, 1.0f, 1.0f, 1.0f) : totalColor / totalTriangles; } -void NetworkGeometry::makeModelRequest() { - _modelReply = Application::getInstance()->getNetworkAccessManager()->get(_modelRequest); +void NetworkGeometry::makeRequest() { + _reply = Application::getInstance()->getNetworkAccessManager()->get(_request); - connect(_modelReply, SIGNAL(downloadProgress(qint64,qint64)), SLOT(maybeReadModelWithMapping())); - connect(_modelReply, SIGNAL(error(QNetworkReply::NetworkError)), SLOT(handleModelReplyError())); + connect(_reply, SIGNAL(downloadProgress(qint64,qint64)), SLOT(handleDownloadProgress(qint64,qint64))); + connect(_reply, SIGNAL(error(QNetworkReply::NetworkError)), SLOT(handleReplyError())); } -void NetworkGeometry::handleModelReplyError() { - QDebug debug = qDebug() << _modelReply->errorString(); +void NetworkGeometry::handleDownloadProgress(qint64 bytesReceived, qint64 bytesTotal) { + if (!_reply->isFinished()) { + return; + } - QNetworkReply::NetworkError error = _modelReply->error(); - _modelReply->disconnect(this); - _modelReply->deleteLater(); - _modelReply = NULL; + QUrl url = _reply->url(); + QByteArray data = _reply->readAll(); + _reply->disconnect(this); + _reply->deleteLater(); + _reply = NULL; + + if (url.path().toLower().endsWith(".fst")) { + // it's a mapping file; parse it and get the mesh filename + _mapping = readMapping(data); + QString filename = _mapping.value("filename").toString(); + if (filename.isNull()) { + qDebug() << "Mapping file " << url << " has no filename."; + maybeLoadFallback(); + } else { + QString texdir = _mapping.value("texdir").toString(); + if (!texdir.isNull()) { + if (!texdir.endsWith('/')) { + texdir += '/'; + } + _textureBase = url.resolved(texdir); + } + _request.setUrl(url.resolved(filename)); + makeRequest(); + } + return; + } + + try { + _geometry = url.path().toLower().endsWith(".svo") ? readSVO(data) : readFBX(data, _mapping); + + } catch (const QString& error) { + qDebug() << "Error reading " << url << ": " << error; + maybeLoadFallback(); + return; + } + + foreach (const FBXMesh& mesh, _geometry.meshes) { + NetworkMesh networkMesh = { QOpenGLBuffer(QOpenGLBuffer::IndexBuffer), QOpenGLBuffer(QOpenGLBuffer::VertexBuffer) }; + + int totalIndices = 0; + foreach (const FBXMeshPart& part, mesh.parts) { + NetworkMeshPart networkPart; + if (!part.diffuseFilename.isEmpty()) { + networkPart.diffuseTexture = Application::getInstance()->getTextureCache()->getTexture( + _textureBase.resolved(QUrl(part.diffuseFilename)), false, mesh.isEye); + } + if (!part.normalFilename.isEmpty()) { + networkPart.normalTexture = Application::getInstance()->getTextureCache()->getTexture( + _textureBase.resolved(QUrl(part.normalFilename)), true); + } + networkMesh.parts.append(networkPart); + + totalIndices += (part.quadIndices.size() + part.triangleIndices.size()); + } + + networkMesh.indexBuffer.create(); + networkMesh.indexBuffer.bind(); + networkMesh.indexBuffer.setUsagePattern(QOpenGLBuffer::StaticDraw); + networkMesh.indexBuffer.allocate(totalIndices * sizeof(int)); + int offset = 0; + foreach (const FBXMeshPart& part, mesh.parts) { + glBufferSubData(GL_ELEMENT_ARRAY_BUFFER, offset, part.quadIndices.size() * sizeof(int), + part.quadIndices.constData()); + offset += part.quadIndices.size() * sizeof(int); + glBufferSubData(GL_ELEMENT_ARRAY_BUFFER, offset, part.triangleIndices.size() * sizeof(int), + part.triangleIndices.constData()); + offset += part.triangleIndices.size() * sizeof(int); + } + networkMesh.indexBuffer.release(); + + networkMesh.vertexBuffer.create(); + networkMesh.vertexBuffer.bind(); + networkMesh.vertexBuffer.setUsagePattern(QOpenGLBuffer::StaticDraw); + + // if we don't need to do any blending or springing, then the positions/normals can be static + if (mesh.blendshapes.isEmpty() && mesh.springiness == 0.0f) { + int normalsOffset = mesh.vertices.size() * sizeof(glm::vec3); + int tangentsOffset = normalsOffset + mesh.normals.size() * sizeof(glm::vec3); + int colorsOffset = tangentsOffset + mesh.tangents.size() * sizeof(glm::vec3); + int texCoordsOffset = colorsOffset + mesh.colors.size() * sizeof(glm::vec3); + int clusterIndicesOffset = texCoordsOffset + mesh.texCoords.size() * sizeof(glm::vec2); + int clusterWeightsOffset = clusterIndicesOffset + mesh.clusterIndices.size() * sizeof(glm::vec4); + + networkMesh.vertexBuffer.allocate(clusterWeightsOffset + mesh.clusterWeights.size() * sizeof(glm::vec4)); + networkMesh.vertexBuffer.write(0, mesh.vertices.constData(), mesh.vertices.size() * sizeof(glm::vec3)); + networkMesh.vertexBuffer.write(normalsOffset, mesh.normals.constData(), mesh.normals.size() * sizeof(glm::vec3)); + networkMesh.vertexBuffer.write(tangentsOffset, mesh.tangents.constData(), + mesh.tangents.size() * sizeof(glm::vec3)); + networkMesh.vertexBuffer.write(colorsOffset, mesh.colors.constData(), mesh.colors.size() * sizeof(glm::vec3)); + networkMesh.vertexBuffer.write(texCoordsOffset, mesh.texCoords.constData(), + mesh.texCoords.size() * sizeof(glm::vec2)); + networkMesh.vertexBuffer.write(clusterIndicesOffset, mesh.clusterIndices.constData(), + mesh.clusterIndices.size() * sizeof(glm::vec4)); + networkMesh.vertexBuffer.write(clusterWeightsOffset, mesh.clusterWeights.constData(), + mesh.clusterWeights.size() * sizeof(glm::vec4)); + + // if there's no springiness, then the cluster indices/weights can be static + } else if (mesh.springiness == 0.0f) { + int colorsOffset = mesh.tangents.size() * sizeof(glm::vec3); + int texCoordsOffset = colorsOffset + mesh.colors.size() * sizeof(glm::vec3); + int clusterIndicesOffset = texCoordsOffset + mesh.texCoords.size() * sizeof(glm::vec2); + int clusterWeightsOffset = clusterIndicesOffset + mesh.clusterIndices.size() * sizeof(glm::vec4); + networkMesh.vertexBuffer.allocate(clusterWeightsOffset + mesh.clusterWeights.size() * sizeof(glm::vec4)); + networkMesh.vertexBuffer.write(0, mesh.tangents.constData(), mesh.tangents.size() * sizeof(glm::vec3)); + networkMesh.vertexBuffer.write(colorsOffset, mesh.colors.constData(), mesh.colors.size() * sizeof(glm::vec3)); + networkMesh.vertexBuffer.write(texCoordsOffset, mesh.texCoords.constData(), + mesh.texCoords.size() * sizeof(glm::vec2)); + networkMesh.vertexBuffer.write(clusterIndicesOffset, mesh.clusterIndices.constData(), + mesh.clusterIndices.size() * sizeof(glm::vec4)); + networkMesh.vertexBuffer.write(clusterWeightsOffset, mesh.clusterWeights.constData(), + mesh.clusterWeights.size() * sizeof(glm::vec4)); + + } else { + int colorsOffset = mesh.tangents.size() * sizeof(glm::vec3); + int texCoordsOffset = colorsOffset + mesh.colors.size() * sizeof(glm::vec3); + networkMesh.vertexBuffer.allocate(texCoordsOffset + mesh.texCoords.size() * sizeof(glm::vec2)); + networkMesh.vertexBuffer.write(0, mesh.tangents.constData(), mesh.tangents.size() * sizeof(glm::vec3)); + networkMesh.vertexBuffer.write(colorsOffset, mesh.colors.constData(), mesh.colors.size() * sizeof(glm::vec3)); + networkMesh.vertexBuffer.write(texCoordsOffset, mesh.texCoords.constData(), + mesh.texCoords.size() * sizeof(glm::vec2)); + } + + networkMesh.vertexBuffer.release(); + + _meshes.append(networkMesh); + } + + emit loaded(); +} + +void NetworkGeometry::handleReplyError() { + QDebug debug = qDebug() << _reply->errorString(); + + QNetworkReply::NetworkError error = _reply->error(); + _reply->disconnect(this); + _reply->deleteLater(); + _reply = NULL; // retry for certain types of failures switch (error) { @@ -394,7 +508,7 @@ void NetworkGeometry::handleModelReplyError() { const int MAX_ATTEMPTS = 8; const int BASE_DELAY_MS = 1000; if (++_attempts < MAX_ATTEMPTS) { - QTimer::singleShot(BASE_DELAY_MS * (int)pow(2.0, _attempts), this, SLOT(makeModelRequest())); + QTimer::singleShot(BASE_DELAY_MS * (int)pow(2.0, _attempts), this, SLOT(makeRequest())); debug << " -- retrying..."; return; } @@ -407,135 +521,6 @@ void NetworkGeometry::handleModelReplyError() { } -void NetworkGeometry::handleMappingReplyError() { - _mappingReply->disconnect(this); - _mappingReply->deleteLater(); - _mappingReply = NULL; - - maybeReadModelWithMapping(); -} - -void NetworkGeometry::maybeReadModelWithMapping() { - if (_modelReply == NULL || !_modelReply->isFinished() || (_mappingReply != NULL && !_mappingReply->isFinished())) { - return; - } - - QUrl url = _modelReply->url(); - QByteArray model = _modelReply->readAll(); - _modelReply->disconnect(this); - _modelReply->deleteLater(); - _modelReply = NULL; - - QByteArray mapping; - if (_mappingReply != NULL) { - mapping = _mappingReply->readAll(); - _mappingReply->disconnect(this); - _mappingReply->deleteLater(); - _mappingReply = NULL; - } - - try { - _geometry = url.path().toLower().endsWith(".svo") ? readSVO(model) : readFBX(model, mapping); - - } catch (const QString& error) { - qDebug() << "Error reading " << url << ": " << error; - maybeLoadFallback(); - return; - } - - foreach (const FBXMesh& mesh, _geometry.meshes) { - NetworkMesh networkMesh; - - int totalIndices = 0; - foreach (const FBXMeshPart& part, mesh.parts) { - NetworkMeshPart networkPart; - QString basePath = url.path(); - basePath = basePath.left(basePath.lastIndexOf('/') + 1); - if (!part.diffuseFilename.isEmpty()) { - url.setPath(basePath + part.diffuseFilename); - networkPart.diffuseTexture = Application::getInstance()->getTextureCache()->getTexture(url, false, mesh.isEye); - } - if (!part.normalFilename.isEmpty()) { - url.setPath(basePath + part.normalFilename); - networkPart.normalTexture = Application::getInstance()->getTextureCache()->getTexture(url, true); - } - networkMesh.parts.append(networkPart); - - totalIndices += (part.quadIndices.size() + part.triangleIndices.size()); - } - - glGenBuffers(1, &networkMesh.indexBufferID); - glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, networkMesh.indexBufferID); - glBufferData(GL_ELEMENT_ARRAY_BUFFER, totalIndices * sizeof(int), NULL, GL_STATIC_DRAW); - int offset = 0; - foreach (const FBXMeshPart& part, mesh.parts) { - glBufferSubData(GL_ELEMENT_ARRAY_BUFFER, offset, part.quadIndices.size() * sizeof(int), - part.quadIndices.constData()); - offset += part.quadIndices.size() * sizeof(int); - glBufferSubData(GL_ELEMENT_ARRAY_BUFFER, offset, part.triangleIndices.size() * sizeof(int), - part.triangleIndices.constData()); - offset += part.triangleIndices.size() * sizeof(int); - } - glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0); - - glGenBuffers(1, &networkMesh.vertexBufferID); - glBindBuffer(GL_ARRAY_BUFFER, networkMesh.vertexBufferID); - - // if we don't need to do any blending or springing, then the positions/normals can be static - if (mesh.blendshapes.isEmpty() && mesh.springiness == 0.0f) { - int normalsOffset = mesh.vertices.size() * sizeof(glm::vec3); - int tangentsOffset = normalsOffset + mesh.normals.size() * sizeof(glm::vec3); - int colorsOffset = tangentsOffset + mesh.tangents.size() * sizeof(glm::vec3); - int texCoordsOffset = colorsOffset + mesh.colors.size() * sizeof(glm::vec3); - int clusterIndicesOffset = texCoordsOffset + mesh.texCoords.size() * sizeof(glm::vec2); - int clusterWeightsOffset = clusterIndicesOffset + mesh.clusterIndices.size() * sizeof(glm::vec4); - glBufferData(GL_ARRAY_BUFFER, clusterWeightsOffset + mesh.clusterWeights.size() * sizeof(glm::vec4), - NULL, GL_STATIC_DRAW); - glBufferSubData(GL_ARRAY_BUFFER, 0, mesh.vertices.size() * sizeof(glm::vec3), mesh.vertices.constData()); - glBufferSubData(GL_ARRAY_BUFFER, normalsOffset, mesh.normals.size() * sizeof(glm::vec3), mesh.normals.constData()); - glBufferSubData(GL_ARRAY_BUFFER, tangentsOffset, mesh.tangents.size() * sizeof(glm::vec3), mesh.tangents.constData()); - glBufferSubData(GL_ARRAY_BUFFER, colorsOffset, mesh.colors.size() * sizeof(glm::vec3), mesh.colors.constData()); - glBufferSubData(GL_ARRAY_BUFFER, texCoordsOffset, mesh.texCoords.size() * sizeof(glm::vec2), - mesh.texCoords.constData()); - glBufferSubData(GL_ARRAY_BUFFER, clusterIndicesOffset, mesh.clusterIndices.size() * sizeof(glm::vec4), - mesh.clusterIndices.constData()); - glBufferSubData(GL_ARRAY_BUFFER, clusterWeightsOffset, mesh.clusterWeights.size() * sizeof(glm::vec4), - mesh.clusterWeights.constData()); - - // if there's no springiness, then the cluster indices/weights can be static - } else if (mesh.springiness == 0.0f) { - int colorsOffset = mesh.tangents.size() * sizeof(glm::vec3); - int texCoordsOffset = colorsOffset + mesh.colors.size() * sizeof(glm::vec3); - int clusterIndicesOffset = texCoordsOffset + mesh.texCoords.size() * sizeof(glm::vec2); - int clusterWeightsOffset = clusterIndicesOffset + mesh.clusterIndices.size() * sizeof(glm::vec4); - glBufferData(GL_ARRAY_BUFFER, clusterWeightsOffset + mesh.clusterWeights.size() * sizeof(glm::vec4), - NULL, GL_STATIC_DRAW); - glBufferSubData(GL_ARRAY_BUFFER, 0, mesh.tangents.size() * sizeof(glm::vec3), mesh.tangents.constData()); - glBufferSubData(GL_ARRAY_BUFFER, colorsOffset, mesh.colors.size() * sizeof(glm::vec3), mesh.colors.constData()); - glBufferSubData(GL_ARRAY_BUFFER, texCoordsOffset, mesh.texCoords.size() * sizeof(glm::vec2), mesh.texCoords.constData()); - glBufferSubData(GL_ARRAY_BUFFER, clusterIndicesOffset, mesh.clusterIndices.size() * sizeof(glm::vec4), - mesh.clusterIndices.constData()); - glBufferSubData(GL_ARRAY_BUFFER, clusterWeightsOffset, mesh.clusterWeights.size() * sizeof(glm::vec4), - mesh.clusterWeights.constData()); - - } else { - int colorsOffset = mesh.tangents.size() * sizeof(glm::vec3); - int texCoordsOffset = colorsOffset + mesh.colors.size() * sizeof(glm::vec3); - glBufferData(GL_ARRAY_BUFFER, texCoordsOffset + mesh.texCoords.size() * sizeof(glm::vec2), NULL, GL_STATIC_DRAW); - glBufferSubData(GL_ARRAY_BUFFER, 0, mesh.tangents.size() * sizeof(glm::vec3), mesh.tangents.constData()); - glBufferSubData(GL_ARRAY_BUFFER, colorsOffset, mesh.colors.size() * sizeof(glm::vec3), mesh.colors.constData()); - glBufferSubData(GL_ARRAY_BUFFER, texCoordsOffset, mesh.texCoords.size() * sizeof(glm::vec2), - mesh.texCoords.constData()); - } - - glBindBuffer(GL_ARRAY_BUFFER, 0); - - _meshes.append(networkMesh); - } - - emit loaded(); -} - void NetworkGeometry::loadFallback() { _geometry = _fallback->_geometry; _meshes = _fallback->_meshes; diff --git a/interface/src/renderer/GeometryCache.h b/interface/src/renderer/GeometryCache.h index 618796e907..0587831721 100644 --- a/interface/src/renderer/GeometryCache.h +++ b/interface/src/renderer/GeometryCache.h @@ -9,17 +9,19 @@ #ifndef __interface__GeometryCache__ #define __interface__GeometryCache__ +// include this before QOpenGLBuffer, which includes an earlier version of OpenGL +#include "InterfaceConfig.h" + #include #include #include +#include #include #include #include "FBXReader.h" -#include "InterfaceConfig.h" class QNetworkReply; -class QOpenGLBuffer; class NetworkGeometry; class NetworkMesh; @@ -76,19 +78,19 @@ signals: private slots: - void makeModelRequest(); - void handleModelReplyError(); - void handleMappingReplyError(); - void maybeReadModelWithMapping(); + void makeRequest(); + void handleDownloadProgress(qint64 bytesReceived, qint64 bytesTotal); + void handleReplyError(); void loadFallback(); private: void maybeLoadFallback(); - QNetworkRequest _modelRequest; - QNetworkReply* _modelReply; - QNetworkReply* _mappingReply; + QNetworkRequest _request; + QNetworkReply* _reply; + QVariantHash _mapping; + QUrl _textureBase; QSharedPointer _fallback; int _attempts; @@ -110,8 +112,8 @@ public: class NetworkMesh { public: - GLuint indexBufferID; - GLuint vertexBufferID; + QOpenGLBuffer indexBuffer; + QOpenGLBuffer vertexBuffer; QVector parts; diff --git a/interface/src/renderer/Model.cpp b/interface/src/renderer/Model.cpp index b14ed1036d..095429f201 100644 --- a/interface/src/renderer/Model.cpp +++ b/interface/src/renderer/Model.cpp @@ -768,13 +768,13 @@ void Model::renderMeshes(float alpha, bool translucent) { (networkMesh.getTranslucentPartCount() == networkMesh.parts.size())) { continue; } - glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, networkMesh.indexBufferID); - + const_cast(networkMesh.indexBuffer).bind(); + const FBXMesh& mesh = geometry.meshes.at(i); int vertexCount = mesh.vertices.size(); - glBindBuffer(GL_ARRAY_BUFFER, networkMesh.vertexBufferID); - + const_cast(networkMesh.vertexBuffer).bind(); + ProgramObject* program = &_program; ProgramObject* skinProgram = &_skinProgram; SkinLocations* skinLocations = &_skinLocations; From 3c535e6c9c28b7e19cddffa308ac516c37974b29 Mon Sep 17 00:00:00 2001 From: Andrzej Kapolka Date: Tue, 11 Feb 2014 14:38:44 -0800 Subject: [PATCH 05/22] These need to be FST files, too. --- libraries/avatars/src/AvatarData.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/libraries/avatars/src/AvatarData.h b/libraries/avatars/src/AvatarData.h index 08604a95f1..6492d9b7ad 100755 --- a/libraries/avatars/src/AvatarData.h +++ b/libraries/avatars/src/AvatarData.h @@ -52,8 +52,8 @@ static const float MIN_AVATAR_SCALE = .005f; const float MAX_AUDIO_LOUDNESS = 1000.0; // close enough for mouth animation -const QUrl DEFAULT_HEAD_MODEL_URL = QUrl("http://public.highfidelity.io/meshes/defaultAvatar_head.fbx"); -const QUrl DEFAULT_BODY_MODEL_URL = QUrl("http://public.highfidelity.io/meshes/defaultAvatar_body.fbx"); +const QUrl DEFAULT_HEAD_MODEL_URL = QUrl("http://public.highfidelity.io/meshes/defaultAvatar_head.fst"); +const QUrl DEFAULT_BODY_MODEL_URL = QUrl("http://public.highfidelity.io/meshes/defaultAvatar_body.fst"); enum KeyState { NO_KEY_DOWN = 0, From d6b71f19a8dd075d45195ddfc05c617c7f40f8e4 Mon Sep 17 00:00:00 2001 From: ZappoMan Date: Wed, 12 Feb 2014 18:00:09 -0800 Subject: [PATCH 06/22] fix voxel texture to work better with larger TREE_SCALE --- interface/resources/shaders/perlin_modulate.frag | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/interface/resources/shaders/perlin_modulate.frag b/interface/resources/shaders/perlin_modulate.frag index eea0da3671..8ead57c238 100644 --- a/interface/resources/shaders/perlin_modulate.frag +++ b/interface/resources/shaders/perlin_modulate.frag @@ -12,7 +12,7 @@ uniform sampler2D permutationNormalTexture; // the noise frequency -const float frequency = 1024.0; +const float frequency = 65536.0; // looks better with current TREE_SCALE, was 1024 when TREE_SCALE was either 512 or 128 // the noise amplitude const float amplitude = 0.1; From 20a6f4eea95e826a23d74ab17d275ac0d52f930d Mon Sep 17 00:00:00 2001 From: ZappoMan Date: Wed, 12 Feb 2014 21:22:21 -0800 Subject: [PATCH 07/22] first cut at auto-LOD adjustment --- interface/src/Application.cpp | 7 +++- interface/src/Menu.cpp | 31 +++++++++++++-- interface/src/Menu.h | 14 +++++++ interface/src/VoxelSystem.cpp | 61 ++++++++++++++++++++--------- interface/src/VoxelSystem.h | 14 ++++--- interface/src/ui/LodToolsDialog.cpp | 6 +++ interface/src/ui/LodToolsDialog.h | 1 + libraries/shared/src/PerfStat.h | 2 + 8 files changed, 108 insertions(+), 28 deletions(-) diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 004b71074b..29d838e10b 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -2720,7 +2720,10 @@ void Application::displaySide(Camera& whichCamera, bool selfAvatarOnly) { PerformanceWarning warn(Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings), "Application::displaySide() ... voxels..."); if (!Menu::getInstance()->isOptionChecked(MenuOption::DontRenderVoxels)) { - _voxels.render(Menu::getInstance()->isOptionChecked(MenuOption::VoxelTextures)); + _voxels.render(); + + // double check that our LOD doesn't need to be auto-adjusted + Menu::getInstance()->autoAdjustLOD(_fps); } } @@ -2811,7 +2814,7 @@ void Application::displaySide(Camera& whichCamera, bool selfAvatarOnly) { _mouseVoxel.s, _mouseVoxel.s); - _sharedVoxelSystem.render(true); + _sharedVoxelSystem.render(); glPopMatrix(); } } diff --git a/interface/src/Menu.cpp b/interface/src/Menu.cpp index 7eb5807c6f..89277c794c 100644 --- a/interface/src/Menu.cpp +++ b/interface/src/Menu.cpp @@ -70,7 +70,8 @@ Menu::Menu() : _maxVoxels(DEFAULT_MAX_VOXELS_PER_SYSTEM), _voxelSizeScale(DEFAULT_OCTREE_SIZE_SCALE), _boundaryLevelAdjust(0), - _maxVoxelPacketsPerSecond(DEFAULT_MAX_VOXEL_PPS) + _maxVoxelPacketsPerSecond(DEFAULT_MAX_VOXEL_PPS), + _lastAdjust(usecTimestampNow()) { Application *appInstance = Application::getInstance(); @@ -1096,14 +1097,38 @@ void Menu::voxelStatsDetailsClosed() { } } +void Menu::autoAdjustLOD(float currentFPS) { + bool changed = false; + quint64 now = usecTimestampNow(); + quint64 elapsed = now - _lastAdjust; + + if (elapsed > ADJUST_LOD_DOWN_DELAY && currentFPS < ADJUST_LOD_DOWN_FPS && _voxelSizeScale > ADJUST_LOD_MIN_SIZE_SCALE) { + _voxelSizeScale *= ADJUST_LOD_DOWN_BY; + changed = true; + _lastAdjust = now; + qDebug() << "adjusting LOD down... currentFPS=" << currentFPS << "_voxelSizeScale=" << _voxelSizeScale; + } + + if (elapsed > ADJUST_LOD_UP_DELAY && currentFPS > ADJUST_LOD_UP_FPS && _voxelSizeScale < ADJUST_LOD_MAX_SIZE_SCALE) { + _voxelSizeScale *= ADJUST_LOD_UP_BY; + changed = true; + _lastAdjust = now; + qDebug() << "adjusting LOD up... currentFPS=" << currentFPS << "_voxelSizeScale=" << _voxelSizeScale; + } + + if (changed) { + if (_lodToolsDialog) { + _lodToolsDialog->reloadSliders(); + } + } +} + void Menu::setVoxelSizeScale(float sizeScale) { _voxelSizeScale = sizeScale; - Application::getInstance()->getVoxels()->redrawInViewVoxels(); } void Menu::setBoundaryLevelAdjust(int boundaryLevelAdjust) { _boundaryLevelAdjust = boundaryLevelAdjust; - Application::getInstance()->getVoxels()->redrawInViewVoxels(); } void Menu::lodTools() { diff --git a/interface/src/Menu.h b/interface/src/Menu.h index 742b9fc66f..fd37856b26 100644 --- a/interface/src/Menu.h +++ b/interface/src/Menu.h @@ -16,6 +16,18 @@ #include +const float ADJUST_LOD_DOWN_FPS = 40.0; +const float ADJUST_LOD_UP_FPS = 55.0; + +const quint64 ADJUST_LOD_DOWN_DELAY = 1000 * 1000 * 5; +const quint64 ADJUST_LOD_UP_DELAY = ADJUST_LOD_DOWN_DELAY * 2; + +const float ADJUST_LOD_DOWN_BY = 0.9f; +const float ADJUST_LOD_UP_BY = 1.1f; + +const float ADJUST_LOD_MIN_SIZE_SCALE = TREE_SCALE * 1.0f; +const float ADJUST_LOD_MAX_SIZE_SCALE = DEFAULT_OCTREE_SIZE_SCALE; + enum FrustumDrawMode { FRUSTUM_DRAW_MODE_ALL, FRUSTUM_DRAW_MODE_VECTORS, @@ -68,6 +80,7 @@ public: void handleViewFrustumOffsetKeyModifier(int key); // User Tweakable LOD Items + void autoAdjustLOD(float currentFPS); void setVoxelSizeScale(float sizeScale); float getVoxelSizeScale() const { return _voxelSizeScale; } void setBoundaryLevelAdjust(int boundaryLevelAdjust); @@ -154,6 +167,7 @@ private: int _maxVoxelPacketsPerSecond; QMenu* _activeScriptsMenu; QString replaceLastOccurrence(QChar search, QChar replace, QString string); + quint64 _lastAdjust; }; namespace MenuOption { diff --git a/interface/src/VoxelSystem.cpp b/interface/src/VoxelSystem.cpp index 1ffc6c3e5e..a4e47138d1 100644 --- a/interface/src/VoxelSystem.cpp +++ b/interface/src/VoxelSystem.cpp @@ -99,6 +99,9 @@ VoxelSystem::VoxelSystem(float treeScale, int maxVoxels) _culledOnce = false; _inhideOutOfView = false; + + _lastKnownVoxelSizeScale = DEFAULT_OCTREE_SIZE_SCALE; + _lastKnownBoundaryLevelAdjust = 0; } void VoxelSystem::elementDeleted(OctreeElement* element) { @@ -249,6 +252,9 @@ VoxelSystem::~VoxelSystem() { delete _tree; } + +// This is called by the main application thread on both the initialization of the application and when +// the preferences dialog box is called/saved void VoxelSystem::setMaxVoxels(int maxVoxels) { if (maxVoxels == _maxVoxels) { return; @@ -267,6 +273,8 @@ void VoxelSystem::setMaxVoxels(int maxVoxels) { } } +// This is called by the main application thread on both the initialization of the application and when +// the use voxel shader menu item is chosen void VoxelSystem::setUseVoxelShader(bool useVoxelShader) { if (_useVoxelShader == useVoxelShader) { return; @@ -330,7 +338,7 @@ void VoxelSystem::setVoxelsAsPoints(bool voxelsAsPoints) { void VoxelSystem::cleanupVoxelMemory() { if (_initialized) { - _bufferWriteLock.lock(); + _readArraysLock.lockForWrite(); _initialized = false; // no longer initialized if (_useVoxelShader) { // these are used when in VoxelShader mode. @@ -368,7 +376,7 @@ void VoxelSystem::cleanupVoxelMemory() { delete[] _writeVoxelDirtyArray; delete[] _readVoxelDirtyArray; _writeVoxelDirtyArray = _readVoxelDirtyArray = NULL; - _bufferWriteLock.unlock(); + _readArraysLock.unlock(); } } @@ -401,7 +409,7 @@ void VoxelSystem::setupFaceIndices(GLuint& faceVBOID, GLubyte faceIdentityIndice } void VoxelSystem::initVoxelMemory() { - _bufferWriteLock.lock(); + _readArraysLock.lockForWrite(); _memoryUsageRAM = 0; _memoryUsageVBO = 0; // our VBO allocations as we know them @@ -516,7 +524,7 @@ void VoxelSystem::initVoxelMemory() { _initialized = true; - _bufferWriteLock.unlock(); + _readArraysLock.unlock(); } void VoxelSystem::writeToSVOFile(const char* filename, VoxelTreeElement* element) const { @@ -646,7 +654,7 @@ void VoxelSystem::setupNewVoxelsForDrawing() { } _inSetupNewVoxelsForDrawing = true; - + bool didWriteFullVBO = _writeRenderFullVBO; if (_tree->isDirty()) { static char buffer[64] = { 0 }; @@ -673,7 +681,7 @@ void VoxelSystem::setupNewVoxelsForDrawing() { } // lock on the buffer write lock so we can't modify the data when the GPU is reading it - _bufferWriteLock.lock(); + _readArraysLock.lockForWrite(); if (_voxelsUpdated) { _voxelsDirty=true; @@ -682,7 +690,7 @@ void VoxelSystem::setupNewVoxelsForDrawing() { // copy the newly written data to the arrays designated for reading, only does something if _voxelsDirty && _voxelsUpdated copyWrittenDataToReadArrays(didWriteFullVBO); - _bufferWriteLock.unlock(); + _readArraysLock.unlock(); quint64 end = usecTimestampNow(); int elapsedmsec = (end - start) / 1000; @@ -713,8 +721,8 @@ void VoxelSystem::setupNewVoxelsForDrawingSingleNode(bool allowBailEarly) { // lock on the buffer write lock so we can't modify the data when the GPU is reading it { PerformanceWarning warn(Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings), - "setupNewVoxelsForDrawingSingleNode()... _bufferWriteLock.lock();" ); - _bufferWriteLock.lock(); + "setupNewVoxelsForDrawingSingleNode()... _readArraysLock.lockForWrite();" ); + _readArraysLock.lockForWrite(); } _voxelsDirty = true; // if we got this far, then we can assume some voxels are dirty @@ -725,7 +733,7 @@ void VoxelSystem::setupNewVoxelsForDrawingSingleNode(bool allowBailEarly) { // after... _voxelsUpdated = 0; - _bufferWriteLock.unlock(); + _readArraysLock.unlock(); quint64 end = usecTimestampNow(); int elapsedmsec = (end - start) / 1000; @@ -733,8 +741,12 @@ void VoxelSystem::setupNewVoxelsForDrawingSingleNode(bool allowBailEarly) { _setupNewVoxelsForDrawingLastElapsed = elapsedmsec; } -void VoxelSystem::checkForCulling() { +void VoxelSystem::recreateVoxelGeometryInView() { + // this is a temporary solution... we need a full implementation that actually does a full redraw into clean VBOs + hideOutOfView(true); +} +void VoxelSystem::checkForCulling() { PerformanceWarning warn(Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings), "checkForCulling()"); quint64 start = usecTimestampNow(); @@ -762,7 +774,20 @@ void VoxelSystem::checkForCulling() { _hasRecentlyChanged = false; } - hideOutOfView(forceFullFrustum); + // This would be a good place to do a special processing pass, for example, switching the LOD of the scene + bool fullRedraw = (_lastKnownVoxelSizeScale != Menu::getInstance()->getVoxelSizeScale() || + _lastKnownBoundaryLevelAdjust != Menu::getInstance()->getBoundaryLevelAdjust()); + + // track that these values + _lastKnownVoxelSizeScale = Menu::getInstance()->getVoxelSizeScale(); + _lastKnownBoundaryLevelAdjust = Menu::getInstance()->getBoundaryLevelAdjust(); + + if (fullRedraw) { + // this will remove all old geometry and recreate the correct geometry for all in view voxels + recreateVoxelGeometryInView(); + } else { + hideOutOfView(forceFullFrustum); + } if (forceFullFrustum) { quint64 endViewCulling = usecTimestampNow(); @@ -1197,7 +1222,8 @@ void VoxelSystem::updateVBOSegment(glBufferIndex segmentStart, glBufferIndex seg } } -void VoxelSystem::render(bool texture) { +void VoxelSystem::render() { + bool texture = Menu::getInstance()->isOptionChecked(MenuOption::VoxelTextures); bool showWarnings = Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings); PerformanceWarning warn(showWarnings, "render()"); @@ -1404,11 +1430,7 @@ void VoxelSystem::killLocalVoxels() { setupNewVoxelsForDrawing(); } -void VoxelSystem::redrawInViewVoxels() { - hideOutOfView(true); -} - - +// only called on main thread bool VoxelSystem::clearAllNodesBufferIndexOperation(OctreeElement* element, void* extraData) { _nodeCount++; VoxelTreeElement* voxel = (VoxelTreeElement*)element; @@ -1416,12 +1438,15 @@ bool VoxelSystem::clearAllNodesBufferIndexOperation(OctreeElement* element, void return true; } +// only called on main thread, and also always followed by a call to cleanupVoxelMemory() +// you shouldn't be calling this on any other thread or without also cleaning up voxel memory void VoxelSystem::clearAllNodesBufferIndex() { PerformanceWarning warn(Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings), "VoxelSystem::clearAllNodesBufferIndex()"); _nodeCount = 0; _tree->lockForRead(); // we won't change the tree so it's ok to treat this as a read _tree->recurseTreeWithOperation(clearAllNodesBufferIndexOperation); + clearFreeBufferIndexes(); // this should be called too _tree->unlock(); if (Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings)) { qDebug("clearing buffer index of %d nodes", _nodeCount); diff --git a/interface/src/VoxelSystem.h b/interface/src/VoxelSystem.h index d1404668bf..c2f60d6699 100644 --- a/interface/src/VoxelSystem.h +++ b/interface/src/VoxelSystem.h @@ -53,7 +53,7 @@ public: virtual void init(); void simulate(float deltaTime) { } - void render(bool texture); + void render(); void changeTree(VoxelTree* newTree); VoxelTree* getTree() const { return _tree; } @@ -79,7 +79,6 @@ public: unsigned long getVoxelMemoryUsageGPU(); void killLocalVoxels(); - void redrawInViewVoxels(); virtual void removeOutOfView(); virtual void hideOutOfView(bool forceFullFrustum = false); @@ -151,6 +150,7 @@ protected: static const bool DONT_BAIL_EARLY; // by default we will bail early, if you want to force not bailing, then use this void setupNewVoxelsForDrawingSingleNode(bool allowBailEarly = true); void checkForCulling(); + void recreateVoxelGeometryInView(); glm::vec3 computeVoxelVertex(const glm::vec3& startVertex, float voxelScale, int index) const; @@ -211,6 +211,10 @@ private: GLfloat* _readVerticesArray; GLubyte* _readColorsArray; + + QReadWriteLock _readArraysLock; + + GLfloat* _writeVerticesArray; GLubyte* _writeColorsArray; bool* _writeVoxelDirtyArray; @@ -253,9 +257,6 @@ private: GLuint _vboIndicesFront; GLuint _vboIndicesBack; - QMutex _bufferWriteLock; - QMutex _treeLock; - ViewFrustum _lastKnownViewFrustum; ViewFrustum _lastStableViewFrustum; ViewFrustum* _viewFrustum; @@ -299,6 +300,9 @@ private: bool _useFastVoxelPipeline; bool _inhideOutOfView; + + float _lastKnownVoxelSizeScale; + int _lastKnownBoundaryLevelAdjust; }; #endif diff --git a/interface/src/ui/LodToolsDialog.cpp b/interface/src/ui/LodToolsDialog.cpp index 788f7e5561..4cf4a29bf1 100644 --- a/interface/src/ui/LodToolsDialog.cpp +++ b/interface/src/ui/LodToolsDialog.cpp @@ -121,6 +121,12 @@ LodToolsDialog::~LodToolsDialog() { delete _boundaryLevelAdjust; } +void LodToolsDialog::reloadSliders() { + _lodSize->setValue(Menu::getInstance()->getVoxelSizeScale() / TREE_SCALE); + _boundaryLevelAdjust->setValue(Menu::getInstance()->getBoundaryLevelAdjust()); + _feedback->setText(getFeedbackText()); +} + void LodToolsDialog::sizeScaleValueChanged(int value) { float realValue = value * TREE_SCALE; Menu::getInstance()->setVoxelSizeScale(realValue); diff --git a/interface/src/ui/LodToolsDialog.h b/interface/src/ui/LodToolsDialog.h index ee14196188..ee96cffd7e 100644 --- a/interface/src/ui/LodToolsDialog.h +++ b/interface/src/ui/LodToolsDialog.h @@ -28,6 +28,7 @@ public slots: void sizeScaleValueChanged(int value); void boundaryLevelValueChanged(int value); void resetClicked(bool checked); + void reloadSliders(); protected: diff --git a/libraries/shared/src/PerfStat.h b/libraries/shared/src/PerfStat.h index fffb095021..a7a10b97b8 100644 --- a/libraries/shared/src/PerfStat.h +++ b/libraries/shared/src/PerfStat.h @@ -45,6 +45,8 @@ public: _alwaysDisplay(alwaysDisplay), _runningTotal(runningTotal), _totalCalls(totalCalls) { } + + quint64 elapsed() const { return (usecTimestampNow() - _start); }; ~PerformanceWarning(); From cac67d7baa0669f46864cce742b47e3099d6f4fd Mon Sep 17 00:00:00 2001 From: ZappoMan Date: Thu, 13 Feb 2014 13:06:45 -0800 Subject: [PATCH 08/22] add proper readWriteLocks around the VBO arrays to preven random triangles --- interface/src/VoxelSystem.cpp | 160 ++++++++++++++++++++++++++++++---- interface/src/VoxelSystem.h | 2 + 2 files changed, 144 insertions(+), 18 deletions(-) diff --git a/interface/src/VoxelSystem.cpp b/interface/src/VoxelSystem.cpp index a4e47138d1..851c24c5ce 100644 --- a/interface/src/VoxelSystem.cpp +++ b/interface/src/VoxelSystem.cpp @@ -57,9 +57,12 @@ GLubyte identityIndicesBack[] = { 4, 5, 6, 4, 6, 7 }; VoxelSystem::VoxelSystem(float treeScale, int maxVoxels) : NodeData(), - _treeScale(treeScale), - _maxVoxels(maxVoxels), - _initialized(false) { + _treeScale(treeScale), + _maxVoxels(maxVoxels), + _initialized(false), + _writeArraysLock(QReadWriteLock::Recursive), + _readArraysLock(QReadWriteLock::Recursive) + { _voxelsInReadArrays = _voxelsInWriteArrays = _voxelsUpdated = 0; _writeRenderFullVBO = true; @@ -124,8 +127,16 @@ void VoxelSystem::setDisableFastVoxelPipeline(bool disableFastVoxelPipeline) { void VoxelSystem::elementUpdated(OctreeElement* element) { VoxelTreeElement* voxel = (VoxelTreeElement*)element; +//qDebug() << "VoxelSystem::elementUpdated()..."; + // If we're in SetupNewVoxelsForDrawing() or _writeRenderFullVBO then bail.. if (!_useFastVoxelPipeline || _inSetupNewVoxelsForDrawing || _writeRenderFullVBO) { +/* +qDebug() << "VoxelSystem::elementUpdated()... BAILING!!! " + << "_writeRenderFullVBO="<< _writeRenderFullVBO + << "_inSetupNewVoxelsForDrawing="<< _inSetupNewVoxelsForDrawing + << "_useFastVoxelPipeline="<< _useFastVoxelPipeline; +*/ return; } @@ -136,6 +147,8 @@ void VoxelSystem::elementUpdated(OctreeElement* element) { int boundaryLevelAdjust = Menu::getInstance()->getBoundaryLevelAdjust(); shouldRender = voxel->calculateShouldRender(_viewFrustum, voxelSizeScale, boundaryLevelAdjust); +//qDebug() << "VoxelSystem::elementUpdated()... recalcing should render!!"; + if (voxel->getShouldRender() != shouldRender) { voxel->setShouldRender(shouldRender); } @@ -163,11 +176,13 @@ void VoxelSystem::elementUpdated(OctreeElement* element) { const bool REUSE_INDEX = true; const bool DONT_FORCE_REDRAW = false; +//qDebug() << "VoxelSystem::elementUpdated()... calling updateNodeInArrays()!!!"; updateNodeInArrays(voxel, REUSE_INDEX, DONT_FORCE_REDRAW); _voxelsUpdated++; voxel->clearDirtyBit(); // clear the dirty bit, do this before we potentially delete things. +//qDebug() << "VoxelSystem::elementUpdated()... calling setupNewVoxelsForDrawingSingleNode()!!!"; setupNewVoxelsForDrawingSingleNode(); } } @@ -409,7 +424,8 @@ void VoxelSystem::setupFaceIndices(GLuint& faceVBOID, GLubyte faceIdentityIndice } void VoxelSystem::initVoxelMemory() { - _readArraysLock.lockForWrite(); + //_readArraysLock.lockForWrite(); + //_writeArraysLock.lockForWrite(); _memoryUsageRAM = 0; _memoryUsageVBO = 0; // our VBO allocations as we know them @@ -524,7 +540,8 @@ void VoxelSystem::initVoxelMemory() { _initialized = true; - _readArraysLock.unlock(); + //_writeArraysLock.unlock(); + //_readArraysLock.unlock(); } void VoxelSystem::writeToSVOFile(const char* filename, VoxelTreeElement* element) const { @@ -741,9 +758,90 @@ void VoxelSystem::setupNewVoxelsForDrawingSingleNode(bool allowBailEarly) { _setupNewVoxelsForDrawingLastElapsed = elapsedmsec; } + + +class recreateVoxelGeometryInViewArgs { +public: + VoxelSystem* thisVoxelSystem; + ViewFrustum thisViewFrustum; + unsigned long nodesScanned; + float voxelSizeScale; + int boundaryLevelAdjust; + + recreateVoxelGeometryInViewArgs(VoxelSystem* voxelSystem) : + thisVoxelSystem(voxelSystem), + thisViewFrustum(*voxelSystem->getViewFrustum()), + nodesScanned(0), + voxelSizeScale(Menu::getInstance()->getVoxelSizeScale()), + boundaryLevelAdjust(Menu::getInstance()->getBoundaryLevelAdjust()) + { + } +}; + +// The goal of this operation is to remove any old references to old geometry, and if the voxel +// should be visible, create new geometry for it. +bool VoxelSystem::recreateVoxelGeometryInViewOperation(OctreeElement* element, void* extraData) { + VoxelTreeElement* voxel = (VoxelTreeElement*)element; + recreateVoxelGeometryInViewArgs* args = (recreateVoxelGeometryInViewArgs*)extraData; + + args->nodesScanned++; + + // reset the old geometry... + // note: this doesn't "mark the voxel as changed", so it only releases the old buffer index thereby forgetting the + // old geometry + voxel->setBufferIndex(GLBUFFER_INDEX_UNKNOWN); + + bool shouldRender = voxel->calculateShouldRender(&args->thisViewFrustum, args->voxelSizeScale, args->boundaryLevelAdjust); + bool inView = voxel->isInView(args->thisViewFrustum); + voxel->setShouldRender(inView && shouldRender); + + if (shouldRender) { + bool falseColorize = false; + if (falseColorize) { + voxel->setFalseColor(0,0,255); // false colorize + } + // These are both needed to force redraw... + voxel->setDirtyBit(); + qDebug() << "recreateVoxelGeometryInViewOperation()... calling voxel->markWithChangedTime()"; + voxel->markWithChangedTime(); // this will notifyUpdateHooks, which will result in our geometry being created + } + + return true; // keep recursing! +} + + +// TODO: does cleanupRemovedVoxels() ever get called? +// TODO: other than cleanupRemovedVoxels() is there anyplace we attempt to detect too many abandoned slots??? void VoxelSystem::recreateVoxelGeometryInView() { - // this is a temporary solution... we need a full implementation that actually does a full redraw into clean VBOs - hideOutOfView(true); + +qDebug() << "recreateVoxelGeometryInView()..."; + + recreateVoxelGeometryInViewArgs args(this); + _writeArraysLock.lockForWrite(); // don't let anyone read or write our write arrays until we're done + _tree->lockForRead(); // don't let anyone change our tree structure until we're run + + // reset our write arrays bookkeeping to think we've got no voxels in it + clearFreeBufferIndexes(); // does this do everything we need? + /** + // clear out all our abandoned indexes - they are all available + // TODO: is it possible freeBufferIndex() will get called??? + _voxelsInWriteArrays = 0; + _freeIndexLock.lock(); + _freeIndexes.clear(); + _freeIndexLock.unlock(); + **/ + + //_voxelsUpdated = 0; // ???? + //_writeRenderFullVBO = true; + + // do we need to reset out _writeVoxelDirtyArray arrays?? + memset(_writeVoxelDirtyArray, false, _maxVoxels * sizeof(bool)); + + + + _tree->recurseTreeWithOperation(recreateVoxelGeometryInViewOperation,(void*)&args); + _tree->unlock(); + _writeArraysLock.unlock(); } void VoxelSystem::checkForCulling() { @@ -784,7 +882,8 @@ void VoxelSystem::checkForCulling() { if (fullRedraw) { // this will remove all old geometry and recreate the correct geometry for all in view voxels - recreateVoxelGeometryInView(); + //recreateVoxelGeometryInView(); + hideOutOfView(forceFullFrustum); } else { hideOutOfView(forceFullFrustum); } @@ -905,12 +1004,26 @@ void VoxelSystem::copyWrittenDataToReadArrays(bool fullVBOs) { PerformanceWarning warn(Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings), "copyWrittenDataToReadArrays()"); - if (_voxelsDirty && _voxelsUpdated) { - if (fullVBOs) { - copyWrittenDataToReadArraysFullVBOs(); + // attempt to get the writeArraysLock for reading and the readArraysLock for writing + // so we can copy from the write to the read... if we fail, that's ok, we'll get it the next + // time around, the only side effect is the VBOs won't be updated this frame + const int WAIT_FOR_LOCK_IN_MS = 5; + if (_readArraysLock.tryLockForWrite(WAIT_FOR_LOCK_IN_MS)) { + if (_writeArraysLock.tryLockForRead(WAIT_FOR_LOCK_IN_MS)) { + if (_voxelsDirty && _voxelsUpdated) { + if (fullVBOs) { + copyWrittenDataToReadArraysFullVBOs(); + } else { + copyWrittenDataToReadArraysPartialVBOs(); + } + } + _writeArraysLock.unlock(); } else { - copyWrittenDataToReadArraysPartialVBOs(); + qDebug() << "couldn't get _writeArraysLock.LockForRead()..."; } + _readArraysLock.unlock(); + } else { + qDebug() << "couldn't get _readArraysLock.LockForWrite()..."; } } @@ -1105,7 +1218,8 @@ void VoxelSystem::changeTree(VoxelTree* newTree) { connect(_tree, SIGNAL(importSize(float,float,float)), SIGNAL(importSize(float,float,float))); connect(_tree, SIGNAL(importProgress(int)), SIGNAL(importProgress(int))); - setupNewVoxelsForDrawing(); + // TODO: hmmmmm????? + //setupNewVoxelsForDrawing(); } void VoxelSystem::updateFullVBOs() { @@ -1166,17 +1280,27 @@ void VoxelSystem::updateVBOs() { // would like to include _callsToTreesToArrays PerformanceWarning warn(Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings), buffer); if (_voxelsDirty) { - if (_readRenderFullVBO) { - updateFullVBOs(); + + // attempt to lock the read arrays, to for copying from them to the actual GPU VBOs. + // if we fail to get the lock, that's ok, our VBOs will update on the next frame... + const int WAIT_FOR_LOCK_IN_MS = 5; + if (_readArraysLock.tryLockForRead(WAIT_FOR_LOCK_IN_MS)) { + if (_readRenderFullVBO) { + updateFullVBOs(); + } else { + updatePartialVBOs(); + } + _voxelsDirty = false; + _readRenderFullVBO = false; + _readArraysLock.unlock(); } else { - updatePartialVBOs(); + qDebug() << "updateVBOs().... couldn't get _readArraysLock.tryLockForRead()"; } - _voxelsDirty = false; - _readRenderFullVBO = false; } _callsToTreesToArrays = 0; // clear it } +// this should only be called on the main application thread during render void VoxelSystem::updateVBOSegment(glBufferIndex segmentStart, glBufferIndex segmentEnd) { bool showWarning = Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings); PerformanceWarning warn(showWarning, "updateVBOSegment()"); diff --git a/interface/src/VoxelSystem.h b/interface/src/VoxelSystem.h index c2f60d6699..121a7f86c4 100644 --- a/interface/src/VoxelSystem.h +++ b/interface/src/VoxelSystem.h @@ -194,6 +194,7 @@ private: static bool showAllSubTreeOperation(OctreeElement* element, void* extraData); static bool showAllLocalVoxelsOperation(OctreeElement* element, void* extraData); static bool getVoxelEnclosingOperation(OctreeElement* element, void* extraData); + static bool recreateVoxelGeometryInViewOperation(OctreeElement* element, void* extraData); int updateNodeInArrays(VoxelTreeElement* node, bool reuseIndex, bool forceDraw); int forceRemoveNodeFromArrays(VoxelTreeElement* node); @@ -212,6 +213,7 @@ private: GLfloat* _readVerticesArray; GLubyte* _readColorsArray; + QReadWriteLock _writeArraysLock; QReadWriteLock _readArraysLock; From 3aba29b0154d97e0ad886f3f8eff5180e8626a63 Mon Sep 17 00:00:00 2001 From: ZappoMan Date: Thu, 13 Feb 2014 13:26:31 -0800 Subject: [PATCH 09/22] first working cut at recreateVoxelGeometryInView() --- interface/src/VoxelSystem.cpp | 33 ++++++--------------------------- 1 file changed, 6 insertions(+), 27 deletions(-) diff --git a/interface/src/VoxelSystem.cpp b/interface/src/VoxelSystem.cpp index 851c24c5ce..37bb2b5db1 100644 --- a/interface/src/VoxelSystem.cpp +++ b/interface/src/VoxelSystem.cpp @@ -794,16 +794,9 @@ bool VoxelSystem::recreateVoxelGeometryInViewOperation(OctreeElement* element, v bool shouldRender = voxel->calculateShouldRender(&args->thisViewFrustum, args->voxelSizeScale, args->boundaryLevelAdjust); bool inView = voxel->isInView(args->thisViewFrustum); voxel->setShouldRender(inView && shouldRender); - - if (shouldRender) { - bool falseColorize = false; - if (falseColorize) { - voxel->setFalseColor(0,0,255); // false colorize - } - // These are both needed to force redraw... - voxel->setDirtyBit(); - qDebug() << "recreateVoxelGeometryInViewOperation()... calling voxel->markWithChangedTime()"; - voxel->markWithChangedTime(); // this will notifyUpdateHooks, which will result in our geometry being created + if (shouldRender && inView) { + // recreate the geometry + args->thisVoxelSystem->updateNodeInArrays(voxel, false, true); // DONT_REUSE_INDEX, FORCE_REDRAW } return true; // keep recursing! @@ -814,30 +807,17 @@ bool VoxelSystem::recreateVoxelGeometryInViewOperation(OctreeElement* element, v // TODO: other than cleanupRemovedVoxels() is there anyplace we attempt to detect too many abandoned slots??? void VoxelSystem::recreateVoxelGeometryInView() { -qDebug() << "recreateVoxelGeometryInView()..."; + qDebug() << "recreateVoxelGeometryInView()..."; recreateVoxelGeometryInViewArgs args(this); _writeArraysLock.lockForWrite(); // don't let anyone read or write our write arrays until we're done _tree->lockForRead(); // don't let anyone change our tree structure until we're run // reset our write arrays bookkeeping to think we've got no voxels in it - clearFreeBufferIndexes(); // does this do everything we need? - /** - // clear out all our abandoned indexes - they are all available - // TODO: is it possible freeBufferIndex() will get called??? - _voxelsInWriteArrays = 0; - _freeIndexLock.lock(); - _freeIndexes.clear(); - _freeIndexLock.unlock(); - **/ - - //_voxelsUpdated = 0; // ???? - //_writeRenderFullVBO = true; + clearFreeBufferIndexes(); // do we need to reset out _writeVoxelDirtyArray arrays?? memset(_writeVoxelDirtyArray, false, _maxVoxels * sizeof(bool)); - - _tree->recurseTreeWithOperation(recreateVoxelGeometryInViewOperation,(void*)&args); _tree->unlock(); @@ -882,8 +862,7 @@ void VoxelSystem::checkForCulling() { if (fullRedraw) { // this will remove all old geometry and recreate the correct geometry for all in view voxels - //recreateVoxelGeometryInView(); - hideOutOfView(forceFullFrustum); + recreateVoxelGeometryInView(); } else { hideOutOfView(forceFullFrustum); } From 6a39290bf576f19f771cc2e0de666038863ba121 Mon Sep 17 00:00:00 2001 From: Andrzej Kapolka Date: Thu, 13 Feb 2014 13:37:29 -0800 Subject: [PATCH 10/22] Basic LOD switching based on distance to camera. --- interface/src/avatar/FaceModel.cpp | 3 -- interface/src/avatar/SkeletonModel.cpp | 4 -- interface/src/renderer/GeometryCache.cpp | 53 +++++++++++++----------- interface/src/renderer/GeometryCache.h | 20 +++++---- interface/src/renderer/Model.cpp | 12 +++++- interface/src/renderer/Model.h | 1 + 6 files changed, 54 insertions(+), 39 deletions(-) diff --git a/interface/src/avatar/FaceModel.cpp b/interface/src/avatar/FaceModel.cpp index b041f5bc2d..b368024337 100644 --- a/interface/src/avatar/FaceModel.cpp +++ b/interface/src/avatar/FaceModel.cpp @@ -19,9 +19,6 @@ FaceModel::FaceModel(Head* owningHead) : } void FaceModel::simulate(float deltaTime) { - if (!isActive()) { - return; - } Avatar* owningAvatar = static_cast(_owningHead->_owningAvatar); glm::vec3 neckPosition; if (!owningAvatar->getSkeletonModel().getNeckPosition(neckPosition)) { diff --git a/interface/src/avatar/SkeletonModel.cpp b/interface/src/avatar/SkeletonModel.cpp index ac08c52b49..5ae17c3923 100644 --- a/interface/src/avatar/SkeletonModel.cpp +++ b/interface/src/avatar/SkeletonModel.cpp @@ -20,10 +20,6 @@ SkeletonModel::SkeletonModel(Avatar* owningAvatar) : } void SkeletonModel::simulate(float deltaTime) { - if (!isActive()) { - return; - } - setTranslation(_owningAvatar->getPosition()); setRotation(_owningAvatar->getOrientation() * glm::angleAxis(180.0f, 0.0f, 1.0f, 0.0f)); const float MODEL_SCALE = 0.0006f; diff --git a/interface/src/renderer/GeometryCache.cpp b/interface/src/renderer/GeometryCache.cpp index dfe6949438..0688166ca3 100644 --- a/interface/src/renderer/GeometryCache.cpp +++ b/interface/src/renderer/GeometryCache.cpp @@ -294,17 +294,21 @@ QSharedPointer GeometryCache::getGeometry(const QUrl& url, cons if (geometry.isNull()) { geometry = QSharedPointer(new NetworkGeometry(url, fallback.isValid() ? getGeometry(fallback) : QSharedPointer())); + geometry->setLODParent(geometry); _networkGeometry.insert(url, geometry); } return geometry; } -NetworkGeometry::NetworkGeometry(const QUrl& url, const QSharedPointer& fallback) : +NetworkGeometry::NetworkGeometry(const QUrl& url, const QSharedPointer& fallback, + const QVariantHash& mapping, const QUrl& textureBase) : _request(url), _reply(NULL), - _textureBase(url), + _mapping(mapping), + _textureBase(textureBase.isValid() ? textureBase : url), _fallback(fallback), - _attempts(0) { + _attempts(0), + _failedToLoad(false) { if (!url.isValid()) { return; @@ -319,6 +323,17 @@ NetworkGeometry::~NetworkGeometry() { } } +QSharedPointer NetworkGeometry::getLODOrFallback(float distance) const { + if (_lodParent.data() != this) { + return _lodParent.data()->getLODOrFallback(distance); + } + if (_failedToLoad && _fallback) { + return _fallback; + } + QMap >::const_iterator it = _lods.upperBound(distance); + return (it == _lods.constBegin()) ? _lodParent.toStrongRef() : *(it - 1); +} + glm::vec4 NetworkGeometry::computeAverageColor() const { glm::vec4 totalColor; int totalTriangles = 0; @@ -367,7 +382,8 @@ void NetworkGeometry::handleDownloadProgress(qint64 bytesReceived, qint64 bytesT QString filename = _mapping.value("filename").toString(); if (filename.isNull()) { qDebug() << "Mapping file " << url << " has no filename."; - maybeLoadFallback(); + _failedToLoad = true; + } else { QString texdir = _mapping.value("texdir").toString(); if (!texdir.isNull()) { @@ -376,6 +392,13 @@ void NetworkGeometry::handleDownloadProgress(qint64 bytesReceived, qint64 bytesT } _textureBase = url.resolved(texdir); } + QVariantHash lods = _mapping.value("lod").toHash(); + for (QVariantHash::const_iterator it = lods.begin(); it != lods.end(); it++) { + QSharedPointer geometry(new NetworkGeometry(url.resolved(it.key()), + QSharedPointer(), _mapping, _textureBase)); + geometry->setLODParent(_lodParent); + _lods.insert(it.value().toFloat(), geometry); + } _request.setUrl(url.resolved(filename)); makeRequest(); } @@ -387,7 +410,7 @@ void NetworkGeometry::handleDownloadProgress(qint64 bytesReceived, qint64 bytesT } catch (const QString& error) { qDebug() << "Error reading " << url << ": " << error; - maybeLoadFallback(); + _failedToLoad = true; return; } @@ -481,8 +504,6 @@ void NetworkGeometry::handleDownloadProgress(qint64 bytesReceived, qint64 bytesT _meshes.append(networkMesh); } - - emit loaded(); } void NetworkGeometry::handleReplyError() { @@ -515,28 +536,12 @@ void NetworkGeometry::handleReplyError() { // fall through to final failure } default: - maybeLoadFallback(); + _failedToLoad = true; break; } } -void NetworkGeometry::loadFallback() { - _geometry = _fallback->_geometry; - _meshes = _fallback->_meshes; - emit loaded(); -} - -void NetworkGeometry::maybeLoadFallback() { - if (_fallback) { - if (_fallback->isLoaded()) { - loadFallback(); - } else { - connect(_fallback.data(), SIGNAL(loaded()), SLOT(loadFallback())); - } - } -} - bool NetworkMeshPart::isTranslucent() const { return diffuseTexture && diffuseTexture->isTranslucent(); } diff --git a/interface/src/renderer/GeometryCache.h b/interface/src/renderer/GeometryCache.h index 0587831721..a3f9c93ca6 100644 --- a/interface/src/renderer/GeometryCache.h +++ b/interface/src/renderer/GeometryCache.h @@ -13,6 +13,7 @@ #include "InterfaceConfig.h" #include +#include #include #include #include @@ -61,41 +62,46 @@ class NetworkGeometry : public QObject { public: - NetworkGeometry(const QUrl& url, const QSharedPointer& fallback); + NetworkGeometry(const QUrl& url, const QSharedPointer& fallback, + const QVariantHash& mapping = QVariantHash(), const QUrl& textureBase = QUrl()); ~NetworkGeometry(); bool isLoaded() const { return !_geometry.joints.isEmpty(); } + /// Returns a pointer to the geometry appropriate for the specified distance. + QSharedPointer getLODOrFallback(float distance) const; + const FBXGeometry& getFBXGeometry() const { return _geometry; } const QVector& getMeshes() const { return _meshes; } /// Returns the average color of all meshes in the geometry. glm::vec4 computeAverageColor() const; -signals: - - void loaded(); - private slots: void makeRequest(); void handleDownloadProgress(qint64 bytesReceived, qint64 bytesTotal); void handleReplyError(); - void loadFallback(); private: - void maybeLoadFallback(); + friend class GeometryCache; + + void setLODParent(const QWeakPointer& lodParent) { _lodParent = lodParent; } QNetworkRequest _request; QNetworkReply* _reply; QVariantHash _mapping; QUrl _textureBase; QSharedPointer _fallback; + bool _failedToLoad; int _attempts; + QMap > _lods; FBXGeometry _geometry; QVector _meshes; + + QWeakPointer _lodParent; }; /// The state associated with a single mesh part. diff --git a/interface/src/renderer/Model.cpp b/interface/src/renderer/Model.cpp index 18654a6efc..869e89539e 100644 --- a/interface/src/renderer/Model.cpp +++ b/interface/src/renderer/Model.cpp @@ -90,6 +90,16 @@ void Model::reset() { } void Model::simulate(float deltaTime) { + // update our LOD + if (_geometry) { + QSharedPointer geometry = _geometry->getLODOrFallback(glm::distance(_translation, + Application::getInstance()->getCamera()->getPosition())); + if (_geometry != geometry) { + deleteGeometry(); + _dilatedTextures.clear(); + _geometry = geometry; + } + } if (!isActive()) { return; } @@ -410,7 +420,7 @@ void Model::setURL(const QUrl& url, const QUrl& fallback) { deleteGeometry(); _dilatedTextures.clear(); - _geometry = Application::getInstance()->getGeometryCache()->getGeometry(url, fallback); + _baseGeometry = _geometry = Application::getInstance()->getGeometryCache()->getGeometry(url, fallback); } glm::vec4 Model::computeAverageColor() const { diff --git a/interface/src/renderer/Model.h b/interface/src/renderer/Model.h index 003cdfe3e5..64ca789570 100644 --- a/interface/src/renderer/Model.h +++ b/interface/src/renderer/Model.h @@ -176,6 +176,7 @@ public: protected: + QSharedPointer _baseGeometry; QSharedPointer _geometry; glm::vec3 _translation; From ab4164a6dfeea9888ee8468aa6b008983188ef1c Mon Sep 17 00:00:00 2001 From: stojce Date: Thu, 13 Feb 2014 22:44:10 +0100 Subject: [PATCH 11/22] #19505 - Add domain & orientation parsing to hifi:// protocol handler --- interface/src/Application.cpp | 21 +++++++++++++-- interface/src/Menu.cpp | 50 ++++++++++++++++++++++++++++++----- interface/src/Menu.h | 2 ++ 3 files changed, 65 insertions(+), 8 deletions(-) diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index eeb9d4a73a..57f66e68b2 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -687,8 +687,25 @@ bool Application::event(QEvent* event) { if (event->type() == QEvent::FileOpen) { QFileOpenEvent* fileEvent = static_cast(event); if (!fileEvent->url().isEmpty() && fileEvent->url().toLocalFile().startsWith(CUSTOM_URL_SCHEME)) { - QString destination = fileEvent->url().toLocalFile().remove(QRegExp(CUSTOM_URL_SCHEME + "|/")); - Menu::getInstance()->goToDestination(destination); + QString destination = fileEvent->url().toLocalFile().remove(CUSTOM_URL_SCHEME); + QStringList urlParts = destination.split('/', QString::SkipEmptyParts); + + if (urlParts.count() > 1) { + // if url has 2 or more parts, the first one is domain name + Menu::getInstance()->goToDomain(urlParts[0]); + + // location coordinates + Menu::getInstance()->goToDestination(urlParts[1]); + if (urlParts.count() > 2) { + + // location orientation + Menu::getInstance()->goToOrientation(urlParts[2]); + } + } else if (urlParts.count() == 1) { + + // location coordinates + Menu::getInstance()->goToDestination(urlParts[0]); + } } return false; diff --git a/interface/src/Menu.cpp b/interface/src/Menu.cpp index 9854a8b97f..e064c4bd20 100644 --- a/interface/src/Menu.cpp +++ b/interface/src/Menu.cpp @@ -889,6 +889,17 @@ void Menu::editPreferences() { sendFakeEnterEvent(); } +void Menu::goToDomain(const QString newDomain) { + if (NodeList::getInstance()->getDomainHostname() != newDomain) { + + // send a node kill request, indicating to other clients that they should play the "disappeared" effect + Application::getInstance()->getAvatar()->sendKillAvatar(); + + // give our nodeList the new domain-server hostname + NodeList::getInstance()->setDomainHostname(newDomain); + } +} + void Menu::goToDomain() { QString currentDomainHostname = NodeList::getInstance()->getDomainHostname(); @@ -913,17 +924,44 @@ void Menu::goToDomain() { // the user input a new hostname, use that newHostname = domainDialog.textValue(); } - - // send a node kill request, indicating to other clients that they should play the "disappeared" effect - Application::getInstance()->getAvatar()->sendKillAvatar(); - - // give our nodeList the new domain-server hostname - NodeList::getInstance()->setDomainHostname(domainDialog.textValue()); + + goToDomain(newHostname); } sendFakeEnterEvent(); } +void Menu::goToOrientation(QString orientation) { + + if (orientation.isEmpty()) { + return; + } + + QStringList orientationItems = orientation.split(QRegExp("_|,"), QString::SkipEmptyParts); + + const int NUMBER_OF_ORIENTATION_ITEMS = 4; + const int W_ITEM = 0; + const int X_ITEM = 1; + const int Y_ITEM = 2; + const int Z_ITEM = 3; + + if (orientationItems.size() == NUMBER_OF_ORIENTATION_ITEMS) { + + double w = replaceLastOccurrence('-', '.', orientationItems[W_ITEM].trimmed()).toDouble(); + double x = replaceLastOccurrence('-', '.', orientationItems[X_ITEM].trimmed()).toDouble(); + double y = replaceLastOccurrence('-', '.', orientationItems[Y_ITEM].trimmed()).toDouble(); + double z = replaceLastOccurrence('-', '.', orientationItems[Z_ITEM].trimmed()).toDouble(); + + glm::quat newAvatarOrientation(w, x, y, z); + + MyAvatar* myAvatar = Application::getInstance()->getAvatar(); + glm::quat avatarOrientation = myAvatar->getOrientation(); + if (newAvatarOrientation != avatarOrientation) { + myAvatar->setOrientation(newAvatarOrientation); + } + } +} + bool Menu::goToDestination(QString destination) { QStringList coordinateItems = destination.split(QRegExp("_|,"), QString::SkipEmptyParts); diff --git a/interface/src/Menu.h b/interface/src/Menu.h index 9ccc5466e8..378c022ae0 100644 --- a/interface/src/Menu.h +++ b/interface/src/Menu.h @@ -85,6 +85,8 @@ public: QAction::MenuRole role = QAction::NoRole); virtual void removeAction(QMenu* menu, const QString& actionName); bool goToDestination(QString destination); + void goToOrientation(QString orientation); + void goToDomain(const QString newDomain); public slots: void bandwidthDetails(); From ff01470850a3f2b0c19d13358ce6208f4b2a4962 Mon Sep 17 00:00:00 2001 From: Andrzej Kapolka Date: Thu, 13 Feb 2014 14:25:01 -0800 Subject: [PATCH 12/22] Wait until each LOD level is actually requested before we start loading; before a level is loaded, try to use the closest already-loaded level. --- interface/src/renderer/GeometryCache.cpp | 37 ++++++++++++++++++++++-- interface/src/renderer/GeometryCache.h | 2 ++ 2 files changed, 36 insertions(+), 3 deletions(-) diff --git a/interface/src/renderer/GeometryCache.cpp b/interface/src/renderer/GeometryCache.cpp index 0688166ca3..28280395e4 100644 --- a/interface/src/renderer/GeometryCache.cpp +++ b/interface/src/renderer/GeometryCache.cpp @@ -308,13 +308,18 @@ NetworkGeometry::NetworkGeometry(const QUrl& url, const QSharedPointer NetworkGeometry::getLODOrFallback(float distance return _fallback; } QMap >::const_iterator it = _lods.upperBound(distance); - return (it == _lods.constBegin()) ? _lodParent.toStrongRef() : *(it - 1); + QSharedPointer lod = (it == _lods.constBegin()) ? _lodParent.toStrongRef() : *(it - 1); + if (lod->isLoaded()) { + return lod; + } + // if the ideal LOD isn't loaded, we need to make sure it's started to load, and possibly return the closest loaded one + if (!lod->_startedLoading) { + lod->makeRequest(); + } + float closestDistance = FLT_MAX; + if (isLoaded()) { + lod = _lodParent; + closestDistance = distance; + } + for (it = _lods.constBegin(); it != _lods.constEnd(); it++) { + float distanceToLOD = glm::abs(distance - it.key()); + if (it.value()->isLoaded() && distanceToLOD < closestDistance) { + lod = it.value(); + closestDistance = distanceToLOD; + } + } + return lod; } glm::vec4 NetworkGeometry::computeAverageColor() const { @@ -359,6 +384,7 @@ glm::vec4 NetworkGeometry::computeAverageColor() const { } void NetworkGeometry::makeRequest() { + _startedLoading = true; _reply = Application::getInstance()->getNetworkAccessManager()->get(_request); connect(_reply, SIGNAL(downloadProgress(qint64,qint64)), SLOT(handleDownloadProgress(qint64,qint64))); @@ -400,7 +426,12 @@ void NetworkGeometry::handleDownloadProgress(qint64 bytesReceived, qint64 bytesT _lods.insert(it.value().toFloat(), geometry); } _request.setUrl(url.resolved(filename)); - makeRequest(); + + // make the request immediately only if we have no LODs to switch between + _startedLoading = false; + if (_lods.isEmpty()) { + makeRequest(); + } } return; } diff --git a/interface/src/renderer/GeometryCache.h b/interface/src/renderer/GeometryCache.h index a3f9c93ca6..cd1972f413 100644 --- a/interface/src/renderer/GeometryCache.h +++ b/interface/src/renderer/GeometryCache.h @@ -66,6 +66,7 @@ public: const QVariantHash& mapping = QVariantHash(), const QUrl& textureBase = QUrl()); ~NetworkGeometry(); + /// Checks whether the geometry is fulled loaded. bool isLoaded() const { return !_geometry.joints.isEmpty(); } /// Returns a pointer to the geometry appropriate for the specified distance. @@ -94,6 +95,7 @@ private: QVariantHash _mapping; QUrl _textureBase; QSharedPointer _fallback; + bool _startedLoading; bool _failedToLoad; int _attempts; From caab3afb69c65f7703ef9e90f76ccd7bd303b534 Mon Sep 17 00:00:00 2001 From: ZappoMan Date: Thu, 13 Feb 2014 14:26:30 -0800 Subject: [PATCH 13/22] add menu item for auto adjust LOD --- interface/src/Application.cpp | 5 ++++- interface/src/Menu.cpp | 1 + interface/src/Menu.h | 1 + 3 files changed, 6 insertions(+), 1 deletion(-) diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index fd4c52d9f2..ab511b62e0 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -2709,7 +2709,10 @@ void Application::displaySide(Camera& whichCamera, bool selfAvatarOnly) { _voxels.render(); // double check that our LOD doesn't need to be auto-adjusted - Menu::getInstance()->autoAdjustLOD(_fps); + // only adjust if our option is set + if (Menu::getInstance()->isOptionChecked(MenuOption::AutoAdjustLOD)) { + Menu::getInstance()->autoAdjustLOD(_fps); + } } } diff --git a/interface/src/Menu.cpp b/interface/src/Menu.cpp index 63717fdbb5..3eecdd1e96 100644 --- a/interface/src/Menu.cpp +++ b/interface/src/Menu.cpp @@ -317,6 +317,7 @@ Menu::Menu() : addCheckableActionToQMenuAndActionHash(voxelOptionsMenu, MenuOption::VoxelTextures); addCheckableActionToQMenuAndActionHash(voxelOptionsMenu, MenuOption::AmbientOcclusion); addCheckableActionToQMenuAndActionHash(voxelOptionsMenu, MenuOption::DontFadeOnVoxelServerChanges); + addCheckableActionToQMenuAndActionHash(voxelOptionsMenu, MenuOption::AutoAdjustLOD); addActionToQMenuAndActionHash(voxelOptionsMenu, MenuOption::LodTools, Qt::SHIFT | Qt::Key_L, this, SLOT(lodTools())); QMenu* voxelProtoOptionsMenu = voxelOptionsMenu->addMenu("Voxel Server Protocol Options"); diff --git a/interface/src/Menu.h b/interface/src/Menu.h index 130ce19475..fd9582229f 100644 --- a/interface/src/Menu.h +++ b/interface/src/Menu.h @@ -175,6 +175,7 @@ namespace MenuOption { const QString AmbientOcclusion = "Ambient Occlusion"; const QString Avatars = "Avatars"; const QString Atmosphere = "Atmosphere"; + const QString AutoAdjustLOD = "Automatically Adjust LOD"; const QString AutomaticallyAuditTree = "Automatically Audit Tree Stats"; const QString Bandwidth = "Bandwidth Display"; const QString BandwidthDetails = "Bandwidth Details"; From 4fb5e6842590feee2960211c448cc5367d03788a Mon Sep 17 00:00:00 2001 From: ZappoMan Date: Thu, 13 Feb 2014 14:33:23 -0800 Subject: [PATCH 14/22] removed some debug --- interface/src/VoxelSystem.cpp | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/interface/src/VoxelSystem.cpp b/interface/src/VoxelSystem.cpp index 37bb2b5db1..9cacd02b18 100644 --- a/interface/src/VoxelSystem.cpp +++ b/interface/src/VoxelSystem.cpp @@ -127,16 +127,9 @@ void VoxelSystem::setDisableFastVoxelPipeline(bool disableFastVoxelPipeline) { void VoxelSystem::elementUpdated(OctreeElement* element) { VoxelTreeElement* voxel = (VoxelTreeElement*)element; -//qDebug() << "VoxelSystem::elementUpdated()..."; // If we're in SetupNewVoxelsForDrawing() or _writeRenderFullVBO then bail.. if (!_useFastVoxelPipeline || _inSetupNewVoxelsForDrawing || _writeRenderFullVBO) { -/* -qDebug() << "VoxelSystem::elementUpdated()... BAILING!!! " - << "_writeRenderFullVBO="<< _writeRenderFullVBO - << "_inSetupNewVoxelsForDrawing="<< _inSetupNewVoxelsForDrawing - << "_useFastVoxelPipeline="<< _useFastVoxelPipeline; -*/ return; } @@ -147,8 +140,6 @@ qDebug() << "VoxelSystem::elementUpdated()... BAILING!!! " int boundaryLevelAdjust = Menu::getInstance()->getBoundaryLevelAdjust(); shouldRender = voxel->calculateShouldRender(_viewFrustum, voxelSizeScale, boundaryLevelAdjust); -//qDebug() << "VoxelSystem::elementUpdated()... recalcing should render!!"; - if (voxel->getShouldRender() != shouldRender) { voxel->setShouldRender(shouldRender); } @@ -176,13 +167,11 @@ qDebug() << "VoxelSystem::elementUpdated()... BAILING!!! " const bool REUSE_INDEX = true; const bool DONT_FORCE_REDRAW = false; -//qDebug() << "VoxelSystem::elementUpdated()... calling updateNodeInArrays()!!!"; updateNodeInArrays(voxel, REUSE_INDEX, DONT_FORCE_REDRAW); _voxelsUpdated++; voxel->clearDirtyBit(); // clear the dirty bit, do this before we potentially delete things. -//qDebug() << "VoxelSystem::elementUpdated()... calling setupNewVoxelsForDrawingSingleNode()!!!"; setupNewVoxelsForDrawingSingleNode(); } } From ed7fa6e311a368b3dbe5bf2e70f8aa7ea33d51b1 Mon Sep 17 00:00:00 2001 From: ZappoMan Date: Thu, 13 Feb 2014 14:39:10 -0800 Subject: [PATCH 15/22] added back setupNewVoxelsForDrawing() on changeTree() --- interface/src/VoxelSystem.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/interface/src/VoxelSystem.cpp b/interface/src/VoxelSystem.cpp index 9cacd02b18..632c5e056e 100644 --- a/interface/src/VoxelSystem.cpp +++ b/interface/src/VoxelSystem.cpp @@ -1186,8 +1186,7 @@ void VoxelSystem::changeTree(VoxelTree* newTree) { connect(_tree, SIGNAL(importSize(float,float,float)), SIGNAL(importSize(float,float,float))); connect(_tree, SIGNAL(importProgress(int)), SIGNAL(importProgress(int))); - // TODO: hmmmmm????? - //setupNewVoxelsForDrawing(); + setupNewVoxelsForDrawing(); } void VoxelSystem::updateFullVBOs() { From 601c155303692956c5d1e6c94b7df4d3e337b584 Mon Sep 17 00:00:00 2001 From: ZappoMan Date: Thu, 13 Feb 2014 14:46:59 -0800 Subject: [PATCH 16/22] correctly lock arrays on init --- interface/src/VoxelSystem.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/interface/src/VoxelSystem.cpp b/interface/src/VoxelSystem.cpp index 632c5e056e..a3352f36e7 100644 --- a/interface/src/VoxelSystem.cpp +++ b/interface/src/VoxelSystem.cpp @@ -413,8 +413,8 @@ void VoxelSystem::setupFaceIndices(GLuint& faceVBOID, GLubyte faceIdentityIndice } void VoxelSystem::initVoxelMemory() { - //_readArraysLock.lockForWrite(); - //_writeArraysLock.lockForWrite(); + _readArraysLock.lockForWrite(); + _writeArraysLock.lockForWrite(); _memoryUsageRAM = 0; _memoryUsageVBO = 0; // our VBO allocations as we know them @@ -529,8 +529,8 @@ void VoxelSystem::initVoxelMemory() { _initialized = true; - //_writeArraysLock.unlock(); - //_readArraysLock.unlock(); + _writeArraysLock.unlock(); + _readArraysLock.unlock(); } void VoxelSystem::writeToSVOFile(const char* filename, VoxelTreeElement* element) const { From 078b15c02d1aeee7df0854e192dba815b9860e59 Mon Sep 17 00:00:00 2001 From: Andrzej Kapolka Date: Thu, 13 Feb 2014 16:20:43 -0800 Subject: [PATCH 17/22] Add some hysteresis on the LOD switching to prevent rapid switching back and forth. --- interface/src/renderer/GeometryCache.cpp | 21 ++++++++++++++++++--- interface/src/renderer/GeometryCache.h | 6 +++++- interface/src/renderer/Model.cpp | 3 ++- interface/src/renderer/Model.h | 4 +++- 4 files changed, 28 insertions(+), 6 deletions(-) diff --git a/interface/src/renderer/GeometryCache.cpp b/interface/src/renderer/GeometryCache.cpp index 28280395e4..c02f69b8fb 100644 --- a/interface/src/renderer/GeometryCache.cpp +++ b/interface/src/renderer/GeometryCache.cpp @@ -328,16 +328,30 @@ NetworkGeometry::~NetworkGeometry() { } } -QSharedPointer NetworkGeometry::getLODOrFallback(float distance) const { +QSharedPointer NetworkGeometry::getLODOrFallback(float distance, float& hysteresis) const { if (_lodParent.data() != this) { - return _lodParent.data()->getLODOrFallback(distance); + return _lodParent.data()->getLODOrFallback(distance, hysteresis); } if (_failedToLoad && _fallback) { return _fallback; } + QSharedPointer lod = _lodParent; + float lodDistance = 0.0f; QMap >::const_iterator it = _lods.upperBound(distance); - QSharedPointer lod = (it == _lods.constBegin()) ? _lodParent.toStrongRef() : *(it - 1); + if (it != _lods.constBegin()) { + it = it - 1; + lod = it.value(); + lodDistance = it.key(); + } + if (hysteresis != NO_HYSTERESIS && hysteresis != lodDistance) { + // if we previously selected a different distance, make sure we've moved far enough to justify switching + const float HYSTERESIS_PROPORTION = 0.1f; + if (glm::abs(distance - qMax(hysteresis, lodDistance)) / fabsf(hysteresis - lodDistance) < HYSTERESIS_PROPORTION) { + return getLODOrFallback(hysteresis, hysteresis); + } + } if (lod->isLoaded()) { + hysteresis = lodDistance; return lod; } // if the ideal LOD isn't loaded, we need to make sure it's started to load, and possibly return the closest loaded one @@ -356,6 +370,7 @@ QSharedPointer NetworkGeometry::getLODOrFallback(float distance closestDistance = distanceToLOD; } } + hysteresis = NO_HYSTERESIS; return lod; } diff --git a/interface/src/renderer/GeometryCache.h b/interface/src/renderer/GeometryCache.h index cd1972f413..110e2eec4c 100644 --- a/interface/src/renderer/GeometryCache.h +++ b/interface/src/renderer/GeometryCache.h @@ -62,6 +62,9 @@ class NetworkGeometry : public QObject { public: + /// A hysteresis value indicating that we have no state memory. + static const float NO_HYSTERESIS = -1.0f; + NetworkGeometry(const QUrl& url, const QSharedPointer& fallback, const QVariantHash& mapping = QVariantHash(), const QUrl& textureBase = QUrl()); ~NetworkGeometry(); @@ -70,7 +73,8 @@ public: bool isLoaded() const { return !_geometry.joints.isEmpty(); } /// Returns a pointer to the geometry appropriate for the specified distance. - QSharedPointer getLODOrFallback(float distance) const; + /// \param hysteresis a hysteresis parameter that prevents rapid model switching + QSharedPointer getLODOrFallback(float distance, float& hysteresis) const; const FBXGeometry& getFBXGeometry() const { return _geometry; } const QVector& getMeshes() const { return _meshes; } diff --git a/interface/src/renderer/Model.cpp b/interface/src/renderer/Model.cpp index 869e89539e..5ea8ff33b4 100644 --- a/interface/src/renderer/Model.cpp +++ b/interface/src/renderer/Model.cpp @@ -93,7 +93,7 @@ void Model::simulate(float deltaTime) { // update our LOD if (_geometry) { QSharedPointer geometry = _geometry->getLODOrFallback(glm::distance(_translation, - Application::getInstance()->getCamera()->getPosition())); + glm::vec3() /* Application::getInstance()->getCamera()->getPosition() */), _lodHysteresis); if (_geometry != geometry) { deleteGeometry(); _dilatedTextures.clear(); @@ -419,6 +419,7 @@ void Model::setURL(const QUrl& url, const QUrl& fallback) { // delete our local geometry and custom textures deleteGeometry(); _dilatedTextures.clear(); + _lodHysteresis = NetworkGeometry::NO_HYSTERESIS; _baseGeometry = _geometry = Application::getInstance()->getGeometryCache()->getGeometry(url, fallback); } diff --git a/interface/src/renderer/Model.h b/interface/src/renderer/Model.h index 64ca789570..74283043cf 100644 --- a/interface/src/renderer/Model.h +++ b/interface/src/renderer/Model.h @@ -176,7 +176,6 @@ public: protected: - QSharedPointer _baseGeometry; QSharedPointer _geometry; glm::vec3 _translation; @@ -237,6 +236,9 @@ private: void deleteGeometry(); void renderMeshes(float alpha, bool translucent); + QSharedPointer _baseGeometry; + float _lodHysteresis; + float _pupilDilation; std::vector _blendshapeCoefficients; From 5f38c328d58f6fa6f91454e17d7a93d73def86a6 Mon Sep 17 00:00:00 2001 From: Andrzej Kapolka Date: Thu, 13 Feb 2014 16:25:04 -0800 Subject: [PATCH 18/22] Removed debugging code. --- interface/src/renderer/Model.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/interface/src/renderer/Model.cpp b/interface/src/renderer/Model.cpp index 5ea8ff33b4..d368de409a 100644 --- a/interface/src/renderer/Model.cpp +++ b/interface/src/renderer/Model.cpp @@ -93,7 +93,7 @@ void Model::simulate(float deltaTime) { // update our LOD if (_geometry) { QSharedPointer geometry = _geometry->getLODOrFallback(glm::distance(_translation, - glm::vec3() /* Application::getInstance()->getCamera()->getPosition() */), _lodHysteresis); + Application::getInstance()->getCamera()->getPosition()), _lodHysteresis); if (_geometry != geometry) { deleteGeometry(); _dilatedTextures.clear(); From b08d4527490c888cbc84c8d9032fad18c14aa59c Mon Sep 17 00:00:00 2001 From: Andrzej Kapolka Date: Thu, 13 Feb 2014 16:49:42 -0800 Subject: [PATCH 19/22] Fix for head appearing at wrong position for a single frame. --- interface/src/avatar/FaceModel.cpp | 4 ++++ interface/src/avatar/SkeletonModel.cpp | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/interface/src/avatar/FaceModel.cpp b/interface/src/avatar/FaceModel.cpp index b368024337..c9d2565cee 100644 --- a/interface/src/avatar/FaceModel.cpp +++ b/interface/src/avatar/FaceModel.cpp @@ -19,6 +19,10 @@ FaceModel::FaceModel(Head* owningHead) : } void FaceModel::simulate(float deltaTime) { + if (!isActive()) { + Model::simulate(deltaTime); + return; + } Avatar* owningAvatar = static_cast(_owningHead->_owningAvatar); glm::vec3 neckPosition; if (!owningAvatar->getSkeletonModel().getNeckPosition(neckPosition)) { diff --git a/interface/src/avatar/SkeletonModel.cpp b/interface/src/avatar/SkeletonModel.cpp index 5ae17c3923..c6788c34f7 100644 --- a/interface/src/avatar/SkeletonModel.cpp +++ b/interface/src/avatar/SkeletonModel.cpp @@ -20,6 +20,10 @@ SkeletonModel::SkeletonModel(Avatar* owningAvatar) : } void SkeletonModel::simulate(float deltaTime) { + if (!isActive()) { + Model::simulate(deltaTime); + return; + } setTranslation(_owningAvatar->getPosition()); setRotation(_owningAvatar->getOrientation() * glm::angleAxis(180.0f, 0.0f, 1.0f, 0.0f)); const float MODEL_SCALE = 0.0006f; From 1afd3ab712aab3e673dcdd2162ee58d2dd175deb Mon Sep 17 00:00:00 2001 From: Andrzej Kapolka Date: Thu, 13 Feb 2014 17:07:57 -0800 Subject: [PATCH 20/22] Fixes for OS X warnings. --- interface/src/avatar/MyAvatar.cpp | 1 - interface/src/renderer/GeometryCache.cpp | 6 ++++-- interface/src/renderer/GeometryCache.h | 2 +- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index 0e2625bf0f..de2459d9ee 100644 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -1002,7 +1002,6 @@ void MyAvatar::updateCollisionWithAvatars(float deltaTime) { avatar->getPosition(), theirCapsuleRadius, theirCapsuleHeight, penetration)) { // move the avatar out by half the penetration setPosition(_position - 0.5f * penetration); - glm::vec3 pushOut = 0.5f * penetration; } } } diff --git a/interface/src/renderer/GeometryCache.cpp b/interface/src/renderer/GeometryCache.cpp index c02f69b8fb..78cc657018 100644 --- a/interface/src/renderer/GeometryCache.cpp +++ b/interface/src/renderer/GeometryCache.cpp @@ -300,6 +300,8 @@ QSharedPointer GeometryCache::getGeometry(const QUrl& url, cons return geometry; } +const float NetworkGeometry::NO_HYSTERESIS = -1.0f; + NetworkGeometry::NetworkGeometry(const QUrl& url, const QSharedPointer& fallback, const QVariantHash& mapping, const QUrl& textureBase) : _request(url), @@ -307,9 +309,9 @@ NetworkGeometry::NetworkGeometry(const QUrl& url, const QSharedPointer& fallback, const QVariantHash& mapping = QVariantHash(), const QUrl& textureBase = QUrl()); From 76142c92f248f4e6e8320a738825b1ed4f08afe0 Mon Sep 17 00:00:00 2001 From: Andrzej Kapolka Date: Fri, 14 Feb 2014 13:04:17 -0800 Subject: [PATCH 21/22] Adjust offset when skipping parts that don't match the translucency setting. Closes #2001. --- interface/src/renderer/Model.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/interface/src/renderer/Model.cpp b/interface/src/renderer/Model.cpp index d5a329c4ac..9029d63bd7 100644 --- a/interface/src/renderer/Model.cpp +++ b/interface/src/renderer/Model.cpp @@ -915,11 +915,11 @@ void Model::renderMeshes(float alpha, bool translucent) { 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); if (networkPart.isTranslucent() != translucent) { + offset += (part.quadIndices.size() + part.triangleIndices.size()) * sizeof(int); 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); From af0d3957510525bb851155788065529b5e7b0fcc Mon Sep 17 00:00:00 2001 From: Andrzej Kapolka Date: Fri, 14 Feb 2014 13:11:16 -0800 Subject: [PATCH 22/22] Added sanity check for zero vertices. --- interface/src/renderer/Model.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/interface/src/renderer/Model.cpp b/interface/src/renderer/Model.cpp index 9029d63bd7..8c00842ea2 100644 --- a/interface/src/renderer/Model.cpp +++ b/interface/src/renderer/Model.cpp @@ -806,6 +806,10 @@ void Model::renderMeshes(float alpha, bool translucent) { const FBXMesh& mesh = geometry.meshes.at(i); int vertexCount = mesh.vertices.size(); + if (vertexCount == 0) { + // sanity check + continue; + } const_cast(networkMesh.vertexBuffer).bind();