diff --git a/KREngine/KREngine/Classes/KRAABB.cpp b/KREngine/KREngine/Classes/KRAABB.cpp index 119fe4f..f35f321 100644 --- a/KREngine/KREngine/Classes/KRAABB.cpp +++ b/KREngine/KREngine/Classes/KRAABB.cpp @@ -8,6 +8,7 @@ #include "KRAABB.h" #include "KRMat4.h" +#include "KRVector2.h" #include "assert.h" KRAABB::KRAABB(const KRVector3 &minPoint, const KRVector3 &maxPoint) @@ -182,3 +183,31 @@ bool KRAABB::visible(const KRMat4 &matViewProjection) const //is_visible = true; return is_visible; } + + +float KRAABB::coverage(const KRMat4 &matMVP, const KRVector2 viewportSize) const +{ + if(!visible(matMVP)) { + return 0.0f; // Culled out by view frustrum + } else { + KRVector2 screen_min; + KRVector2 screen_max; + // Loop through all corners and transform them to screen space + for(int i=0; i<8; i++) { + KRVector3 screen_pos = KRMat4::Dot(matMVP, KRVector3(i & 1 ? min.x : max.x, i & 2 ? min.y : max.y, i & 4 ? min.z :max.z)); + if(i==0) { + screen_min.x = screen_pos.x; + screen_min.y = screen_pos.y; + screen_max.x = screen_pos.x; + screen_max.y = screen_pos.y; + } else { + if(screen_pos.x < screen_min.x) screen_min.x = screen_pos.x; + if(screen_pos.y < screen_min.y) screen_min.y = screen_pos.y; + if(screen_pos.x > screen_max.x) screen_max.x = screen_pos.x; + if(screen_pos.y > screen_max.y) screen_max.y = screen_pos.y; + } + } + + return (screen_max.x - screen_min.x) / viewportSize.x * (screen_max.y - screen_min.y) / viewportSize.y; + } +} \ No newline at end of file diff --git a/KREngine/KREngine/Classes/KRAABB.h b/KREngine/KREngine/Classes/KRAABB.h index 481ba5c..53be5b1 100644 --- a/KREngine/KREngine/Classes/KRAABB.h +++ b/KREngine/KREngine/Classes/KRAABB.h @@ -14,6 +14,7 @@ #include "KRVector3.h" class KRMat4; +class KRVector2; class KRAABB { public: @@ -39,6 +40,7 @@ public: KRVector3 max; static KRAABB Infinite(); + float coverage(const KRMat4 &matMVP, const KRVector2 viewportSize) const; }; diff --git a/KREngine/KREngine/Classes/KRInstance.cpp b/KREngine/KREngine/Classes/KRInstance.cpp index 3c54646..577abb2 100644 --- a/KREngine/KREngine/Classes/KRInstance.cpp +++ b/KREngine/KREngine/Classes/KRInstance.cpp @@ -35,15 +35,15 @@ #import "KRModel.h" #include -KRInstance::KRInstance(KRScene &scene, std::string instance_name, std::string model_name, std::string light_map) : KRNode(scene, instance_name) { +KRInstance::KRInstance(KRScene &scene, std::string instance_name, std::string model_name, std::string light_map, float lod_min_coverage) : KRNode(scene, instance_name) { m_lightMap = light_map; m_pLightMap = NULL; - m_pModel = NULL; m_model_name = model_name; + m_min_lod_coverage = lod_min_coverage; } KRInstance::~KRInstance() { - + } std::string KRInstance::getElementName() { @@ -55,6 +55,7 @@ tinyxml2::XMLElement *KRInstance::saveXML( tinyxml2::XMLNode *parent) tinyxml2::XMLElement *e = KRNode::saveXML(parent); e->SetAttribute("mesh_name", m_model_name.c_str()); e->SetAttribute("light_map", m_lightMap.c_str()); + e->SetAttribute("lod_min_coverage", m_min_lod_coverage); return e; } @@ -65,9 +66,9 @@ KRMat4 &KRInstance::getModelMatrix() { } void KRInstance::loadModel() { - if(m_pModel == NULL) { - m_pModel = m_pContext->getModelManager()->getModel(m_model_name.c_str()); - if(m_pModel != NULL) { + if(m_models.size() == 0) { + m_models = m_pContext->getModelManager()->getModel(m_model_name.c_str()); // The model manager returns the LOD levels in sorted order, with the highest detail first + if(m_models.size() > 0) { getScene().notify_sceneGraphModify(this); } // if(m_pModel == NULL) { @@ -89,14 +90,28 @@ void KRInstance::render(KRCamera *pCamera, KRContext *pContext, KRMat4 &viewMatr loadModel(); - KRMat4 projectionMatrix; - if(renderPass != KRNode::RENDER_PASS_SHADOWMAP) { - projectionMatrix = pCamera->getProjectionMatrix(); - } - - if(m_pModel != NULL) { - if(getBounds().visible(viewMatrix * projectionMatrix)) { - + if(m_models.size() > 0) { + KRMat4 projectionMatrix; + if(renderPass != KRNode::RENDER_PASS_SHADOWMAP) { + projectionMatrix = pCamera->getProjectionMatrix(); + } + KRMat4 matVP = viewMatrix * projectionMatrix; + float lod_coverage = getBounds().coverage(matVP, pCamera->getViewportSize()); // This also checks the view frustrum culling + if(lod_coverage > m_min_lod_coverage) { + + // ---===--- Select the best LOD model based on screen coverage ---===--- + std::vector::iterator itr=m_models.begin(); + KRModel *pModel = *itr++; + + while(itr != m_models.end()) { + KRModel *pLODModel = *itr++; + if((float)pLODModel->getLODCoverage() / 100.0f > lod_coverage) { + pModel = pLODModel; + } else { + break; + } + } + if(m_pLightMap == NULL && m_lightMap.size()) { m_pLightMap = pContext->getTextureManager()->getTexture(m_lightMap.c_str()); } @@ -121,7 +136,7 @@ void KRInstance::render(KRCamera *pCamera, KRContext *pContext, KRMat4 &viewMatr KRVector3 cameraPosObject = KRMat4::Dot(inverseModelMatrix, cameraPosition); KRVector3 lightDirObject = KRMat4::Dot(inverseModelMatrix, lightDirection); - m_pModel->render(pCamera, pContext, viewMatrix, matModelToView, mvpmatrix, lightDirection, pShadowMatrices, shadowDepthTextures, cShadowBuffers, m_pLightMap, renderPass); + pModel->render(pCamera, pContext, viewMatrix, matModelToView, mvpmatrix, lightDirection, pShadowMatrices, shadowDepthTextures, cShadowBuffers, m_pLightMap, renderPass); } } } @@ -130,8 +145,8 @@ void KRInstance::render(KRCamera *pCamera, KRContext *pContext, KRMat4 &viewMatr #endif bool KRInstance::hasTransparency() { - if(m_pModel) { - return m_pModel->hasTransparency(); + if(m_models.size() > 0) { + return m_models[0]->hasTransparency(); } else { return false; } @@ -142,9 +157,9 @@ KRAABB KRInstance::getBounds() { loadModel(); KRVector3 meshMin, meshMax; - if(m_pModel) { - meshMin = m_pModel->getMinPoint(); - meshMax = m_pModel->getMaxPoint(); + if(m_models.size() > 0) { + meshMin = m_models[0]->getMinPoint(); + meshMax = m_models[0]->getMaxPoint(); } else { meshMin = -KRVector3::Max(); meshMax = KRVector3::Max(); diff --git a/KREngine/KREngine/Classes/KRInstance.h b/KREngine/KREngine/Classes/KRInstance.h index 009ca96..64fdc55 100644 --- a/KREngine/KREngine/Classes/KRInstance.h +++ b/KREngine/KREngine/Classes/KRInstance.h @@ -50,7 +50,7 @@ class KRInstance : public KRNode { public: - KRInstance(KRScene &scene, std::string instance_name, std::string model_name, std::string light_map); + KRInstance(KRScene &scene, std::string instance_name, std::string model_name, std::string light_map, float lod_min_coverage); virtual ~KRInstance(); virtual std::string getElementName(); @@ -70,12 +70,14 @@ public: void calcModelMatrix(); private: - KRModel *m_pModel; + std::vector m_models; KRMat4 m_modelMatrix; KRTexture *m_pLightMap; std::string m_lightMap; std::string m_model_name; + + float m_min_lod_coverage; void loadModel(); }; diff --git a/KREngine/KREngine/Classes/KRModel.cpp b/KREngine/KREngine/Classes/KRModel.cpp index 50b9db9..2ab41b9 100644 --- a/KREngine/KREngine/Classes/KRModel.cpp +++ b/KREngine/KREngine/Classes/KRModel.cpp @@ -52,6 +52,23 @@ KRModel::KRModel(KRContext &context, std::string name) : KRResource(context, nam m_materials.clear(); m_uniqueMaterials.clear(); m_pData = new KRDataBlock(); + m_lodCoverage = 100; // Default to being the full detail mesh; not an LOD level + m_lodBaseName = name; + + size_t last_underscore_pos = name.find_last_of('_'); + if(last_underscore_pos != std::string::npos) { + // Found an underscore + std::string suffix = name.substr(last_underscore_pos + 1); + if(suffix.find_first_of("lod") == 0) { + std::string lod_level_string = suffix.substr(3); + char *end = NULL; + int c = (int)strtol(suffix.c_str(), &end, 10); + if(m_lodCoverage < 0 || m_lodCoverage > 100 || *end != '\0') { + m_lodCoverage = c; + m_lodBaseName = name.substr(0, last_underscore_pos - 1); + } + } + } } KRModel::KRModel(KRContext &context, std::string name, KRDataBlock *data) : KRResource(context, name) { @@ -59,6 +76,7 @@ KRModel::KRModel(KRContext &context, std::string name, KRDataBlock *data) : KRRe m_materials.clear(); m_uniqueMaterials.clear(); m_pData = new KRDataBlock(); + m_lodCoverage = 100; loadPack(data); } @@ -434,3 +452,17 @@ void KRModel::clearBuffers() { m_submeshes.clear(); } +int KRModel::getLODCoverage() const { + return m_lodCoverage; +} + +std::string KRModel::getLODBaseName() const { + return m_lodBaseName; +} + +// Predicate used with std::sort to sort by highest detail model first, decending to lowest detail LOD model +bool KRModel::lod_sort_predicate(const KRModel *m1, const KRModel *m2) +{ + return m1->m_lodCoverage > m2->m_lodCoverage; +} + diff --git a/KREngine/KREngine/Classes/KRModel.h b/KREngine/KREngine/Classes/KRModel.h index d1a7131..075ed59 100644 --- a/KREngine/KREngine/Classes/KRModel.h +++ b/KREngine/KREngine/Classes/KRModel.h @@ -70,6 +70,8 @@ public: #endif + std::string m_lodBaseName; + virtual std::string getExtension(); virtual bool save(const std::string& path); @@ -119,8 +121,14 @@ public: char szName[256]; } pack_material; + int getLODCoverage() const; + std::string getLODBaseName() const; + + + static bool lod_sort_predicate(const KRModel *m1, const KRModel *m2); -private: +private: + int m_lodCoverage; // This LOD level is activated when the bounding box of the model will cover less than this percent of the screen (100 = highest detail model) vector m_materials; set m_uniqueMaterials; diff --git a/KREngine/KREngine/Classes/KRModelManager.cpp b/KREngine/KREngine/Classes/KRModelManager.cpp index 78ea06b..b0e4bc9 100644 --- a/KREngine/KREngine/Classes/KRModelManager.cpp +++ b/KREngine/KREngine/Classes/KRModelManager.cpp @@ -46,26 +46,45 @@ KRModelManager::KRModelManager(KRContext &context) : KRContextObject(context) { } KRModelManager::~KRModelManager() { - for(map::iterator itr = m_models.begin(); itr != m_models.end(); ++itr){ + for(std::multimap::iterator itr = m_models.begin(); itr != m_models.end(); ++itr){ delete (*itr).second; } m_models.empty(); } KRModel *KRModelManager::loadModel(const char *szName, KRDataBlock *pData) { - KRModel *pModel = new KRModel(*m_pContext, szName, pData); - m_models[szName] = pModel; + + std::string lowerName = szName; + std::transform(lowerName.begin(), lowerName.end(), + lowerName.begin(), ::tolower); + + + KRModel *pModel = new KRModel(*m_pContext, lowerName, pData); + m_models.insert(std::pair(pModel->getName(), pModel)); + return pModel; } -KRModel *KRModelManager::getModel(const char *szName) { - std::map::iterator itr_match = m_models.find(szName); - if(itr_match == m_models.end()) { - fprintf(stderr, "ERROR: Model not found: %s\n", szName); - return NULL; - } else { - return itr_match->second; +std::vector KRModelManager::getModel(const char *szName) { + std::string lowerName = szName; + std::transform(lowerName.begin(), lowerName.end(), + lowerName.begin(), ::tolower); + + + std::vector matching_models; + + std::pair::iterator, std::multimap::iterator> range = m_models.equal_range(lowerName); + for(std::multimap::iterator itr_match = range.first; itr_match != range.second; itr_match++) { + matching_models.push_back(itr_match->second); } + + std::sort(matching_models.begin(), matching_models.end(), KRModel::lod_sort_predicate); + + if(matching_models.size() == 0) { + fprintf(stderr, "ERROR: Model not found: %s\n", lowerName.c_str()); + } + + return matching_models; } KRModel *KRModelManager::getFirstModel() { @@ -73,7 +92,7 @@ KRModel *KRModelManager::getFirstModel() { return (*model_itr).second; } -std::map KRModelManager::getModels() { +std::multimap KRModelManager::getModels() { return m_models; } diff --git a/KREngine/KREngine/Classes/KRModelManager.h b/KREngine/KREngine/Classes/KRModelManager.h index acfc9de..a913a90 100644 --- a/KREngine/KREngine/Classes/KRModelManager.h +++ b/KREngine/KREngine/Classes/KRModelManager.h @@ -54,11 +54,11 @@ public: void rotateBuffers(bool new_frame); KRModel *loadModel(const char *szName, KRDataBlock *pData); - KRModel *getModel(const char *szName); + std::vector getModel(const char *szName); KRModel *getFirstModel(); std::vector getModelNames(); - std::map getModels(); + std::multimap getModels(); void bindVBO(GLvoid *data, GLsizeiptr size, bool enable_vertex, bool enable_normal, bool enable_tangent, bool enable_uva, bool enable_uvb); @@ -68,7 +68,7 @@ public: void configureAttribs(bool enable_vertex, bool enable_normal, bool enable_tangent, bool enable_uva, bool enable_uvb); private: - std::map m_models; + std::multimap m_models; // Multiple models with the same name/key may be inserted, representing multiple LOD levels of the model typedef struct vbo_info { GLuint handle; diff --git a/KREngine/KREngine/Classes/KRNode.cpp b/KREngine/KREngine/Classes/KRNode.cpp index 1d8e4eb..ed4fe71 100644 --- a/KREngine/KREngine/Classes/KRNode.cpp +++ b/KREngine/KREngine/Classes/KRNode.cpp @@ -138,7 +138,11 @@ KRNode *KRNode::LoadXML(KRScene &scene, tinyxml2::XMLElement *e) { } else if(strcmp(szElementName, "spot_light") == 0) { new_node = new KRSpotLight(scene, szName); } else if(strcmp(szElementName, "mesh") == 0) { - new_node = new KRInstance(scene, szName, szName, e->Attribute("light_map")); + float lod_min_coverage = 0.0f; + if(e->QueryFloatAttribute("lod_min_coverage", &lod_min_coverage) != tinyxml2::XML_SUCCESS) { + lod_min_coverage = 0.0f; //1.0f / 1024.0f / 768.0f; // FINDME - HACK - Need to dynamically select the absolute minimum based on the render buffer size + } + new_node = new KRInstance(scene, szName, szName, e->Attribute("light_map"), lod_min_coverage); } else if(strcmp(szElementName, "sky_box") == 0) { new_node = new KRSkyBox(scene, szName); } diff --git a/KREngine/KREngine/Classes/KRScene.cpp b/KREngine/KREngine/Classes/KRScene.cpp index b050996..123e8cb 100644 --- a/KREngine/KREngine/Classes/KRScene.cpp +++ b/KREngine/KREngine/Classes/KRScene.cpp @@ -168,7 +168,14 @@ void KRScene::render(int childOrder[], KROctreeNode *pOctreeNode, std::setgetProjectionMatrix(); } - if(pOctreeNode->getBounds().visible(viewMatrix * projectionMatrix)) { // Only recurse deeper if within the view frustrum + KRMat4 matVP = viewMatrix * projectionMatrix; + + float min_coverage = 0.0f; // 1.0f / 1024.0f / 768.0f; // FINDME - HACK - Need to dynamically select the absolute minimum based on the render buffer size + + float lod_coverage = pOctreeNode->getBounds().coverage(matVP, pCamera->getViewportSize()); // This also checks the view frustrum culling + if(lod_coverage > min_coverage) { + +// if(pOctreeNode->getBounds().visible(viewMatrix * projectionMatrix)) { // Only recurse deeper if within the view frustrum // ----====---- Rendering and occlusion test pass ----====---- bool bVisible = false;