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..8e96006828 100644
--- a/interface/CMakeLists.txt
+++ b/interface/CMakeLists.txt
@@ -90,7 +90,13 @@ 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(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)
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/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;
diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp
index 521757abab..36fd8f9635 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";
@@ -682,6 +684,38 @@ 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(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;
+ }
+ return QApplication::event(event);
+}
+
void Application::keyPressEvent(QKeyEvent* event) {
_controllerScriptingInterface.emitKeyPressEvent(event); // send events to any registered scripts
@@ -2711,7 +2745,13 @@ 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
+ // only adjust if our option is set
+ if (Menu::getInstance()->isOptionChecked(MenuOption::AutoAdjustLOD)) {
+ Menu::getInstance()->autoAdjustLOD(_fps);
+ }
}
}
@@ -2802,7 +2842,7 @@ void Application::displaySide(Camera& whichCamera, bool selfAvatarOnly) {
_mouseVoxel.s,
_mouseVoxel.s);
- _sharedVoxelSystem.render(true);
+ _sharedVoxelSystem.render();
glPopMatrix();
}
}
diff --git a/interface/src/Application.h b/interface/src/Application.h
index b2a66d0bf8..a9c20ac00d 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 f16a276653..7b3ee15500 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();
@@ -318,6 +319,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");
@@ -884,6 +886,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();
@@ -908,17 +921,77 @@ 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);
+
+ 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());
@@ -934,31 +1007,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()
@@ -989,29 +1039,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();
@@ -1091,14 +1119,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 31b19a64c6..3d15128e2e 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);
@@ -84,6 +97,9 @@ public:
const char* member = NULL,
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();
@@ -154,6 +170,7 @@ private:
int _maxVoxelPacketsPerSecond;
QMenu* _activeScriptsMenu;
QString replaceLastOccurrence(QChar search, QChar replace, QString string);
+ quint64 _lastAdjust;
};
namespace MenuOption {
@@ -161,6 +178,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";
diff --git a/interface/src/VoxelSystem.cpp b/interface/src/VoxelSystem.cpp
index 1ffc6c3e5e..a3352f36e7 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;
@@ -99,6 +102,9 @@ VoxelSystem::VoxelSystem(float treeScale, int maxVoxels)
_culledOnce = false;
_inhideOutOfView = false;
+
+ _lastKnownVoxelSizeScale = DEFAULT_OCTREE_SIZE_SCALE;
+ _lastKnownBoundaryLevelAdjust = 0;
}
void VoxelSystem::elementDeleted(OctreeElement* element) {
@@ -121,6 +127,7 @@ void VoxelSystem::setDisableFastVoxelPipeline(bool disableFastVoxelPipeline) {
void VoxelSystem::elementUpdated(OctreeElement* element) {
VoxelTreeElement* voxel = (VoxelTreeElement*)element;
+
// If we're in SetupNewVoxelsForDrawing() or _writeRenderFullVBO then bail..
if (!_useFastVoxelPipeline || _inSetupNewVoxelsForDrawing || _writeRenderFullVBO) {
return;
@@ -249,6 +256,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 +277,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 +342,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 +380,7 @@ void VoxelSystem::cleanupVoxelMemory() {
delete[] _writeVoxelDirtyArray;
delete[] _readVoxelDirtyArray;
_writeVoxelDirtyArray = _readVoxelDirtyArray = NULL;
- _bufferWriteLock.unlock();
+ _readArraysLock.unlock();
}
}
@@ -401,7 +413,8 @@ void VoxelSystem::setupFaceIndices(GLuint& faceVBOID, GLubyte faceIdentityIndice
}
void VoxelSystem::initVoxelMemory() {
- _bufferWriteLock.lock();
+ _readArraysLock.lockForWrite();
+ _writeArraysLock.lockForWrite();
_memoryUsageRAM = 0;
_memoryUsageVBO = 0; // our VBO allocations as we know them
@@ -516,7 +529,8 @@ void VoxelSystem::initVoxelMemory() {
_initialized = true;
- _bufferWriteLock.unlock();
+ _writeArraysLock.unlock();
+ _readArraysLock.unlock();
}
void VoxelSystem::writeToSVOFile(const char* filename, VoxelTreeElement* element) const {
@@ -646,7 +660,7 @@ void VoxelSystem::setupNewVoxelsForDrawing() {
}
_inSetupNewVoxelsForDrawing = true;
-
+
bool didWriteFullVBO = _writeRenderFullVBO;
if (_tree->isDirty()) {
static char buffer[64] = { 0 };
@@ -673,7 +687,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 +696,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 +727,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 +739,7 @@ void VoxelSystem::setupNewVoxelsForDrawingSingleNode(bool allowBailEarly) {
// after...
_voxelsUpdated = 0;
- _bufferWriteLock.unlock();
+ _readArraysLock.unlock();
quint64 end = usecTimestampNow();
int elapsedmsec = (end - start) / 1000;
@@ -733,8 +747,73 @@ void VoxelSystem::setupNewVoxelsForDrawingSingleNode(bool allowBailEarly) {
_setupNewVoxelsForDrawingLastElapsed = elapsedmsec;
}
-void VoxelSystem::checkForCulling() {
+
+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 && inView) {
+ // recreate the geometry
+ args->thisVoxelSystem->updateNodeInArrays(voxel, false, true); // DONT_REUSE_INDEX, FORCE_REDRAW
+ }
+
+ 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() {
+
+ 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();
+
+ // 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() {
PerformanceWarning warn(Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings), "checkForCulling()");
quint64 start = usecTimestampNow();
@@ -762,7 +841,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();
@@ -880,12 +972,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()...";
}
}
@@ -1141,17 +1247,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()");
@@ -1197,7 +1313,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 +1521,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 +1529,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..121a7f86c4 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;
@@ -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);
@@ -211,6 +212,11 @@ private:
GLfloat* _readVerticesArray;
GLubyte* _readColorsArray;
+
+ QReadWriteLock _writeArraysLock;
+ QReadWriteLock _readArraysLock;
+
+
GLfloat* _writeVerticesArray;
GLubyte* _writeColorsArray;
bool* _writeVoxelDirtyArray;
@@ -253,9 +259,6 @@ private:
GLuint _vboIndicesFront;
GLuint _vboIndicesBack;
- QMutex _bufferWriteLock;
- QMutex _treeLock;
-
ViewFrustum _lastKnownViewFrustum;
ViewFrustum _lastStableViewFrustum;
ViewFrustum* _viewFrustum;
@@ -299,6 +302,9 @@ private:
bool _useFastVoxelPipeline;
bool _inhideOutOfView;
+
+ float _lastKnownVoxelSizeScale;
+ int _lastKnownBoundaryLevelAdjust;
};
#endif
diff --git a/interface/src/avatar/Avatar.cpp b/interface/src/avatar/Avatar.cpp
index f36c03cba6..3782b39136 100644
--- a/interface/src/avatar/Avatar.cpp
+++ b/interface/src/avatar/Avatar.cpp
@@ -349,13 +349,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/avatar/FaceModel.cpp b/interface/src/avatar/FaceModel.cpp
index b041f5bc2d..c9d2565cee 100644
--- a/interface/src/avatar/FaceModel.cpp
+++ b/interface/src/avatar/FaceModel.cpp
@@ -20,6 +20,7 @@ FaceModel::FaceModel(Head* owningHead) :
void FaceModel::simulate(float deltaTime) {
if (!isActive()) {
+ Model::simulate(deltaTime);
return;
}
Avatar* owningAvatar = static_cast(_owningHead->_owningAvatar);
diff --git a/interface/src/avatar/SkeletonModel.cpp b/interface/src/avatar/SkeletonModel.cpp
index ff4de8c41e..9bf2e0f727 100644
--- a/interface/src/avatar/SkeletonModel.cpp
+++ b/interface/src/avatar/SkeletonModel.cpp
@@ -21,9 +21,9 @@ 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;
diff --git a/interface/src/renderer/FBXReader.cpp b/interface/src/renderer/FBXReader.cpp
index 702b84ecda..35512d88da 100644
--- a/interface/src/renderer/FBXReader.cpp
+++ b/interface/src/renderer/FBXReader.cpp
@@ -1577,14 +1577,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 eb4c5bac41..8efb23c98c 100644
--- a/interface/src/renderer/FBXReader.h
+++ b/interface/src/renderer/FBXReader.h
@@ -164,9 +164,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..78cc657018 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"
@@ -298,46 +294,86 @@ 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) :
- _modelRequest(url),
- _modelReply(NULL),
- _mappingReply(NULL),
+const float NetworkGeometry::NO_HYSTERESIS = -1.0f;
+
+NetworkGeometry::NetworkGeometry(const QUrl& url, const QSharedPointer& fallback,
+ const QVariantHash& mapping, const QUrl& textureBase) :
+ _request(url),
+ _reply(NULL),
+ _mapping(mapping),
+ _textureBase(textureBase.isValid() ? textureBase : url),
_fallback(fallback),
- _attempts(0)
-{
+ _startedLoading(false),
+ _failedToLoad(false),
+ _attempts(0) {
+
if (!url.isValid()) {
return;
}
- _modelRequest.setAttribute(QNetworkRequest::CacheLoadControlAttribute, QNetworkRequest::PreferCache);
- makeModelRequest();
+ _request.setAttribute(QNetworkRequest::CacheLoadControlAttribute, QNetworkRequest::PreferCache);
- 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()));
+ // if we already have a mapping (because we're an LOD), hold off on loading until we're requested
+ if (mapping.isEmpty()) {
+ makeRequest();
+ }
}
NetworkGeometry::~NetworkGeometry() {
- if (_modelReply != NULL) {
- delete _modelReply;
+ if (_reply != NULL) {
+ delete _reply;
}
- if (_mappingReply != NULL) {
- delete _mappingReply;
+}
+
+QSharedPointer NetworkGeometry::getLODOrFallback(float distance, float& hysteresis) const {
+ if (_lodParent.data() != this) {
+ return _lodParent.data()->getLODOrFallback(distance, hysteresis);
}
- foreach (const NetworkMesh& mesh, _meshes) {
- glDeleteBuffers(1, &mesh.indexBufferID);
- glDeleteBuffers(1, &mesh.vertexBufferID);
- }
+ if (_failedToLoad && _fallback) {
+ return _fallback;
+ }
+ QSharedPointer lod = _lodParent;
+ float lodDistance = 0.0f;
+ QMap >::const_iterator it = _lods.upperBound(distance);
+ 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
+ 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;
+ }
+ }
+ hysteresis = NO_HYSTERESIS;
+ return lod;
}
glm::vec4 NetworkGeometry::computeAverageColor() const {
@@ -364,20 +400,167 @@ 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() {
+ _startedLoading = true;
+ _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.";
+ _failedToLoad = true;
+
+ } else {
+ QString texdir = _mapping.value("texdir").toString();
+ if (!texdir.isNull()) {
+ if (!texdir.endsWith('/')) {
+ texdir += '/';
+ }
+ _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));
+
+ // make the request immediately only if we have no LODs to switch between
+ _startedLoading = false;
+ if (_lods.isEmpty()) {
+ makeRequest();
+ }
+ }
+ return;
+ }
+
+ try {
+ _geometry = url.path().toLower().endsWith(".svo") ? readSVO(data) : readFBX(data, _mapping);
+
+ } catch (const QString& error) {
+ qDebug() << "Error reading " << url << ": " << error;
+ _failedToLoad = true;
+ 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);
+ }
+}
+
+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,164 +577,19 @@ 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;
}
// fall through to final failure
}
default:
- maybeLoadFallback();
+ _failedToLoad = true;
break;
}
}
-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;
- 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 618796e907..ef53fe9c3e 100644
--- a/interface/src/renderer/GeometryCache.h
+++ b/interface/src/renderer/GeometryCache.h
@@ -9,17 +9,20 @@
#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
#include "FBXReader.h"
-#include "InterfaceConfig.h"
class QNetworkReply;
-class QOpenGLBuffer;
class NetworkGeometry;
class NetworkMesh;
@@ -59,41 +62,52 @@ class NetworkGeometry : public QObject {
public:
- NetworkGeometry(const QUrl& url, const QSharedPointer& fallback);
+ /// A hysteresis value indicating that we have no state memory.
+ static const float NO_HYSTERESIS;
+
+ NetworkGeometry(const QUrl& url, const QSharedPointer& fallback,
+ 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.
+ /// \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; }
/// Returns the average color of all meshes in the geometry.
glm::vec4 computeAverageColor() const;
-signals:
-
- void loaded();
-
private slots:
- void makeModelRequest();
- void handleModelReplyError();
- void handleMappingReplyError();
- void maybeReadModelWithMapping();
- void loadFallback();
+ void makeRequest();
+ void handleDownloadProgress(qint64 bytesReceived, qint64 bytesTotal);
+ void handleReplyError();
private:
- void maybeLoadFallback();
+ friend class GeometryCache;
- QNetworkRequest _modelRequest;
- QNetworkReply* _modelReply;
- QNetworkReply* _mappingReply;
+ void setLODParent(const QWeakPointer& lodParent) { _lodParent = lodParent; }
+
+ QNetworkRequest _request;
+ QNetworkReply* _reply;
+ QVariantHash _mapping;
+ QUrl _textureBase;
QSharedPointer _fallback;
+ bool _startedLoading;
+ bool _failedToLoad;
int _attempts;
+ QMap > _lods;
FBXGeometry _geometry;
QVector _meshes;
+
+ QWeakPointer _lodParent;
};
/// The state associated with a single mesh part.
@@ -110,8 +124,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 7686b1ac7f..8c00842ea2 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()), _lodHysteresis);
+ if (_geometry != geometry) {
+ deleteGeometry();
+ _dilatedTextures.clear();
+ _geometry = geometry;
+ }
+ }
if (!isActive()) {
return;
}
@@ -409,8 +419,9 @@ void Model::setURL(const QUrl& url, const QUrl& fallback) {
// delete our local geometry and custom textures
deleteGeometry();
_dilatedTextures.clear();
+ _lodHysteresis = NetworkGeometry::NO_HYSTERESIS;
- _geometry = Application::getInstance()->getGeometryCache()->getGeometry(url, fallback);
+ _baseGeometry = _geometry = Application::getInstance()->getGeometryCache()->getGeometry(url, fallback);
}
glm::vec4 Model::computeAverageColor() const {
@@ -791,13 +802,17 @@ 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();
+ if (vertexCount == 0) {
+ // sanity check
+ continue;
+ }
+
+ const_cast(networkMesh.vertexBuffer).bind();
- glBindBuffer(GL_ARRAY_BUFFER, networkMesh.vertexBufferID);
-
ProgramObject* program = &_program;
ProgramObject* skinProgram = &_skinProgram;
SkinLocations* skinLocations = &_skinLocations;
@@ -904,11 +919,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);
diff --git a/interface/src/renderer/Model.h b/interface/src/renderer/Model.h
index bab25bed7a..41435c00b7 100644
--- a/interface/src/renderer/Model.h
+++ b/interface/src/renderer/Model.h
@@ -236,6 +236,9 @@ private:
void deleteGeometry();
void renderMeshes(float alpha, bool translucent);
+ QSharedPointer _baseGeometry;
+ float _lodHysteresis;
+
float _pupilDilation;
std::vector _blendshapeCoefficients;
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/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,
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();