diff --git a/interface/src/Menu.cpp b/interface/src/Menu.cpp index eea251603e..e0d6c4fa53 100644 --- a/interface/src/Menu.cpp +++ b/interface/src/Menu.cpp @@ -122,7 +122,8 @@ Menu::Menu() : _hasLoginDialogDisplayed(false), _snapshotsLocation(), _scriptsLocation(), - _walletPrivateKey() + _walletPrivateKey(), + _shouldRenderTableNeedsRebuilding(true) { Application *appInstance = Application::getInstance(); @@ -419,10 +420,12 @@ Menu::Menu() : addCheckableActionToQMenuAndActionHash(avatarDebugMenu, MenuOption::RenderFocusIndicator, 0, false); QMenu* modelDebugMenu = developerMenu->addMenu("Models"); - addCheckableActionToQMenuAndActionHash(modelDebugMenu, MenuOption::DontCullMeshParts, 0, false); addCheckableActionToQMenuAndActionHash(modelDebugMenu, MenuOption::DisplayModelBounds, 0, false); addCheckableActionToQMenuAndActionHash(modelDebugMenu, MenuOption::DisplayModelElementProxy, 0, false); addCheckableActionToQMenuAndActionHash(modelDebugMenu, MenuOption::DisplayModelElementChildProxies, 0, false); + QMenu* modelCullingMenu = modelDebugMenu->addMenu("Culling"); + addCheckableActionToQMenuAndActionHash(modelCullingMenu, MenuOption::DontCullOutOfViewMeshParts, 0, false); + addCheckableActionToQMenuAndActionHash(modelCullingMenu, MenuOption::DontCullTooSmallMeshParts, 0, false); QMenu* voxelOptionsMenu = developerMenu->addMenu("Voxels"); addCheckableActionToQMenuAndActionHash(voxelOptionsMenu, MenuOption::VoxelTextures); @@ -1547,6 +1550,7 @@ void Menu::autoAdjustLOD(float currentFPS) { && _voxelSizeScale > ADJUST_LOD_MIN_SIZE_SCALE) { _voxelSizeScale *= ADJUST_LOD_DOWN_BY; + if (_voxelSizeScale < ADJUST_LOD_MIN_SIZE_SCALE) { _voxelSizeScale = ADJUST_LOD_MIN_SIZE_SCALE; } @@ -1569,6 +1573,7 @@ void Menu::autoAdjustLOD(float currentFPS) { } if (changed) { + _shouldRenderTableNeedsRebuilding = true; if (_lodToolsDialog) { _lodToolsDialog->reloadSliders(); } @@ -1583,14 +1588,56 @@ void Menu::resetLODAdjust() { void Menu::setVoxelSizeScale(float sizeScale) { _voxelSizeScale = sizeScale; + _shouldRenderTableNeedsRebuilding = true; bumpSettings(); } void Menu::setBoundaryLevelAdjust(int boundaryLevelAdjust) { _boundaryLevelAdjust = boundaryLevelAdjust; + _shouldRenderTableNeedsRebuilding = true; bumpSettings(); } +// TODO: This is essentially the same logic used to render voxels, but since models are more detailed then voxels +// I've added a voxelToModelRatio that adjusts how much closer to a model you have to be to see it. +bool Menu::shouldRenderMesh(float largestDimension, float distanceToCamera) { + const float voxelToMeshRatio = 4.0f; // must be this many times closer to a mesh than a voxel to see it. + float voxelSizeScale = getVoxelSizeScale(); + int boundaryLevelAdjust = getBoundaryLevelAdjust(); + float maxScale = (float)TREE_SCALE; + float visibleDistanceAtMaxScale = boundaryDistanceForRenderLevel(boundaryLevelAdjust, voxelSizeScale) / voxelToMeshRatio; + + if (_shouldRenderTableNeedsRebuilding) { + _shouldRenderTable.clear(); + + float SMALLEST_SCALE_IN_TABLE = 0.001f; // 1mm is plenty small + float scale = maxScale; + float visibleDistanceAtScale = visibleDistanceAtMaxScale; + + while (scale > SMALLEST_SCALE_IN_TABLE) { + scale /= 2.0f; + visibleDistanceAtScale /= 2.0f; + _shouldRenderTable[scale] = visibleDistanceAtScale; + } + _shouldRenderTableNeedsRebuilding = false; + } + + float closestScale = maxScale; + float visibleDistanceAtClosestScale = visibleDistanceAtMaxScale; + QMap::const_iterator lowerBound = _shouldRenderTable.lowerBound(largestDimension); + if (lowerBound != _shouldRenderTable.constEnd()) { + closestScale = lowerBound.key(); + visibleDistanceAtClosestScale = lowerBound.value(); + } + + if (closestScale < largestDimension) { + visibleDistanceAtClosestScale *= 2.0f; + } + + return (distanceToCamera <= visibleDistanceAtClosestScale); +} + + void Menu::lodTools() { if (!_lodToolsDialog) { _lodToolsDialog = new LodToolsDialog(Application::getInstance()->getGLWidget()); diff --git a/interface/src/Menu.h b/interface/src/Menu.h index d29c108eb3..7563a2ee4b 100644 --- a/interface/src/Menu.h +++ b/interface/src/Menu.h @@ -143,6 +143,8 @@ public: void setBoundaryLevelAdjust(int boundaryLevelAdjust); int getBoundaryLevelAdjust() const { return _boundaryLevelAdjust; } + bool shouldRenderMesh(float largestDimension, float distanceToCamera); + #ifdef Q_OS_MAC SpeechRecognizer* getSpeechRecognizer() { return &_speechRecognizer; } #endif @@ -310,6 +312,9 @@ private: QString _snapshotsLocation; QString _scriptsLocation; QByteArray _walletPrivateKey; + + bool _shouldRenderTableNeedsRebuilding; + QMap _shouldRenderTable; }; @@ -367,7 +372,8 @@ namespace MenuOption { const QString Collisions = "Collisions"; const QString Console = "Console..."; const QString ControlWithSpeech = "Control With Speech"; - const QString DontCullMeshParts = "Don't Cull Mesh Parts"; + const QString DontCullOutOfViewMeshParts = "Don't Cull Out Of View Mesh Parts"; + const QString DontCullTooSmallMeshParts = "Don't Cull Too Small Mesh Parts"; const QString DecreaseAvatarSize = "Decrease Avatar Size"; const QString DecreaseVoxelSize = "Decrease Voxel Size"; const QString DisableActivityLogger = "Disable Activity Logger"; diff --git a/interface/src/entities/EntityTreeRenderer.cpp b/interface/src/entities/EntityTreeRenderer.cpp index c7849c136d..2c7c970376 100644 --- a/interface/src/entities/EntityTreeRenderer.cpp +++ b/interface/src/entities/EntityTreeRenderer.cpp @@ -162,36 +162,6 @@ void renderElementProxy(EntityTreeElement* entityTreeElement) { } } -float EntityTreeRenderer::distanceToCamera(const glm::vec3& center, const ViewFrustum& viewFrustum) const { - glm::vec3 temp = viewFrustum.getPosition() - center; - float distanceToVoxelCenter = sqrtf(glm::dot(temp, temp)); - return distanceToVoxelCenter; -} - -// TODO: This could be optimized to be a table, or something that doesn't require recalculation on every -// render call for every entity -// TODO: This is essentially the same logic used to render voxels, but since models are more detailed then voxels -// I've added a voxelToModelRatio that adjusts how much closer to a model you have to be to see it. -bool EntityTreeRenderer::shouldRenderEntity(float largestDimension, float distanceToCamera) const { - const float voxelToModelRatio = 4.0f; // must be this many times closer to a model than a voxel to see it. - float voxelSizeScale = Menu::getInstance()->getVoxelSizeScale(); - int boundaryLevelAdjust = Menu::getInstance()->getBoundaryLevelAdjust(); - - float scale = (float)TREE_SCALE; - float visibleDistanceAtScale = boundaryDistanceForRenderLevel(boundaryLevelAdjust, voxelSizeScale) / voxelToModelRatio; - - while (scale > largestDimension) { - scale /= 2.0f; - visibleDistanceAtScale /= 2.0f; - } - - if (scale < largestDimension) { - visibleDistanceAtScale *= 2.0f; - } - - return (distanceToCamera <= visibleDistanceAtScale); -} - void EntityTreeRenderer::renderProxies(const EntityItem* entity, RenderArgs* args) { bool isShadowMode = args->_renderMode == OctreeRenderer::SHADOW_RENDER_MODE; bool displayModelBounds = Menu::getInstance()->isOptionChecked(MenuOption::DisplayModelBounds); @@ -287,7 +257,7 @@ void EntityTreeRenderer::renderElement(OctreeElement* element, RenderArgs* args) // TODO: some entity types (like lights) might want to be rendered even // when they are outside of the view frustum... - float distance = distanceToCamera(entityBox.calcCenter(), *args->_viewFrustum); + float distance = args->_viewFrustum->distanceToCamera(entityBox.calcCenter()); if (wantDebug) { qDebug() << "------- renderElement() ----------"; @@ -299,24 +269,28 @@ void EntityTreeRenderer::renderElement(OctreeElement* element, RenderArgs* args) qDebug() << " entityBox:" << entityItem->getAABox(); qDebug() << " dimensions:" << entityItem->getDimensionsInMeters() << "in meters"; qDebug() << " largestDimension:" << entityBox.getLargestDimension() << "in meters"; - qDebug() << " shouldRender:" << shouldRenderEntity(entityBox.getLargestDimension(), distance); + qDebug() << " shouldRender:" << Menu::getInstance()->shouldRenderMesh(entityBox.getLargestDimension(), distance); qDebug() << " in frustum:" << (args->_viewFrustum->boxInFrustum(entityBox) != ViewFrustum::OUTSIDE); } - - if (shouldRenderEntity(entityBox.getLargestDimension(), distance) && - args->_viewFrustum->boxInFrustum(entityBox) != ViewFrustum::OUTSIDE) { - - - renderProxies(entityItem, args); - Glower* glower = NULL; - if (entityItem->getGlowLevel() > 0.0f) { - glower = new Glower(entityItem->getGlowLevel()); - } - entityItem->render(args); - args->_itemsRendered++; - if (glower) { - delete glower; + bool outOfView = args->_viewFrustum->boxInFrustum(entityBox) == ViewFrustum::OUTSIDE; + if (!outOfView) { + bool bigEnoughToRender = Menu::getInstance()->shouldRenderMesh(entityBox.getLargestDimension(), distance); + + if (bigEnoughToRender) { + renderProxies(entityItem, args); + + Glower* glower = NULL; + if (entityItem->getGlowLevel() > 0.0f) { + glower = new Glower(entityItem->getGlowLevel()); + } + entityItem->render(args); + args->_itemsRendered++; + if (glower) { + delete glower; + } + } else { + args->_itemsTooSmall++; } } else { args->_itemsOutOfView++; diff --git a/interface/src/entities/EntityTreeRenderer.h b/interface/src/entities/EntityTreeRenderer.h index 9e0368b61b..7ce984a97d 100644 --- a/interface/src/entities/EntityTreeRenderer.h +++ b/interface/src/entities/EntityTreeRenderer.h @@ -76,11 +76,7 @@ public: void deleteReleasedModels(); private: QList _releasedModels; - - float distanceToCamera(const glm::vec3& center, const ViewFrustum& viewFrustum) const; - bool shouldRenderEntity(float largestDimension, float distanceToCamera) const; void renderProxies(const EntityItem* entity, RenderArgs* args); - }; #endif // hifi_EntityTreeRenderer_h diff --git a/interface/src/renderer/Model.cpp b/interface/src/renderer/Model.cpp index 72a6ab7afe..40359c309d 100644 --- a/interface/src/renderer/Model.cpp +++ b/interface/src/renderer/Model.cpp @@ -47,7 +47,7 @@ Model::Model(QObject* parent) : _blendNumber(0), _appliedBlendNumber(0), _calculatedMeshBoxesValid(false), - _meshesGroupsKnown(false) { + _meshGroupsKnown(false) { // we may have been created in the network thread, but we live in the main thread moveToThread(Application::getInstance()->thread()); @@ -273,7 +273,7 @@ void Model::reset() { _jointStates[i].setRotationInConstrainedFrame(geometry.joints.at(i).rotation, 0.0f); } - _meshesGroupsKnown = false; + _meshGroupsKnown = false; } bool Model::updateGeometry() { @@ -323,7 +323,7 @@ bool Model::updateGeometry() { deleteGeometry(); _dilatedTextures.clear(); _geometry = geometry; - _meshesGroupsKnown = false; + _meshGroupsKnown = false; setJointStates(newJointStates); needToRebuild = true; } else if (_jointStates.isEmpty()) { @@ -426,7 +426,7 @@ bool Model::render(float alpha, RenderMode mode, RenderArgs* args) { } } - if (!_meshesGroupsKnown) { + if (!_meshGroupsKnown) { segregateMeshGroups(); } @@ -1247,7 +1247,7 @@ void Model::applyNextGeometry() { // we retain a reference to the base geometry so that its reference count doesn't fall to zero _baseGeometry = _nextBaseGeometry; _geometry = _nextGeometry; - _meshesGroupsKnown = false; + _meshGroupsKnown = false; _nextBaseGeometry.reset(); _nextGeometry.reset(); } @@ -1380,7 +1380,7 @@ void Model::segregateMeshGroups() { qDebug() << "unexpected!!! this mesh didn't fall into any or our groups???"; } } - _meshesGroupsKnown = true; + _meshGroupsKnown = true; } int Model::renderMeshes(RenderMode mode, bool translucent, float alphaThreshold, @@ -1390,10 +1390,8 @@ int Model::renderMeshes(RenderMode mode, bool translucent, float alphaThreshold, const FBXGeometry& geometry = _geometry->getFBXGeometry(); const QVector& networkMeshes = _geometry->getMeshes(); - bool cullMeshParts = args && !Menu::getInstance()->isOptionChecked(MenuOption::DontCullMeshParts); - // depending on which parameters we were called with, pick the correct mesh group to render - QList* whichList = NULL; + QVector* whichList = NULL; if (translucent && !hasTangents && !hasSpecular && !isSkinned) { whichList = &_meshesTranslucent; } else if (translucent && hasTangents && !hasSpecular && !isSkinned) { @@ -1434,7 +1432,7 @@ int Model::renderMeshes(RenderMode mode, bool translucent, float alphaThreshold, qDebug() << "unexpected!!! we don't know which list of meshes to render..."; return 0; } - QList& list = *whichList; + QVector& list = *whichList; ProgramObject* program = &_program; Locations* locations = &_locations; @@ -1486,6 +1484,14 @@ int Model::renderMeshes(RenderMode mode, bool translucent, float alphaThreshold, // i is the "index" from the original networkMeshes QVector... foreach (int i, list) { + + // if our index is ever out of range for either meshes or networkMeshes, then skip it, and set our _meshGroupsKnown + // to false to rebuild out mesh groups. + + if (i < 0 || i >= networkMeshes.size() || i > geometry.meshes.size()) { + _meshGroupsKnown = false; // regenerate these lists next time around. + continue; + } // exit early if the translucency doesn't match what we're drawing const NetworkMesh& networkMesh = networkMeshes.at(i); @@ -1501,17 +1507,30 @@ int Model::renderMeshes(RenderMode mode, bool translucent, float alphaThreshold, // if we got here, then check to see if this mesh is in view if (args) { + bool dontCullOutOfViewMeshParts = Menu::getInstance()->isOptionChecked(MenuOption::DontCullOutOfViewMeshParts); + bool cullTooSmallMeshParts = !Menu::getInstance()->isOptionChecked(MenuOption::DontCullTooSmallMeshParts); + bool shouldRender = true; args->_meshesConsidered++; - if (cullMeshParts && args->_viewFrustum) { - shouldRender = args->_viewFrustum->boxInFrustum(_calculatedMeshBoxes.at(i)) != ViewFrustum::OUTSIDE; + if (args->_viewFrustum) { + shouldRender = dontCullOutOfViewMeshParts || + args->_viewFrustum->boxInFrustum(_calculatedMeshBoxes.at(i)) != ViewFrustum::OUTSIDE; + if (shouldRender && cullTooSmallMeshParts) { + float distance = args->_viewFrustum->distanceToCamera(_calculatedMeshBoxes.at(i).calcCenter()); + shouldRender = Menu::getInstance()->shouldRenderMesh(_calculatedMeshBoxes.at(i).getLargestDimension(), + distance); + if (!shouldRender) { + args->_meshesTooSmall++; + } + } else { + args->_meshesOutOfView++; + } } if (shouldRender) { args->_meshesRendered++; } else { - args->_meshesOutOfView++; continue; // skip this mesh } } diff --git a/interface/src/renderer/Model.h b/interface/src/renderer/Model.h index 55a2eed9d1..f03e3f5e38 100644 --- a/interface/src/renderer/Model.h +++ b/interface/src/renderer/Model.h @@ -31,6 +31,7 @@ class QScriptEngine; class AnimationHandle; class Shape; class RenderArgs; +class ViewFrustum; typedef QSharedPointer AnimationHandlePointer; typedef QWeakPointer WeakAnimationHandlePointer; @@ -335,27 +336,27 @@ private: void segregateMeshGroups(); // used to calculate our list of translucent vs opaque meshes - bool _meshesGroupsKnown; + bool _meshGroupsKnown; - QList _meshesTranslucent; - QList _meshesTranslucentTangents; - QList _meshesTranslucentTangentsSpecular; - QList _meshesTranslucentSpecular; + QVector _meshesTranslucent; + QVector _meshesTranslucentTangents; + QVector _meshesTranslucentTangentsSpecular; + QVector _meshesTranslucentSpecular; - QList _meshesTranslucentSkinned; - QList _meshesTranslucentTangentsSkinned; - QList _meshesTranslucentTangentsSpecularSkinned; - QList _meshesTranslucentSpecularSkinned; + QVector _meshesTranslucentSkinned; + QVector _meshesTranslucentTangentsSkinned; + QVector _meshesTranslucentTangentsSpecularSkinned; + QVector _meshesTranslucentSpecularSkinned; - QList _meshesOpaque; - QList _meshesOpaqueTangents; - QList _meshesOpaqueTangentsSpecular; - QList _meshesOpaqueSpecular; + QVector _meshesOpaque; + QVector _meshesOpaqueTangents; + QVector _meshesOpaqueTangentsSpecular; + QVector _meshesOpaqueSpecular; - QList _meshesOpaqueSkinned; - QList _meshesOpaqueTangentsSkinned; - QList _meshesOpaqueTangentsSpecularSkinned; - QList _meshesOpaqueSpecularSkinned; + QVector _meshesOpaqueSkinned; + QVector _meshesOpaqueTangentsSkinned; + QVector _meshesOpaqueTangentsSpecularSkinned; + QVector _meshesOpaqueSpecularSkinned; }; diff --git a/interface/src/ui/Stats.cpp b/interface/src/ui/Stats.cpp index e78297323a..90e21e9caf 100644 --- a/interface/src/ui/Stats.cpp +++ b/interface/src/ui/Stats.cpp @@ -459,7 +459,7 @@ void Stats::display( VoxelSystem* voxels = Application::getInstance()->getVoxels(); - lines = _expanded ? 14 : 3; + lines = _expanded ? 15 : 3; if (_expanded && Menu::getInstance()->isOptionChecked(MenuOption::AudioSpatialProcessing)) { lines += 10; // spatial audio processing adds 1 spacing line and 8 extra lines of info } @@ -472,12 +472,16 @@ void Stats::display( // Model/Entity render details EntityTreeRenderer* entities = Application::getInstance()->getEntities(); voxelStats.str(""); - voxelStats << "Entity Items rendered: " << entities->getItemsRendered() << " Out of view:" << entities->getItemsOutOfView(); + voxelStats << "Entity Items rendered: " << entities->getItemsRendered() + << " Out of view:" << entities->getItemsOutOfView() + << " Too small:" << entities->getItemsTooSmall(); verticalOffset += STATS_PELS_PER_LINE; drawText(horizontalOffset, verticalOffset, scale, rotation, font, (char*)voxelStats.str().c_str(), color); voxelStats.str(""); - voxelStats << "Meshes rendered: " << entities->getMeshesRendered() << " Out of view:" << entities->getMeshesOutOfView(); + voxelStats << "Meshes rendered: " << entities->getMeshesRendered() + << " Out of view:" << entities->getMeshesOutOfView() + << " Too small:" << entities->getMeshesTooSmall(); verticalOffset += STATS_PELS_PER_LINE; drawText(horizontalOffset, verticalOffset, scale, rotation, font, (char*)voxelStats.str().c_str(), color); diff --git a/libraries/octree/src/OctreeRenderer.cpp b/libraries/octree/src/OctreeRenderer.cpp index 21c085c5df..006255bfff 100644 --- a/libraries/octree/src/OctreeRenderer.cpp +++ b/libraries/octree/src/OctreeRenderer.cpp @@ -162,7 +162,8 @@ bool OctreeRenderer::renderOperation(OctreeElement* element, void* extraData) { } void OctreeRenderer::render(RenderMode renderMode) { - RenderArgs args = { this, _viewFrustum, getSizeScale(), getBoundaryLevelAdjust(), renderMode, 0, 0, 0, 0, 0, 0, 0, 0 }; + RenderArgs args = { this, _viewFrustum, getSizeScale(), getBoundaryLevelAdjust(), renderMode, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; if (_tree) { _tree->lockForRead(); _tree->recurseTreeWithOperation(renderOperation, &args); @@ -171,10 +172,12 @@ void OctreeRenderer::render(RenderMode renderMode) { _meshesConsidered = args._meshesConsidered; _meshesRendered = args._meshesRendered; _meshesOutOfView = args._meshesOutOfView; + _meshesTooSmall = args._meshesTooSmall; _elementsTouched = args._elementsTouched; _itemsRendered = args._itemsRendered; _itemsOutOfView = args._itemsOutOfView; + _itemsTooSmall = args._itemsTooSmall; _trianglesRendered = args._trianglesRendered; _quadsRendered = args._quadsRendered; diff --git a/libraries/octree/src/OctreeRenderer.h b/libraries/octree/src/OctreeRenderer.h index 6d734d113e..528235ed86 100644 --- a/libraries/octree/src/OctreeRenderer.h +++ b/libraries/octree/src/OctreeRenderer.h @@ -67,10 +67,12 @@ public: int getElementsTouched() const { return _elementsTouched; } int getItemsRendered() const { return _itemsRendered; } int getItemsOutOfView() const { return _itemsOutOfView; } + int getItemsTooSmall() const { return _itemsTooSmall; } int getMeshesConsidered() const { return _meshesConsidered; } int getMeshesRendered() const { return _meshesRendered; } int getMeshesOutOfView() const { return _meshesOutOfView; } + int getMeshesTooSmall() const { return _meshesTooSmall; } int getTrianglesRendered() const { return _trianglesRendered; } int getQuadsRendered() const { return _quadsRendered; } @@ -86,10 +88,12 @@ protected: int _elementsTouched; int _itemsRendered; int _itemsOutOfView; + int _itemsTooSmall; int _meshesConsidered; int _meshesRendered; int _meshesOutOfView; + int _meshesTooSmall; int _trianglesRendered; int _quadsRendered; @@ -109,10 +113,12 @@ public: int _elementsTouched; int _itemsRendered; int _itemsOutOfView; + int _itemsTooSmall; int _meshesConsidered; int _meshesRendered; int _meshesOutOfView; + int _meshesTooSmall; int _trianglesRendered; int _quadsRendered; diff --git a/libraries/octree/src/ViewFrustum.cpp b/libraries/octree/src/ViewFrustum.cpp index 1e8dc41cdd..c1348e28c7 100644 --- a/libraries/octree/src/ViewFrustum.cpp +++ b/libraries/octree/src/ViewFrustum.cpp @@ -876,3 +876,10 @@ void ViewFrustum::getFurthestPointFromCameraVoxelScale(const AACube& box, glm::v } } +float ViewFrustum::distanceToCamera(const glm::vec3& point) const { + glm::vec3 temp = getPosition() - point; + float distanceToPoint = sqrtf(glm::dot(temp, temp)); + return distanceToPoint; +} + + diff --git a/libraries/octree/src/ViewFrustum.h b/libraries/octree/src/ViewFrustum.h index a23ec2af92..4b2b57fab2 100644 --- a/libraries/octree/src/ViewFrustum.h +++ b/libraries/octree/src/ViewFrustum.h @@ -118,6 +118,8 @@ public: // assumes box is in voxel scale, not TREE_SCALE, will scale view frustum's position accordingly void getFurthestPointFromCameraVoxelScale(const AACube& box, glm::vec3& furthestPoint) const; + + float distanceToCamera(const glm::vec3& point) const; private: // Used for keyhole calculations