From 7b009c114a95a4c2d2002562d95a6c4e4bf27971 Mon Sep 17 00:00:00 2001 From: Peter Date: Mon, 9 Dec 2013 15:35:07 -0800 Subject: [PATCH 01/84] Updates for strip_unchanging_attributes to make it act on groups of x, y, z attires --HG-- branch : nfb --- KREngine/kraken/KRAnimation.cpp | 36 +++++++++++++++++++++++++----- KREngine/kraken/KRResource+fbx.cpp | 2 +- 2 files changed, 31 insertions(+), 7 deletions(-) diff --git a/KREngine/kraken/KRAnimation.cpp b/KREngine/kraken/KRAnimation.cpp index 7db4273..2624ce4 100644 --- a/KREngine/kraken/KRAnimation.cpp +++ b/KREngine/kraken/KRAnimation.cpp @@ -237,6 +237,8 @@ void KRAnimation::setLooping(bool looping) m_loop = looping; } + + KRAnimation *KRAnimation::split(const std::string &name, float start_time, float duration, bool strip_unchanging_attributes, bool clone_curves) { KRAnimation *new_animation = new KRAnimation(getContext(), name); @@ -255,15 +257,37 @@ KRAnimation *KRAnimation::split(const std::string &name, float start_time, float new_animation->m_layers[new_layer->getName()] = new_layer; for(std::vector::iterator attribute_itr = layer->getAttributes().begin(); attribute_itr != layer->getAttributes().end(); attribute_itr++) { KRAnimationAttribute *attribute = *attribute_itr; - KRAnimationCurve *curve = attribute->getCurve(); - if(curve != NULL) { + + // Updated Dec 9, 2013 by Peter to change the way that attributes are stripped. + // + // If we have been asked to strip_unchanging_attributes then we only want to strip those attributes that don't havechanges + // in any of their components (x, y, z). This means that we have to group the attributes before we check for valueChanges(). + // The attributes won't come through in order, but they do come through in group order (each group of 3 arrives as x, y, z) + // + // Since this method isn't designed to handle groups, this is a bit of a hack. We simply take whatever channel is coming + // through and then check the other associated curves. + // + int targetAttribute = attribute->getTargetAttribute(); + if (targetAttribute > 0) { // we have a valid target that fits within a group of 3 + targetAttribute--; // this is now group relative 0,1,2 is the first group .. 3,4,5 is the second group, etc. + + KRAnimationCurve *curve = attribute->getCurve(); // this is the curve we are currently handling + + int placeInGroup = targetAttribute % 3; // this will be 0, 1 or 2 + static long placeLookup[] = { 1, 2, -1, 1, -2, -1 }; + + KRAnimationAttribute *attribute2 = *(attribute_itr + placeLookup[placeInGroup*2]); + KRAnimationAttribute *attribute3 = *(attribute_itr + placeLookup[placeInGroup*2+1]); + KRAnimationCurve *curve2 = attribute2->getCurve(); + KRAnimationCurve *curve3 = attribute3->getCurve(); + bool include_attribute = true; if(strip_unchanging_attributes) { - if(!curve->valueChanges(start_time, duration)) { - include_attribute = false; + include_attribute = curve->valueChanges(start_time, duration) | + curve2->valueChanges(start_time, duration) | + curve3->valueChanges(start_time, duration); } - } - + if(include_attribute) { KRAnimationAttribute *new_attribute = new KRAnimationAttribute(getContext()); KRAnimationCurve *new_curve = curve; diff --git a/KREngine/kraken/KRResource+fbx.cpp b/KREngine/kraken/KRResource+fbx.cpp index 798794b..89d9c28 100644 --- a/KREngine/kraken/KRResource+fbx.cpp +++ b/KREngine/kraken/KRResource+fbx.cpp @@ -1636,7 +1636,7 @@ KRNode *LoadLocator(KRNode *parent_node, FbxScene* pFbxScene, FbxNode* pNode) { break; default: { - fprintf(stderr, "FBX property not imported due to unsupported data type: %s.%s\n", name.c_str(), property_name.c_str()); + // fprintf(stderr, "FBX property not imported due to unsupported data type: %s.%s\n", name.c_str(), property_name.c_str()); } break; } From bea3dc61e8b2a67ae2646509c5b206c2503aeeaa Mon Sep 17 00:00:00 2001 From: Kearwood Gilbert Date: Mon, 9 Dec 2013 22:29:13 -0800 Subject: [PATCH 02/84] Reverb and Ambient zone visualizations are now exposed through the debug interface. Colliders are now imported with a prefix of collider#_ instead of collider_#_ --HG-- branch : nfb --- KREngine/kraken/KRAmbientZone.cpp | 2 +- KREngine/kraken/KRCamera.cpp | 5 +++++ KREngine/kraken/KRRenderSettings.h | 2 ++ KREngine/kraken/KRResource+fbx.cpp | 12 +++++++++--- KREngine/kraken/KRReverbZone.cpp | 2 +- 5 files changed, 18 insertions(+), 5 deletions(-) diff --git a/KREngine/kraken/KRAmbientZone.cpp b/KREngine/kraken/KRAmbientZone.cpp index fac94fb..d44d075 100644 --- a/KREngine/kraken/KRAmbientZone.cpp +++ b/KREngine/kraken/KRAmbientZone.cpp @@ -90,7 +90,7 @@ void KRAmbientZone::render(KRCamera *pCamera, std::vector &point KRNode::render(pCamera, point_lights, directional_lights, spot_lights, viewport, renderPass); - bool bVisualize = false; + bool bVisualize = pCamera->settings.debug_display == KRRenderSettings::KRENGINE_DEBUG_DISPLAY_SIREN_AMBIENT_ZONES; if(renderPass == KRNode::RENDER_PASS_FORWARD_TRANSPARENT && bVisualize) { KRMat4 sphereModelMatrix = getModelMatrix(); diff --git a/KREngine/kraken/KRCamera.cpp b/KREngine/kraken/KRCamera.cpp index fb205a6..03c0845 100644 --- a/KREngine/kraken/KRCamera.cpp +++ b/KREngine/kraken/KRCamera.cpp @@ -1091,6 +1091,11 @@ std::string KRCamera::getDebugText() break; case KRRenderSettings::KRENGINE_DEBUG_DISPLAY_BONES: stream << "Bone Visualization"; + case KRRenderSettings::KRENGINE_DEBUG_DISPLAY_SIREN_REVERB_ZONES: + stream << "Siren - Reverb Zones"; + break; + case KRRenderSettings::KRENGINE_DEBUG_DISPLAY_SIREN_AMBIENT_ZONES: + stream << "Siren - Ambient Zones"; break; } return stream.str(); diff --git a/KREngine/kraken/KRRenderSettings.h b/KREngine/kraken/KRRenderSettings.h index 1cfff0e..a00e73b 100644 --- a/KREngine/kraken/KRRenderSettings.h +++ b/KREngine/kraken/KRRenderSettings.h @@ -96,6 +96,8 @@ public: KRENGINE_DEBUG_DISPLAY_OCTREE, KRENGINE_DEBUG_DISPLAY_COLLIDERS, KRENGINE_DEBUG_DISPLAY_BONES, + KRENGINE_DEBUG_DISPLAY_SIREN_REVERB_ZONES, + KRENGINE_DEBUG_DISPLAY_SIREN_AMBIENT_ZONES, KRENGINE_DEBUG_DISPLAY_NUMBER } debug_display; diff --git a/KREngine/kraken/KRResource+fbx.cpp b/KREngine/kraken/KRResource+fbx.cpp index 89d9c28..18958d8 100644 --- a/KREngine/kraken/KRResource+fbx.cpp +++ b/KREngine/kraken/KRResource+fbx.cpp @@ -1566,13 +1566,13 @@ KRNode *LoadMesh(KRNode *parent_node, FbxScene* pFbxScene, FbxGeometryConverter return new KRCollider(parent_node->getScene(), GetFbxObjectName(pNode), pSourceMesh->GetNode()->GetName(), KRAKEN_COLLIDER_PHYSICS, 0.0f); } else if(strncmp(node_name, "audio_collider_", strlen("audio_collider_")) == 0) { return new KRCollider(parent_node->getScene(), GetFbxObjectName(pNode), pSourceMesh->GetNode()->GetName(), KRAKEN_COLLIDER_AUDIO, 1.0f); - } else if(strncmp(node_name, "collider_", 9) == 0) { // 9 == strlen("collider_") + } else if(strncmp(node_name, "collider", 8) == 0) { // 8 == strlen("collider") // Colliders can have a prefix of collider_##_, where ## indicates the layer mask // Colliders with a prefix of only collider_ will have a default layer mask of KRAKEN_COLLIDER_PHYSICS | KRAKEN_COLLIDER_AUDIO // Scan through the characters of the name until we no longer see digit characters (or see a '\0' indicating the end of the string) unsigned int layer = 0; - const char *szNodeName = node_name + 9; // 9 == strlen("collider_") + const char *szNodeName = node_name + 8; // 8 == strlen("collider") const char *source_char = szNodeName; while(*source_char >= '0' && *source_char <= '9') { layer = layer * 10 + (*source_char++ - '0'); @@ -1582,7 +1582,13 @@ KRNode *LoadMesh(KRNode *parent_node, FbxScene* pFbxScene, FbxGeometryConverter // No layer mask number was found, use the default layer = KRAKEN_COLLIDER_PHYSICS | KRAKEN_COLLIDER_AUDIO; } - return new KRCollider(parent_node->getScene(), GetFbxObjectName(pNode), pSourceMesh->GetNode()->GetName(), layer, 1.0f); + if(*source_char == '_') { + // Pattern has matched + return new KRCollider(parent_node->getScene(), GetFbxObjectName(pNode), pSourceMesh->GetNode()->GetName(), layer, 1.0f); + } else { + // This is just a normal node, which happened to be prefixed with "collider" but didn't have a number and underscore + return new KRModel(parent_node->getScene(), GetFbxObjectName(pNode), pSourceMesh->GetNode()->GetName(), light_map, 0.0f, true, false); + } } else { return new KRModel(parent_node->getScene(), GetFbxObjectName(pNode), pSourceMesh->GetNode()->GetName(), light_map, 0.0f, true, false); } diff --git a/KREngine/kraken/KRReverbZone.cpp b/KREngine/kraken/KRReverbZone.cpp index 95a4e95..5622717 100644 --- a/KREngine/kraken/KRReverbZone.cpp +++ b/KREngine/kraken/KRReverbZone.cpp @@ -89,7 +89,7 @@ void KRReverbZone::render(KRCamera *pCamera, std::vector &point_ KRNode::render(pCamera, point_lights, directional_lights, spot_lights, viewport, renderPass); - bool bVisualize = false; + bool bVisualize = pCamera->settings.debug_display == KRRenderSettings::KRENGINE_DEBUG_DISPLAY_SIREN_REVERB_ZONES; if(renderPass == KRNode::RENDER_PASS_FORWARD_TRANSPARENT && bVisualize) { KRMat4 sphereModelMatrix = getModelMatrix(); From a0dbc8953cd6285a35136826e892c7a4b3b3486d Mon Sep 17 00:00:00 2001 From: "admin8onf@admin8onfs-pro.nfbonf.nfb.ca" Date: Tue, 10 Dec 2013 11:25:08 -0800 Subject: [PATCH 03/84] no message --HG-- branch : nfb --- .../xcschemes/xcschememanagement.plist | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/KREngine/Kraken.xcodeproj/xcuserdata/admin8onf.xcuserdatad/xcschemes/xcschememanagement.plist b/KREngine/Kraken.xcodeproj/xcuserdata/admin8onf.xcuserdatad/xcschemes/xcschememanagement.plist index 46ad7fe..287911f 100644 --- a/KREngine/Kraken.xcodeproj/xcuserdata/admin8onf.xcuserdatad/xcschemes/xcschememanagement.plist +++ b/KREngine/Kraken.xcodeproj/xcuserdata/admin8onf.xcuserdatad/xcschemes/xcschememanagement.plist @@ -9,28 +9,28 @@ isShown orderHint - 30 + 0 Kraken - osx.xcscheme isShown orderHint - 31 + 1 Kraken Standard Assets - OSX.xcscheme isShown orderHint - 33 + 3 Kraken Standard Assets - iOS.xcscheme isShown orderHint - 32 + 2 SuppressBuildableAutocreation From 1c31eded59054e2d03d7222dd80d15c09b6c8fd7 Mon Sep 17 00:00:00 2001 From: Peter Courtemanche Date: Tue, 10 Dec 2013 18:05:25 -0800 Subject: [PATCH 04/84] removed VBO swapping printf - speeds up the render by 50% --HG-- branch : nfb --- KREngine/kraken/KRMeshManager.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/KREngine/kraken/KRMeshManager.cpp b/KREngine/kraken/KRMeshManager.cpp index 65c7985..9247ae1 100644 --- a/KREngine/kraken/KRMeshManager.cpp +++ b/KREngine/kraken/KRMeshManager.cpp @@ -233,7 +233,7 @@ void KRMeshManager::bindVBO(KRDataBlock &data, KRDataBlock &index_data, int vert } m_vboMemUsed -= firstVBO.size; m_vbosPool.erase(first_itr); - fprintf(stderr, "VBO Swapping...\n"); + // fprintf(stderr, "VBO Swapping...\n"); } m_currentVBO.vao_handle = -1; From f1bc1a52a0dad1d13807ff84f9ddc61fe791a7b0 Mon Sep 17 00:00:00 2001 From: Peter Courtemanche Date: Sat, 14 Dec 2013 14:20:30 -0800 Subject: [PATCH 05/84] Added a quick warning message macro. KRSceneManager updates to del with NULL scenes. --HG-- branch : nfb --- KREngine/kraken/KRResource+fbx.cpp | 10 ++++++---- KREngine/kraken/KRSceneManager.cpp | 13 +++++++++---- 2 files changed, 15 insertions(+), 8 deletions(-) diff --git a/KREngine/kraken/KRResource+fbx.cpp b/KREngine/kraken/KRResource+fbx.cpp index 18958d8..d54adcf 100644 --- a/KREngine/kraken/KRResource+fbx.cpp +++ b/KREngine/kraken/KRResource+fbx.cpp @@ -35,6 +35,8 @@ #define IOS_REF (*(pSdkManager->GetIOSettings())) #endif +#define warning(e,s) if(!(e))printf("WARNING: %s\n",s) + void InitializeSdkObjects(FbxManager*& pSdkManager, FbxScene*& pScene); void DestroySdkObjects(FbxManager* pSdkManager); bool LoadScene(FbxManager* pSdkManager, FbxDocument* pScene, const char* pFilename); @@ -893,10 +895,10 @@ void LoadNode(FbxScene* pFbxScene, KRNode *parent_node, FbxGeometryConverter *pG FbxVector4 lZero(0.0, 0.0, 0.0); FbxVector4 lOne(1.0, 1.0, 1.0); - assert(geometric_rotation == lZero); - assert(geometric_translation == lZero); - assert(geometric_scaling == lOne); - assert(rotation_order == eEulerXYZ); + warning((geometric_rotation == lZero), "Geometric Rotation not supported .. 3DSMax file??"); + warning((geometric_translation == lZero), "Geometric Rotation not supported .. 3DSMax file??"); + warning((geometric_scaling == lOne), "Geometric Rotation not supported .. 3DSMax file??"); + warning((rotation_order == eEulerXYZ), "Geometric Rotation not supported .. 3DSMax file??"); // FINDME - node_key_frame_position contains the key frame (start location) for an animation node // node_has_n_points diff --git a/KREngine/kraken/KRSceneManager.cpp b/KREngine/kraken/KRSceneManager.cpp index ff0ded7..4489f05 100644 --- a/KREngine/kraken/KRSceneManager.cpp +++ b/KREngine/kraken/KRSceneManager.cpp @@ -65,15 +65,20 @@ KRScene *KRSceneManager::getScene(const std::string &name) { std::transform(lowerName.begin(), lowerName.end(), lowerName.begin(), ::tolower); - return m_scenes[lowerName]; + static unordered_map::iterator scene_itr = m_scenes.find(lowerName); + if(scene_itr != m_scenes.end()) { + return (*scene_itr).second; + } else { + return NULL; + } } KRScene *KRSceneManager::getFirstScene() { static unordered_map::iterator scene_itr = m_scenes.begin(); - if(scene_itr == m_scenes.end()) { - return NULL; - } else { + if(scene_itr != m_scenes.end()) { return (*scene_itr).second; + } else { + return NULL; } } From e39eebff26a38db41f5a83c75dbc59ed6b220eae Mon Sep 17 00:00:00 2001 From: Kearwood Gilbert Date: Sat, 14 Dec 2013 16:06:44 -0800 Subject: [PATCH 06/84] Implemented logging callbacks for client applications that would like to implement their own GUI --HG-- branch : nfb --- KREngine/kraken/KRAnimationAttribute.cpp | 2 +- KREngine/kraken/KRContext.cpp | 37 ++++++++++++++++++++++-- KREngine/kraken/KRContext.h | 16 ++++++++++ KREngine/kraken/KRDataBlock.cpp | 17 ++++++----- KREngine/kraken/KRHitInfo.cpp | 3 +- KREngine/kraken/KRMaterialManager.cpp | 2 +- KREngine/kraken/KRMesh.cpp | 4 +-- KREngine/kraken/KRMeshManager.cpp | 6 ++-- KREngine/kraken/KRShader.cpp | 24 ++++++++------- KREngine/kraken/KRShaderManager.cpp | 6 ++-- 10 files changed, 85 insertions(+), 32 deletions(-) diff --git a/KREngine/kraken/KRAnimationAttribute.cpp b/KREngine/kraken/KRAnimationAttribute.cpp index 9133f6a..f2ecebe 100644 --- a/KREngine/kraken/KRAnimationAttribute.cpp +++ b/KREngine/kraken/KRAnimationAttribute.cpp @@ -256,7 +256,7 @@ KRNode *KRAnimationAttribute::getTarget() m_target = getContext().getSceneManager()->getFirstScene()->getRootNode()->find(m_target_name); // FINDME, HACK! - This won't work with multiple scenes in a context; we should move the animations out of KRAnimationManager and attach them to the parent nodes of the animated KRNode's } if(m_target == NULL) { - fprintf(stderr, "Kraken - Animation attribute could not find object: %s\n", m_target_name.c_str()); + KRContext::Log(KRContext::LOG_LEVEL_ERROR, "Kraken - Animation attribute could not find object: %s", m_target_name.c_str()); } return m_target; } diff --git a/KREngine/kraken/KRContext.cpp b/KREngine/kraken/KRContext.cpp index a26e398..c11dbb1 100644 --- a/KREngine/kraken/KRContext.cpp +++ b/KREngine/kraken/KRContext.cpp @@ -25,6 +25,9 @@ const char *KRContext::extension_names[KRENGINE_NUM_EXTENSIONS] = { "GL_EXT_texture_storage" }; +KRContext::log_callback *KRContext::s_log_callback = NULL; +void *KRContext::s_log_callback_user_data = NULL; + KRContext::KRContext() { m_streamingEnabled = false; mach_timebase_info(&m_timebase_info); @@ -44,6 +47,8 @@ KRContext::KRContext() { m_pSoundManager = new KRAudioManager(*this); m_pUnknownManager = new KRUnknownManager(*this); m_streamingEnabled = true; + + } KRContext::~KRContext() { @@ -100,6 +105,32 @@ KRContext::~KRContext() { } } +void KRContext::SetLogCallback(log_callback *log_callback, void *user_data) +{ + s_log_callback = log_callback; + s_log_callback_user_data = user_data; +} + +void KRContext::Log(log_level level, const std::string &message_format, ...) +{ + va_list args; + va_start(args, message_format); + + if(s_log_callback) { + const int LOG_BUFFER_SIZE = 32768; + char log_buffer[LOG_BUFFER_SIZE]; + snprintf(log_buffer, LOG_BUFFER_SIZE, message_format.c_str(), args); + s_log_callback(s_log_callback_user_data, std::string(log_buffer), level); + } else { + FILE *out_file = level == LOG_LEVEL_INFORMATION ? stdout : stderr; + fprintf(out_file, "Kraken - INFO: "); + fprintf(out_file, message_format.c_str(), args); + fprintf(out_file, "\n"); + } + + va_end(args); +} + KRBundleManager *KRContext::getBundleManager() { return m_pBundleManager; } @@ -220,7 +251,7 @@ void KRContext::loadResource(std::string path) { if(data->load(path)) { loadResource(path, data); } else { - fprintf(stderr, "KRContext::loadResource - Failed to open file: %s\n", path.c_str()); + KRContext::Log(KRContext::LOG_LEVEL_ERROR, "KRContext::loadResource - Failed to open file: %s", path.c_str()); delete data; } } @@ -291,9 +322,9 @@ void KRContext::getMemoryStats(long &free_memory) vm_statistics_data_t vm_stat; int total_ram = 256 * 1024 * 1024; if(host_page_size(host_port, &pagesize) != KERN_SUCCESS) { - fprintf(stderr, "ERROR: Could not get VM page size.\n"); + KRContext::Log(KRContext::LOG_LEVEL_ERROR, "Could not get VM page size."); } else if(host_statistics(host_port, HOST_VM_INFO, (host_info_t)&vm_stat, &host_size) != KERN_SUCCESS) { - fprintf(stderr, "ERROR: Could not get VM stats.\n"); + KRContext::Log(KRContext::LOG_LEVEL_ERROR, "Could not get VM stats."); } else { total_ram = (vm_stat.wire_count + vm_stat.active_count + vm_stat.inactive_count + vm_stat.free_count) * pagesize; diff --git a/KREngine/kraken/KRContext.h b/KREngine/kraken/KRContext.h index c75f846..4f91d40 100644 --- a/KREngine/kraken/KRContext.h +++ b/KREngine/kraken/KRContext.h @@ -77,6 +77,18 @@ public: void getMemoryStats(long &free_memory); + typedef enum { + LOG_LEVEL_INFORMATION, + LOG_LEVEL_WARNING, + LOG_LEVEL_ERROR + } log_level; + + typedef void log_callback(void *userdata, const std::string &message, log_level level); + + static void SetLogCallback(log_callback *log_callback, void *user_data); + static void Log(log_level level, const std::string &message_format, ...); + + private: KRBundleManager *m_pBundleManager; KRSceneManager *m_pSceneManager; @@ -98,6 +110,10 @@ private: mach_timebase_info_data_t m_timebase_info; std::atomic m_streamingEnabled; + + + static log_callback *s_log_callback; + static void *s_log_callback_user_data; }; #endif diff --git a/KREngine/kraken/KRDataBlock.cpp b/KREngine/kraken/KRDataBlock.cpp index cfc2421..1a32081 100644 --- a/KREngine/kraken/KRDataBlock.cpp +++ b/KREngine/kraken/KRDataBlock.cpp @@ -32,6 +32,7 @@ #include "KRDataBlock.h" #include "KREngine-common.h" #include "KRResource.h" +#include "KRContext.h" #include @@ -311,28 +312,28 @@ void KRDataBlock::lock() int iError = errno; switch(iError) { case EACCES: - fprintf(stderr, "mmap failed with EACCES\n"); + KRContext::Log(KRContext::LOG_LEVEL_ERROR, "mmap failed with EACCES"); break; case EBADF: - fprintf(stderr, "mmap failed with EBADF\n"); + KRContext::Log(KRContext::LOG_LEVEL_ERROR, "mmap failed with EBADF"); break; case EMFILE: - fprintf(stderr, "mmap failed with EMFILE\n"); + KRContext::Log(KRContext::LOG_LEVEL_ERROR, "mmap failed with EMFILE"); break; case EINVAL: - fprintf(stderr, "mmap failed with EINVAL\n"); + KRContext::Log(KRContext::LOG_LEVEL_ERROR, "mmap failed with EINVAL"); break; case ENOMEM: - fprintf(stderr, "mmap failed with ENOMEM\n"); + KRContext::Log(KRContext::LOG_LEVEL_ERROR, "mmap failed with ENOMEM"); break; case ENXIO: - fprintf(stderr, "mmap failed with ENXIO\n"); + KRContext::Log(KRContext::LOG_LEVEL_ERROR, "mmap failed with ENXIO"); break; case EOVERFLOW: - fprintf(stderr, "mmap failed with EOVERFLOW\n"); + KRContext::Log(KRContext::LOG_LEVEL_ERROR, "mmap failed with EOVERFLOW"); break; default: - fprintf(stderr, "mmap failed with errno: %i\n", iError); + KRContext::Log(KRContext::LOG_LEVEL_ERROR, "mmap failed with errno: %i", iError); break; } assert(false); // mmap() failed. diff --git a/KREngine/kraken/KRHitInfo.cpp b/KREngine/kraken/KRHitInfo.cpp index 3e0d048..724dfb0 100644 --- a/KREngine/kraken/KRHitInfo.cpp +++ b/KREngine/kraken/KRHitInfo.cpp @@ -30,6 +30,7 @@ // #include "KRHitInfo.h" +#include "KRContext.h" KRHitInfo::KRHitInfo() { @@ -42,7 +43,7 @@ KRHitInfo::KRHitInfo(const KRVector3 &position, const KRVector3 &normal, KRNode { m_position = position; if(m_position == KRVector3::Zero()) { - fprintf(stderr, "Zero position hitinfo\n"); + KRContext::Log(KRContext::LOG_LEVEL_ERROR, "Zero position hitinfo"); } m_normal = normal; m_node = node; diff --git a/KREngine/kraken/KRMaterialManager.cpp b/KREngine/kraken/KRMaterialManager.cpp index 8b7deaa..cab3a97 100644 --- a/KREngine/kraken/KRMaterialManager.cpp +++ b/KREngine/kraken/KRMaterialManager.cpp @@ -80,7 +80,7 @@ KRMaterial *KRMaterialManager::getMaterial(const std::string &name) { unordered_map::iterator itr = m_materials.find(lowerName); if(itr == m_materials.end()) { - fprintf(stderr, "Material not found: %s\n", name.c_str()); + KRContext::Log(KRContext::LOG_LEVEL_WARNING, "Material not found: %s", name.c_str()); // Not found return NULL; } else { diff --git a/KREngine/kraken/KRMesh.cpp b/KREngine/kraken/KRMesh.cpp index 2cf0c2f..f379f27 100644 --- a/KREngine/kraken/KRMesh.cpp +++ b/KREngine/kraken/KRMesh.cpp @@ -175,7 +175,7 @@ void KRMesh::render(const std::string &object_name, KRCamera *pCamera, std::vect if(pMaterial) { m_uniqueMaterials.insert(pMaterial); } else { - fprintf(stderr, "Missing material: %s\n", szMaterialName); + KRContext::Log(KRContext::LOG_LEVEL_WARNING, "Missing material: %s", szMaterialName); } } @@ -1301,7 +1301,7 @@ void KRMesh::convertToIndexed() delete szKey; - fprintf(stderr, "Convert to indexed, before: %i after: %i \(%.2f%% saving)\n", getHeader()->vertex_count, mi.vertices.size(), ((float)getHeader()->vertex_count - (float)mi.vertices.size()) / (float)getHeader()->vertex_count * 100.0f); + KRContext::Log(KRContext::LOG_LEVEL_INFORMATION, "Convert to indexed, before: %i after: %i \(%.2f%% saving)", getHeader()->vertex_count, mi.vertices.size(), ((float)getHeader()->vertex_count - (float)mi.vertices.size()) / (float)getHeader()->vertex_count * 100.0f); mi.format = KRENGINE_MODEL_FORMAT_INDEXED_TRIANGLES; diff --git a/KREngine/kraken/KRMeshManager.cpp b/KREngine/kraken/KRMeshManager.cpp index 9247ae1..68b5c53 100644 --- a/KREngine/kraken/KRMeshManager.cpp +++ b/KREngine/kraken/KRMeshManager.cpp @@ -129,7 +129,7 @@ std::vector KRMeshManager::getModel(const char *szName) { std::sort(matching_models.begin(), matching_models.end(), KRMesh::lod_sort_predicate); if(matching_models.size() == 0) { - fprintf(stderr, "ERROR: Model not found: %s\n", lowerName.c_str()); + KRContext::Log(KRContext::LOG_LEVEL_INFORMATION, "Model not found: %s", lowerName.c_str()); } return matching_models; @@ -159,7 +159,7 @@ void KRMeshManager::releaseVBO(KRDataBlock &data) vbo_info_type vbo_to_release; if(m_vbosActive.find(&data) != m_vbosActive.end()) { - fprintf(stderr, "glFinish called due to releasing a VBO that is active in the current frame.\n"); + KRContext::Log(KRContext::LOG_LEVEL_WARNING, "glFinish called due to releasing a VBO that is active in the current frame."); GLDEBUG(glFinish()); // The VBO is active @@ -219,7 +219,7 @@ void KRMeshManager::bindVBO(KRDataBlock &data, KRDataBlock &index_data, int vert while(m_vbosPool.size() + m_vbosActive.size() + 1 >= KRContext::KRENGINE_MAX_VBO_HANDLES || m_vboMemUsed + data.getSize() + index_data.getSize() >= KRContext::KRENGINE_MAX_VBO_MEM) { if(m_vbosPool.empty()) { - fprintf(stderr, "flushBuffers due to VBO exhaustion...\n"); + KRContext::Log(KRContext::LOG_LEVEL_WARNING, "flushBuffers due to VBO exhaustion..."); m_pContext->rotateBuffers(false); } unordered_map::iterator first_itr = m_vbosPool.begin(); diff --git a/KREngine/kraken/KRShader.cpp b/KREngine/kraken/KRShader.cpp index bdb2cef..b1423e0 100644 --- a/KREngine/kraken/KRShader.cpp +++ b/KREngine/kraken/KRShader.cpp @@ -129,10 +129,11 @@ KRShader::KRShader(KRContext &context, char *szKey, std::string options, std::st GLint logLength; GLDEBUG(glGetShaderiv(vertexShader, GL_INFO_LOG_LENGTH, &logLength)); if (logLength > 0) { - GLchar *log = (GLchar *)malloc(logLength); + GLchar *log = (GLchar *)malloc(logLength + 1); assert(log != NULL); GLDEBUG(glGetShaderInfoLog(vertexShader, logLength, &logLength, log)); - fprintf(stderr, "KREngine - Failed to compile vertex shader: %s\nShader compile log:\n%s", szKey, log); + log[logLength] = '\0'; + KRContext::Log(KRContext::LOG_LEVEL_ERROR, "KREngine - Failed to compile vertex shader: %s\nShader compile log:\n%s", szKey, log); free(log); } @@ -145,10 +146,11 @@ KRShader::KRShader(KRContext &context, char *szKey, std::string options, std::st // Report any compile issues to stderr GLDEBUG(glGetShaderiv(fragShader, GL_INFO_LOG_LENGTH, &logLength)); if (logLength > 0) { - GLchar *log = (GLchar *)malloc(logLength); + GLchar *log = (GLchar *)malloc(logLength + 1); assert(log != NULL); GLDEBUG(glGetShaderInfoLog(fragShader, logLength, &logLength, log)); - fprintf(stderr, "KREngine - Failed to compile fragment shader: %s\nShader compile log:\n%s", szKey, log); + log[logLength] = '\0'; + KRContext::Log(KRContext::LOG_LEVEL_ERROR, "KREngine - Failed to compile fragment shader: %s\nShader compile log:\n%s", szKey, log); free(log); } @@ -176,15 +178,16 @@ KRShader::KRShader(KRContext &context, char *szKey, std::string options, std::st if(link_success != GL_TRUE) { // Report any linking issues to stderr - fprintf(stderr, "KREngine - Failed to link shader program: %s\n", szKey); + KRContext::Log(KRContext::LOG_LEVEL_ERROR, "KREngine - Failed to link shader program: %s", szKey); GLDEBUG(glGetProgramiv(m_iProgram, GL_INFO_LOG_LENGTH, &logLength)); if (logLength > 0) { - GLchar *log = (GLchar *)malloc(logLength); + GLchar *log = (GLchar *)malloc(logLength + 1); assert(log != NULL); GLDEBUG(glGetProgramInfoLog(m_iProgram, logLength, &logLength, log)); - fprintf(stderr, "Program link log:\n%s", log); + log[logLength] = '\0'; + KRContext::Log(KRContext::LOG_LEVEL_ERROR, "Program link log:\n%s", log); free(log); } GLDEBUG(glDeleteProgram(m_iProgram)); @@ -557,14 +560,15 @@ bool KRShader::bind(KRCamera &camera, const KRViewport &viewport, const KRMat4 & GLDEBUG(glValidateProgram(m_iProgram)); GLDEBUG(glGetProgramiv(m_iProgram, GL_VALIDATE_STATUS, &validate_status)); if(validate_status != GL_TRUE) { - fprintf(stderr, "KREngine - Failed to validate shader program: %s\n", m_szKey); + KRContext::Log(KRContext::LOG_LEVEL_ERROR, "KREngine - Failed to validate shader program: %s", m_szKey); GLDEBUG(glGetProgramiv(m_iProgram, GL_INFO_LOG_LENGTH, &logLength)); if (logLength > 0) { - GLchar *log = (GLchar *)malloc(logLength); + GLchar *log = (GLchar *)malloc(logLength + 1); assert(log != NULL); GLDEBUG(glGetProgramInfoLog(m_iProgram, logLength, &logLength, log)); - fprintf(stderr, "Program validate log:\n%s", log); + log[logLength] = '\0'; + KRContext::Log(KRContext::LOG_LEVEL_ERROR, "Program validate log:\n%s", log); free(log); } diff --git a/KREngine/kraken/KRShaderManager.cpp b/KREngine/kraken/KRShaderManager.cpp index 0cbe5d2..664cb1e 100644 --- a/KREngine/kraken/KRShaderManager.cpp +++ b/KREngine/kraken/KRShaderManager.cpp @@ -126,7 +126,7 @@ KRShader *KRShaderManager::getShader(const std::string &shader_name, KRCamera *p std::map > , KRShader *>::iterator itr = m_shaders.begin(); delete (*itr).second; m_shaders.erase(itr); - fprintf(stderr, "Swapping shaders...\n"); + KRContext::Log(KRContext::LOG_LEVEL_INFORMATION, "Swapping shaders...\n"); } @@ -224,10 +224,10 @@ KRShader *KRShaderManager::getShader(const std::string &shader_name, KRCamera *p std::string fragShaderSource = m_fragShaderSource[platform_shader_name]; if(vertShaderSource.length() == 0) { - fprintf(stderr, "ERROR: Vertex Shader Missing: %s\n", platform_shader_name.c_str()); + KRContext::Log(KRContext::LOG_LEVEL_ERROR, "Vertex Shader Missing: %s", platform_shader_name.c_str()); } if(fragShaderSource.length() == 0) { - fprintf(stderr, "ERROR: Fragment Shader Missing: %s\n", platform_shader_name.c_str()); + KRContext::Log(KRContext::LOG_LEVEL_ERROR, "Fragment Shader Missing: %s", platform_shader_name.c_str()); } char szKey[256]; From c602b12f04ba6523b50dc4f547cc8cfdc08c8208 Mon Sep 17 00:00:00 2001 From: Kearwood Gilbert Date: Sat, 14 Dec 2013 16:37:14 -0800 Subject: [PATCH 07/84] FBX Importer messages are now sent through the logging callbacks --HG-- branch : nfb --- KREngine/kraken/KRResource+fbx.cpp | 82 +++++++++++++++--------------- 1 file changed, 40 insertions(+), 42 deletions(-) diff --git a/KREngine/kraken/KRResource+fbx.cpp b/KREngine/kraken/KRResource+fbx.cpp index d54adcf..697da3e 100644 --- a/KREngine/kraken/KRResource+fbx.cpp +++ b/KREngine/kraken/KRResource+fbx.cpp @@ -35,7 +35,7 @@ #define IOS_REF (*(pSdkManager->GetIOSettings())) #endif -#define warning(e,s) if(!(e))printf("WARNING: %s\n",s) +#define warning(e,s) if(!(e))KRContext::Log(KRContext::LOG_LEVEL_WARNING, "%s\n",s) void InitializeSdkObjects(FbxManager*& pSdkManager, FbxScene*& pScene); void DestroySdkObjects(FbxManager* pSdkManager); @@ -126,11 +126,11 @@ void KRResource::LoadFbx(KRContext &context, const std::string& path) */ // ----====---- Import Animation Layers ----====---- - printf("\nLoading animations...\n"); + KRContext::Log(KRContext::LOG_LEVEL_INFORMATION, "\nLoading animations..."); int animation_count = pFbxScene->GetSrcObjectCount(); for(int i = 0; i < animation_count; i++) { FbxAnimStack *animation = pFbxScene->GetSrcObject(i); - printf(" Animation %i of %i: %s\n", i+1, animation_count, animation->GetName()); + KRContext::Log(KRContext::LOG_LEVEL_INFORMATION, " Animation %i of %i: %s", i+1, animation_count, animation->GetName()); KRAnimation *new_animation = LoadAnimation(context, animation); if(new_animation) { context.getAnimationManager()->addAnimation(new_animation); @@ -138,51 +138,51 @@ void KRResource::LoadFbx(KRContext &context, const std::string& path) } // ----====---- Import Animation Curves ----====---- - printf("\nLoading animation curves...\n"); + KRContext::Log(KRContext::LOG_LEVEL_INFORMATION, "\nLoading animation curves..."); int curve_count = pFbxScene->GetSrcObjectCount(); for(int i=0; i < curve_count; i++) { FbxAnimCurve *curve = pFbxScene->GetSrcObject(i); - printf(" Animation Curve %i of %i: %s\n", i+1, curve_count, curve->GetName()); + KRContext::Log(KRContext::LOG_LEVEL_INFORMATION, " Animation Curve %i of %i: %s", i+1, curve_count, curve->GetName()); KRAnimationCurve *new_curve = LoadAnimationCurve(context, curve); if(new_curve) { - printf("Adding a curve\n"); + KRContext::Log(KRContext::LOG_LEVEL_INFORMATION, "Adding a curve"); context.getAnimationCurveManager()->addAnimationCurve(new_curve); } } // ----====---- Import Materials ----====---- int material_count = pFbxScene->GetSrcObjectCount(); - printf("\nLoading materials...\n"); + KRContext::Log(KRContext::LOG_LEVEL_INFORMATION, "\nLoading materials..."); for(int i=0; i < material_count; i++) { FbxSurfaceMaterial *material = pFbxScene->GetSrcObject(i); - printf(" Material %i of %i: %s\n", i+1, material_count, material->GetName()); + KRContext::Log(KRContext::LOG_LEVEL_INFORMATION, " Material %i of %i: %s", i+1, material_count, material->GetName()); LoadMaterial(context, material); } // ----====---- Import Meshes ----====---- int mesh_count = pFbxScene->GetSrcObjectCount(); - printf("\nLoading meshes...\n"); + KRContext::Log(KRContext::LOG_LEVEL_INFORMATION, "Loading meshes..."); for(int i=0; i < mesh_count; i++) { FbxMesh *mesh = pFbxScene->GetSrcObject(i); - printf(" Mesh %i of %i: %s\n", i+1, mesh_count, mesh->GetNode()->GetName()); + KRContext::Log(KRContext::LOG_LEVEL_INFORMATION, " Mesh %i of %i: %s", i+1, mesh_count, mesh->GetNode()->GetName()); LoadMesh(context, pFbxScene, pGeometryConverter, mesh); } // ----====---- Import Textures ----====---- int texture_count = pFbxScene->GetSrcObjectCount(); - printf("\nLoading textures...\n"); + KRContext::Log(KRContext::LOG_LEVEL_INFORMATION, "Loading textures..."); for(int i=0; i < texture_count; i++) { FbxFileTexture *texture = pFbxScene->GetSrcObject(i); const char *file_name = texture->GetFileName(); - printf(" Texture %i of %i: %s\n", i+1, texture_count, (KRResource::GetFileBase(file_name) + "." + KRResource::GetFileExtension(file_name)).c_str()); + KRContext::Log(KRContext::LOG_LEVEL_INFORMATION, " Texture %i of %i: %s", i+1, texture_count, (KRResource::GetFileBase(file_name) + "." + KRResource::GetFileExtension(file_name)).c_str()); context.loadResource(file_name); } // ----====---- Import Scene Graph Nodes ----====---- - printf("\nLoading scene graph...\n"); + KRContext::Log(KRContext::LOG_LEVEL_INFORMATION, "Loading scene graph..."); if(pNode) { for(int i = 0; i < pNode->GetChildCount(); i++) @@ -205,7 +205,7 @@ void InitializeSdkObjects(FbxManager*& pSdkManager, FbxScene*& pScene) if (!pSdkManager) { - printf("Unable to create the FBX SDK manager\n"); + KRContext::Log(KRContext::LOG_LEVEL_ERROR, "Unable to create the FBX SDK manager"); exit(0); } @@ -263,40 +263,38 @@ bool LoadScene(FbxManager* pSdkManager, FbxDocument* pScene, const char* pFilena { FbxStatus &status = lImporter->GetStatus(); - printf("Call to KFbxImporter::Initialize() failed.\n"); - printf("Error returned: %s\n\n", status.GetErrorString()); + KRContext::Log(KRContext::LOG_LEVEL_ERROR, "Call to KFbxImporter::Initialize() failed.\nError returned: %s", status.GetErrorString()); if (status.GetCode() == FbxStatus::EStatusCode::eInvalidFileVersion) { - printf("FBX version number for this FBX SDK is %d.%d.%d\n", lSDKMajor, lSDKMinor, lSDKRevision); - printf("FBX version number for file %s is %d.%d.%d\n\n", pFilename, lFileMajor, lFileMinor, lFileRevision); + KRContext::Log(KRContext::LOG_LEVEL_INFORMATION, "FBX version number for this FBX SDK is %d.%d.%d", lSDKMajor, lSDKMinor, lSDKRevision); + KRContext::Log(KRContext::LOG_LEVEL_INFORMATION, "FBX version number for file %s is %d.%d.%d", pFilename, lFileMajor, lFileMinor, lFileRevision); } return false; } - printf("FBX version number for this FBX SDK is %d.%d.%d\n", lSDKMajor, lSDKMinor, lSDKRevision); + KRContext::Log(KRContext::LOG_LEVEL_INFORMATION, "FBX version number for this FBX SDK is %d.%d.%d", lSDKMajor, lSDKMinor, lSDKRevision); if(!lImporter->IsFBX()) { - printf("ERROR Unrecognized FBX File\n"); + KRContext::Log(KRContext::LOG_LEVEL_ERROR, "ERROR Unrecognized FBX File"); return false; } - printf("FBX version number for file %s is %d.%d.%d\n\n", pFilename, lFileMajor, lFileMinor, lFileRevision); + KRContext::Log(KRContext::LOG_LEVEL_INFORMATION, "FBX version number for file %s is %d.%d.%d\n", pFilename, lFileMajor, lFileMinor, lFileRevision); // From this point, it is possible to access animation stack information without // the expense of loading the entire file. - printf("Animation Stack Information\n"); + KRContext::Log(KRContext::LOG_LEVEL_INFORMATION, "Animation Stack Information"); lAnimStackCount = lImporter->GetAnimStackCount(); - printf(" Number of Animation Stacks: %d\n", lAnimStackCount); - printf(" Current Animation Stack: \"%s\"\n", lImporter->GetActiveAnimStackName().Buffer()); - printf("\n"); + KRContext::Log(KRContext::LOG_LEVEL_INFORMATION, " Number of Animation Stacks: %d", lAnimStackCount); + KRContext::Log(KRContext::LOG_LEVEL_INFORMATION, " Current Animation Stack: \"%s\"", lImporter->GetActiveAnimStackName().Buffer()); // Set the import states. By default, the import states are always set to @@ -369,7 +367,7 @@ bool LoadScene(FbxManager* pSdkManager, FbxDocument* pScene, const char* pFilena KRAnimation *LoadAnimation(KRContext &context, FbxAnimStack* pAnimStack) { - printf("Loading animation: \"%s\"\n", pAnimStack->GetName()); + KRContext::Log(KRContext::LOG_LEVEL_INFORMATION, "Loading animation: \"%s\"", pAnimStack->GetName()); KRAnimation *new_animation = new KRAnimation(context, pAnimStack->GetName()); int cLayers = pAnimStack->GetMemberCount(); @@ -383,10 +381,10 @@ KRAnimation *LoadAnimation(KRContext &context, FbxAnimStack* pAnimStack) KRAnimationCurve *LoadAnimationCurve(KRContext &context, FbxAnimCurve* pAnimCurve) { std::string name = GetFbxObjectName(pAnimCurve); - printf("Loading animation curve: \"%s\"\n", name.c_str()); + KRContext::Log(KRContext::LOG_LEVEL_INFORMATION, "Loading animation curve: \"%s\"", name.c_str()); FbxTimeSpan time_span; if(!pAnimCurve->GetTimeInterval(time_span)) { - printf(" ERROR: Failed to get time interval.\n"); + KRContext::Log(KRContext::LOG_LEVEL_ERROR, "Failed to get time interval."); return NULL; } @@ -394,7 +392,7 @@ KRAnimationCurve *LoadAnimationCurve(KRContext &context, FbxAnimCurve* pAnimCurv int frame_start = time_span.GetStart().GetSecondDouble() * dest_frame_rate; int frame_count = (time_span.GetStop().GetSecondDouble() * dest_frame_rate) - frame_start; - printf(" animation start %d and frame count %d\n", frame_start, frame_count); + KRContext::Log(KRContext::LOG_LEVEL_INFORMATION, " animation start %d and frame count %d", frame_start, frame_count); KRAnimationCurve *new_curve = new KRAnimationCurve(context, name); new_curve->setFrameRate(dest_frame_rate); @@ -409,7 +407,7 @@ KRAnimationCurve *LoadAnimationCurve(KRContext &context, FbxAnimCurve* pAnimCurv frame_time.SetSecondDouble(frame_seconds); float frame_value = pAnimCurve->Evaluate(frame_time, &last_frame); // printf(" Frame %i / %i: %.6f\n", frame_number, frame_count, frame_value); - if (0 == frame_number) printf("Value at starting key frame = %3.3f\n", frame_value); + if (0 == frame_number) KRContext::Log(KRContext::LOG_LEVEL_INFORMATION, "Value at starting key frame = %3.3f", frame_value); new_curve->setValue(frame_number+frame_start, frame_value); // BUG FIX Dec 2, 2013 .. changed frame_number to frame_number+frame_start // setValue(frame_number, frame_value) clamps the frame_number range between frame_start : frame_start+frame_count @@ -904,7 +902,7 @@ void LoadNode(FbxScene* pFbxScene, KRNode *parent_node, FbxGeometryConverter *pG // node_has_n_points // node_key_frame_position if (3 == node_has_n_points) - printf("key frame at %2.3f, %2.3f, %2.3f\n", + KRContext::Log(KRContext::LOG_LEVEL_INFORMATION, "key frame at %2.3f, %2.3f, %2.3f", node_key_frame_position.x, node_key_frame_position.y, node_key_frame_position.z); @@ -1188,7 +1186,7 @@ void LoadMaterial(KRContext &context, FbxSurfaceMaterial *pMaterial) { new_material->setTransparency(1.0f - (lKFbxDouble3.Get()[0] + lKFbxDouble3.Get()[1] + lKFbxDouble3.Get()[2]) / 3.0f); } else { - printf("Error! Unable to convert material: %s", pMaterial->GetName()); + KRContext::Log(KRContext::LOG_LEVEL_WARNING, "Unable to convert material: %s", pMaterial->GetName()); } @@ -1198,12 +1196,12 @@ void LoadMaterial(KRContext &context, FbxSurfaceMaterial *pMaterial) { // Diffuse Map Texture pProperty = pMaterial->FindProperty(FbxSurfaceMaterial::sDiffuse); if(pProperty.GetSrcObjectCount(FbxLayeredTexture::ClassId) > 0) { - printf("Warning! Layered textures not supported.\n"); + KRContext::Log(KRContext::LOG_LEVEL_WARNING, "Layered textures not supported."); } int texture_count = pProperty.GetSrcObjectCount(FbxTexture::ClassId); if(texture_count > 1) { - printf("Error! Multiple diffuse textures not supported.\n"); + KRContext::Log(KRContext::LOG_LEVEL_WARNING, "Multiple diffuse textures not supported."); } else if(texture_count == 1) { FbxTexture* pTexture = FbxCast (pProperty.GetSrcObject(FbxTexture::ClassId,0)); assert(!pTexture->GetSwapUV()); @@ -1227,11 +1225,11 @@ void LoadMaterial(KRContext &context, FbxSurfaceMaterial *pMaterial) { // Specular Map Texture pProperty = pMaterial->FindProperty(FbxSurfaceMaterial::sSpecular); if(pProperty.GetSrcObjectCount(FbxLayeredTexture::ClassId) > 0) { - printf("Warning! Layered textures not supported.\n"); + KRContext::Log(KRContext::LOG_LEVEL_INFORMATION, "Layered textures not supported."); } texture_count = pProperty.GetSrcObjectCount(FbxTexture::ClassId); if(texture_count > 1) { - printf("Error! Multiple specular textures not supported.\n"); + KRContext::Log(KRContext::LOG_LEVEL_INFORMATION, "Multiple specular textures not supported."); } else if(texture_count == 1) { FbxTexture* pTexture = FbxCast (pProperty.GetSrcObject(FbxTexture::ClassId,0)); FbxFileTexture *pFileTexture = FbxCast(pTexture); @@ -1243,13 +1241,13 @@ void LoadMaterial(KRContext &context, FbxSurfaceMaterial *pMaterial) { // Normal Map Texture pProperty = pMaterial->FindProperty(FbxSurfaceMaterial::sNormalMap); if(pProperty.GetSrcObjectCount(FbxLayeredTexture::ClassId) > 0) { - printf("Warning! Layered textures not supported.\n"); + KRContext::Log(KRContext::LOG_LEVEL_INFORMATION, "Layered textures not supported."); } texture_count = pProperty.GetSrcObjectCount(); if(texture_count > 1) { - printf("Error! Multiple normal map textures not supported.\n"); + KRContext::Log(KRContext::LOG_LEVEL_INFORMATION, "Multiple normal map textures not supported."); } else if(texture_count == 1) { FbxTexture* pTexture = pProperty.GetSrcObject(0); FbxFileTexture *pFileTexture = FbxCast(pTexture); @@ -1300,11 +1298,11 @@ void LoadMesh(KRContext &context, FbxScene* pFbxScene, FbxGeometryConverter *pGe for(int skin_index=0; skin_indexGetDeformer(skin_index, FbxDeformer::eSkin); int cluster_count = skin->GetClusterCount(); - printf(" Found skin with %i clusters.\n", cluster_count); + KRContext::Log(KRContext::LOG_LEVEL_INFORMATION, " Found skin with %i clusters.\n", cluster_count); for(int cluster_index=0; cluster_index < cluster_count; cluster_index++) { FbxCluster *cluster = skin->GetCluster(cluster_index); if(cluster->GetLinkMode() != FbxCluster::eNormalize) { - printf(" Warning! link mode not supported.\n"); + KRContext::Log(KRContext::LOG_LEVEL_WARNING, " Warning! link mode not supported."); } std::string bone_name = GetFbxObjectName(cluster->GetLink()); mi.bone_names.push_back(bone_name); @@ -1363,7 +1361,7 @@ void LoadMesh(KRContext &context, FbxScene* pFbxScene, FbxGeometryConverter *pGe } if(too_many_bone_weights) { - printf(" WARNING! - Clipped bone weights to limit of %i per vertex (selecting largest weights and re-normalizing).\n", KRENGINE_MAX_BONE_WEIGHTS_PER_VERTEX); + KRContext::Log(KRContext::LOG_LEVEL_WARNING, "Clipped bone weights to limit of %i per vertex (selecting largest weights and re-normalizing).", KRENGINE_MAX_BONE_WEIGHTS_PER_VERTEX); } // Normalize bone weights if(mi.bone_names.size() > 0) { @@ -1420,7 +1418,7 @@ void LoadMesh(KRContext &context, FbxScene* pFbxScene, FbxGeometryConverter *pGe int lPolygonSize = pMesh->GetPolygonSize(iPolygon); if(lPolygonSize != 3) { source_vertex_id += lPolygonSize; - printf(" Warning - Poly with %i vertices found. Expecting only triangles.", lPolygonSize); + KRContext::Log(KRContext::LOG_LEVEL_INFORMATION, "Poly with %i vertices found. Expecting only triangles.", lPolygonSize); } else { // ----====---- Read SubMesh / Material Mapping ----====---- int iNewMaterial = -1; From 99b97169922bdc3a2343bc272b1f53dffebfc480 Mon Sep 17 00:00:00 2001 From: Kearwood Gilbert Date: Sat, 14 Dec 2013 18:02:01 -0800 Subject: [PATCH 08/84] Implemented KRModel::setLightMap Implemented KRModel::getLightMap Corrected logging bugs and crashes during fbx import pipeline --HG-- branch : nfb --- KREngine/kraken/KRModel.cpp | 11 +++++++++++ KREngine/kraken/KRModel.h | 3 +++ KREngine/kraken/KRResource+fbx.cpp | 8 ++++---- 3 files changed, 18 insertions(+), 4 deletions(-) diff --git a/KREngine/kraken/KRModel.cpp b/KREngine/kraken/KRModel.cpp index a7bd54e..f337aba 100644 --- a/KREngine/kraken/KRModel.cpp +++ b/KREngine/kraken/KRModel.cpp @@ -105,6 +105,17 @@ float KRModel::getRimPower() return m_rim_power; } +void KRModel::setLightMap(const std::string &name) +{ + m_lightMap = name; + m_pLightMap = NULL; +} + +std::string KRModel::getLightMap() +{ + return m_lightMap; +} + void KRModel::loadModel() { if(m_models.size() == 0) { std::vector 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 diff --git a/KREngine/kraken/KRModel.h b/KREngine/kraken/KRModel.h index 25f46e0..3f8df27 100644 --- a/KREngine/kraken/KRModel.h +++ b/KREngine/kraken/KRModel.h @@ -66,6 +66,9 @@ public: KRVector3 getRimColor(); float getRimPower(); + void setLightMap(const std::string &name); + std::string getLightMap(); + private: std::vector m_models; unordered_map > m_bones; // Outer std::map connects model to set of bones diff --git a/KREngine/kraken/KRResource+fbx.cpp b/KREngine/kraken/KRResource+fbx.cpp index 697da3e..d9589b0 100644 --- a/KREngine/kraken/KRResource+fbx.cpp +++ b/KREngine/kraken/KRResource+fbx.cpp @@ -130,7 +130,7 @@ void KRResource::LoadFbx(KRContext &context, const std::string& path) int animation_count = pFbxScene->GetSrcObjectCount(); for(int i = 0; i < animation_count; i++) { FbxAnimStack *animation = pFbxScene->GetSrcObject(i); - KRContext::Log(KRContext::LOG_LEVEL_INFORMATION, " Animation %i of %i: %s", i+1, animation_count, animation->GetName()); + KRContext::Log(KRContext::LOG_LEVEL_INFORMATION, " Animation %i of %i: %s", i+1, animation_count, animation->GetName().c_str()); KRAnimation *new_animation = LoadAnimation(context, animation); if(new_animation) { context.getAnimationManager()->addAnimation(new_animation); @@ -142,7 +142,7 @@ void KRResource::LoadFbx(KRContext &context, const std::string& path) int curve_count = pFbxScene->GetSrcObjectCount(); for(int i=0; i < curve_count; i++) { FbxAnimCurve *curve = pFbxScene->GetSrcObject(i); - KRContext::Log(KRContext::LOG_LEVEL_INFORMATION, " Animation Curve %i of %i: %s", i+1, curve_count, curve->GetName()); + KRContext::Log(KRContext::LOG_LEVEL_INFORMATION, " Animation Curve %i of %i: %s", i+1, curve_count, curve->GetName().c_str()); KRAnimationCurve *new_curve = LoadAnimationCurve(context, curve); if(new_curve) { @@ -156,7 +156,7 @@ void KRResource::LoadFbx(KRContext &context, const std::string& path) KRContext::Log(KRContext::LOG_LEVEL_INFORMATION, "\nLoading materials..."); for(int i=0; i < material_count; i++) { FbxSurfaceMaterial *material = pFbxScene->GetSrcObject(i); - KRContext::Log(KRContext::LOG_LEVEL_INFORMATION, " Material %i of %i: %s", i+1, material_count, material->GetName()); + KRContext::Log(KRContext::LOG_LEVEL_INFORMATION, " Material %i of %i: %s", i+1, material_count, material->GetName().c_str()); LoadMaterial(context, material); } @@ -166,7 +166,7 @@ void KRResource::LoadFbx(KRContext &context, const std::string& path) for(int i=0; i < mesh_count; i++) { FbxMesh *mesh = pFbxScene->GetSrcObject(i); - KRContext::Log(KRContext::LOG_LEVEL_INFORMATION, " Mesh %i of %i: %s", i+1, mesh_count, mesh->GetNode()->GetName()); + KRContext::Log(KRContext::LOG_LEVEL_INFORMATION, " Mesh %i of %i: %s", i+1, mesh_count, mesh->GetNode()->GetName().c_str()); LoadMesh(context, pFbxScene, pGeometryConverter, mesh); } From 41f6fc12a5f46e403e824d6b3909e09b4324a91b Mon Sep 17 00:00:00 2001 From: Kearwood Gilbert Date: Sat, 14 Dec 2013 18:03:51 -0800 Subject: [PATCH 09/84] Corrected compile error in fbx pipeline --HG-- branch : nfb --- KREngine/kraken/KRResource+fbx.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/KREngine/kraken/KRResource+fbx.cpp b/KREngine/kraken/KRResource+fbx.cpp index d9589b0..697da3e 100644 --- a/KREngine/kraken/KRResource+fbx.cpp +++ b/KREngine/kraken/KRResource+fbx.cpp @@ -130,7 +130,7 @@ void KRResource::LoadFbx(KRContext &context, const std::string& path) int animation_count = pFbxScene->GetSrcObjectCount(); for(int i = 0; i < animation_count; i++) { FbxAnimStack *animation = pFbxScene->GetSrcObject(i); - KRContext::Log(KRContext::LOG_LEVEL_INFORMATION, " Animation %i of %i: %s", i+1, animation_count, animation->GetName().c_str()); + KRContext::Log(KRContext::LOG_LEVEL_INFORMATION, " Animation %i of %i: %s", i+1, animation_count, animation->GetName()); KRAnimation *new_animation = LoadAnimation(context, animation); if(new_animation) { context.getAnimationManager()->addAnimation(new_animation); @@ -142,7 +142,7 @@ void KRResource::LoadFbx(KRContext &context, const std::string& path) int curve_count = pFbxScene->GetSrcObjectCount(); for(int i=0; i < curve_count; i++) { FbxAnimCurve *curve = pFbxScene->GetSrcObject(i); - KRContext::Log(KRContext::LOG_LEVEL_INFORMATION, " Animation Curve %i of %i: %s", i+1, curve_count, curve->GetName().c_str()); + KRContext::Log(KRContext::LOG_LEVEL_INFORMATION, " Animation Curve %i of %i: %s", i+1, curve_count, curve->GetName()); KRAnimationCurve *new_curve = LoadAnimationCurve(context, curve); if(new_curve) { @@ -156,7 +156,7 @@ void KRResource::LoadFbx(KRContext &context, const std::string& path) KRContext::Log(KRContext::LOG_LEVEL_INFORMATION, "\nLoading materials..."); for(int i=0; i < material_count; i++) { FbxSurfaceMaterial *material = pFbxScene->GetSrcObject(i); - KRContext::Log(KRContext::LOG_LEVEL_INFORMATION, " Material %i of %i: %s", i+1, material_count, material->GetName().c_str()); + KRContext::Log(KRContext::LOG_LEVEL_INFORMATION, " Material %i of %i: %s", i+1, material_count, material->GetName()); LoadMaterial(context, material); } @@ -166,7 +166,7 @@ void KRResource::LoadFbx(KRContext &context, const std::string& path) for(int i=0; i < mesh_count; i++) { FbxMesh *mesh = pFbxScene->GetSrcObject(i); - KRContext::Log(KRContext::LOG_LEVEL_INFORMATION, " Mesh %i of %i: %s", i+1, mesh_count, mesh->GetNode()->GetName().c_str()); + KRContext::Log(KRContext::LOG_LEVEL_INFORMATION, " Mesh %i of %i: %s", i+1, mesh_count, mesh->GetNode()->GetName()); LoadMesh(context, pFbxScene, pGeometryConverter, mesh); } From 5170722a4c8d63d25b595d4d6910d6fb586ae93e Mon Sep 17 00:00:00 2001 From: Kearwood Gilbert Date: Sat, 14 Dec 2013 18:13:00 -0800 Subject: [PATCH 10/84] Corrected formatting of logged messages. --HG-- branch : nfb --- KREngine/kraken/KRContext.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/KREngine/kraken/KRContext.cpp b/KREngine/kraken/KRContext.cpp index c11dbb1..aa44b5c 100644 --- a/KREngine/kraken/KRContext.cpp +++ b/KREngine/kraken/KRContext.cpp @@ -119,12 +119,12 @@ void KRContext::Log(log_level level, const std::string &message_format, ...) if(s_log_callback) { const int LOG_BUFFER_SIZE = 32768; char log_buffer[LOG_BUFFER_SIZE]; - snprintf(log_buffer, LOG_BUFFER_SIZE, message_format.c_str(), args); + vsnprintf(log_buffer, LOG_BUFFER_SIZE, message_format.c_str(), args); s_log_callback(s_log_callback_user_data, std::string(log_buffer), level); } else { FILE *out_file = level == LOG_LEVEL_INFORMATION ? stdout : stderr; fprintf(out_file, "Kraken - INFO: "); - fprintf(out_file, message_format.c_str(), args); + vfprintf(out_file, message_format.c_str(), args); fprintf(out_file, "\n"); } From 5923046cd87beac24cb2927eba93351328efd41f Mon Sep 17 00:00:00 2001 From: Peter Courtemanche Date: Mon, 16 Dec 2013 17:18:12 -0800 Subject: [PATCH 11/84] Update LoadXML to use the same field names as SaveXML --HG-- branch : nfb --- KREngine/kraken/KRAmbientZone.cpp | 4 ++-- KREngine/kraken/KRReverbZone.cpp | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/KREngine/kraken/KRAmbientZone.cpp b/KREngine/kraken/KRAmbientZone.cpp index d44d075..927caa5 100644 --- a/KREngine/kraken/KRAmbientZone.cpp +++ b/KREngine/kraken/KRAmbientZone.cpp @@ -47,10 +47,10 @@ void KRAmbientZone::loadXML(tinyxml2::XMLElement *e) m_gradient_distance = 0.25f; } - m_ambient = e->Attribute("ambient"); + m_ambient = e->Attribute("sample"); m_ambient_gain = 1.0f; - if(e->QueryFloatAttribute("ambient_gain", &m_ambient_gain) != tinyxml2::XML_SUCCESS) { + if(e->QueryFloatAttribute("gain", &m_ambient_gain) != tinyxml2::XML_SUCCESS) { m_ambient_gain = 1.0f; } } diff --git a/KREngine/kraken/KRReverbZone.cpp b/KREngine/kraken/KRReverbZone.cpp index 5622717..322dfea 100644 --- a/KREngine/kraken/KRReverbZone.cpp +++ b/KREngine/kraken/KRReverbZone.cpp @@ -46,10 +46,10 @@ void KRReverbZone::loadXML(tinyxml2::XMLElement *e) m_gradient_distance = 0.25f; } - m_reverb = e->Attribute("reverb"); + m_reverb = e->Attribute("sample"); m_reverb_gain = 1.0f; - if(e->QueryFloatAttribute("reverb_gain", &m_reverb_gain) != tinyxml2::XML_SUCCESS) { + if(e->QueryFloatAttribute("gain", &m_reverb_gain) != tinyxml2::XML_SUCCESS) { m_reverb_gain = 1.0f; } } From 2a34b54ed4c2a6a9a6152b741cc3aabe11246219 Mon Sep 17 00:00:00 2001 From: Peter Courtemanche Date: Tue, 17 Dec 2013 20:20:49 -0800 Subject: [PATCH 12/84] Updated header search paths (actually reverted to the one that works) --HG-- branch : nfb --- KREngine/Kraken.xcodeproj/project.pbxproj | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/KREngine/Kraken.xcodeproj/project.pbxproj b/KREngine/Kraken.xcodeproj/project.pbxproj index eef1240..f7686fe 100644 --- a/KREngine/Kraken.xcodeproj/project.pbxproj +++ b/KREngine/Kraken.xcodeproj/project.pbxproj @@ -1792,10 +1792,7 @@ GCC_VERSION = ""; GCC_WARN_ABOUT_RETURN_TYPE = YES; GCC_WARN_UNUSED_VARIABLE = YES; - HEADER_SEARCH_PATHS = ( - /usr/local/include, - "$(SRCROOT)/3rdparty/**", - ); + HEADER_SEARCH_PATHS = /usr/local/include/; LLVM_VECTORIZE_LOOPS = YES; MACOSX_DEPLOYMENT_TARGET = 10.6; SDKROOT = iphoneos; @@ -1815,10 +1812,7 @@ GCC_VERSION = ""; GCC_WARN_ABOUT_RETURN_TYPE = YES; GCC_WARN_UNUSED_VARIABLE = YES; - HEADER_SEARCH_PATHS = ( - /usr/local/include, - "$(SRCROOT)/3rdparty/**", - ); + HEADER_SEARCH_PATHS = /usr/local/include/; LLVM_VECTORIZE_LOOPS = YES; MACOSX_DEPLOYMENT_TARGET = 10.6; SDKROOT = iphoneos; @@ -1838,6 +1832,7 @@ ); GCC_PRECOMPILE_PREFIX_HEADER = YES; GCC_PREFIX_HEADER = "kraken_ios/Kraken-Prefix.pch"; + HEADER_SEARCH_PATHS = /usr/local/include/; IPHONEOS_DEPLOYMENT_TARGET = 7.0; LIBRARY_SEARCH_PATHS = ( "$(inherited)", @@ -1847,6 +1842,7 @@ PRODUCT_NAME = kraken; SHARED_PRECOMPS_DIR = "$(CACHE_ROOT)/SharedPrecompiledHeaders"; TARGETED_DEVICE_FAMILY = "1,2"; + USER_HEADER_SEARCH_PATHS = /usr/local/include; }; name = Debug; }; @@ -1862,6 +1858,7 @@ ); GCC_PRECOMPILE_PREFIX_HEADER = YES; GCC_PREFIX_HEADER = "kraken_ios/Kraken-Prefix.pch"; + HEADER_SEARCH_PATHS = /usr/local/include/; IPHONEOS_DEPLOYMENT_TARGET = 7.0; LIBRARY_SEARCH_PATHS = ( "$(inherited)", @@ -1871,6 +1868,7 @@ PRODUCT_NAME = kraken; SHARED_PRECOMPS_DIR = "$(CACHE_ROOT)/SharedPrecompiledHeaders"; TARGETED_DEVICE_FAMILY = "1,2"; + USER_HEADER_SEARCH_PATHS = /usr/local/include; }; name = Release; }; From 5de7ef1d2638a482772e9a57eb8225656537e483 Mon Sep 17 00:00:00 2001 From: Kearwood Gilbert Date: Fri, 3 Jan 2014 02:17:27 -0800 Subject: [PATCH 13/84] OSX Build now running again Implemented KTX texture file support Implemented workflow for compressing TGA textures referenced by FBX files to KTX with DXT1 and DXT5 format. --HG-- branch : nfb --- KREngine/Kraken.xcodeproj/project.pbxproj | 12 + KREngine/kraken/KRContext.cpp | 2 + KREngine/kraken/KRMeshStreamer.mm | 4 +- KREngine/kraken/KRTexture.h | 2 +- KREngine/kraken/KRTexture2D.h | 2 +- KREngine/kraken/KRTextureKTX.cpp | 277 ++++++++++++++++++++++ KREngine/kraken/KRTextureKTX.h | 51 ++++ KREngine/kraken/KRTextureManager.cpp | 3 + KREngine/kraken/KRTexturePVR.cpp | 2 +- KREngine/kraken/KRTexturePVR.h | 2 +- KREngine/kraken/KRTextureStreamer.mm | 4 +- KREngine/kraken/KRTextureTGA.cpp | 82 ++++++- KREngine/kraken/KRTextureTGA.h | 3 +- 13 files changed, 434 insertions(+), 12 deletions(-) create mode 100644 KREngine/kraken/KRTextureKTX.cpp create mode 100644 KREngine/kraken/KRTextureKTX.h diff --git a/KREngine/Kraken.xcodeproj/project.pbxproj b/KREngine/Kraken.xcodeproj/project.pbxproj index f7686fe..18e49e4 100644 --- a/KREngine/Kraken.xcodeproj/project.pbxproj +++ b/KREngine/Kraken.xcodeproj/project.pbxproj @@ -12,6 +12,10 @@ 10CC33A5168534F000BB9846 /* KRCamera.cpp in Sources */ = {isa = PBXBuildFile; fileRef = E48B3CBF14393E2F000C50E2 /* KRCamera.cpp */; }; E4030E4C160A3CF000592648 /* KRStockGeometry.h in Headers */ = {isa = PBXBuildFile; fileRef = E4030E4B160A3CF000592648 /* KRStockGeometry.h */; settings = {ATTRIBUTES = (); }; }; E4030E4D160A3CF000592648 /* KRStockGeometry.h in Headers */ = {isa = PBXBuildFile; fileRef = E4030E4B160A3CF000592648 /* KRStockGeometry.h */; settings = {ATTRIBUTES = (Public, ); }; }; + E404702018695DD200F01F42 /* KRTextureKTX.cpp in Sources */ = {isa = PBXBuildFile; fileRef = E404701E18695DD200F01F42 /* KRTextureKTX.cpp */; }; + E404702118695DD200F01F42 /* KRTextureKTX.cpp in Sources */ = {isa = PBXBuildFile; fileRef = E404701E18695DD200F01F42 /* KRTextureKTX.cpp */; }; + E404702218695DD200F01F42 /* KRTextureKTX.h in Headers */ = {isa = PBXBuildFile; fileRef = E404701F18695DD200F01F42 /* KRTextureKTX.h */; }; + E404702318695DD200F01F42 /* KRTextureKTX.h in Headers */ = {isa = PBXBuildFile; fileRef = E404701F18695DD200F01F42 /* KRTextureKTX.h */; }; E40BA45415EFF79500D7C3DD /* KRAABB.cpp in Sources */ = {isa = PBXBuildFile; fileRef = E40BA45215EFF79500D7C3DD /* KRAABB.cpp */; }; E40BA45515EFF79500D7C3DD /* KRAABB.cpp in Sources */ = {isa = PBXBuildFile; fileRef = E40BA45215EFF79500D7C3DD /* KRAABB.cpp */; }; E40BA45615EFF79500D7C3DD /* KRAABB.h in Headers */ = {isa = PBXBuildFile; fileRef = E40BA45315EFF79500D7C3DD /* KRAABB.h */; settings = {ATTRIBUTES = (); }; }; @@ -409,6 +413,8 @@ 104A335D1672D31C001C8BA6 /* KRCollider.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; lineEnding = 0; path = KRCollider.h; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.objcpp; }; 10CC33A3168530A300BB9846 /* libPVRTexLib.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libPVRTexLib.a; path = Utilities/PVRTexLib/MacOS/libPVRTexLib.a; sourceTree = PVRSDK; }; E4030E4B160A3CF000592648 /* KRStockGeometry.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = KRStockGeometry.h; sourceTree = ""; }; + E404701E18695DD200F01F42 /* KRTextureKTX.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = KRTextureKTX.cpp; sourceTree = ""; }; + E404701F18695DD200F01F42 /* KRTextureKTX.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = KRTextureKTX.h; sourceTree = ""; }; E40BA45215EFF79500D7C3DD /* KRAABB.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = KRAABB.cpp; sourceTree = ""; }; E40BA45315EFF79500D7C3DD /* KRAABB.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = KRAABB.h; sourceTree = ""; }; E40F982A184A7A2700CFA4D8 /* KRMeshQuad.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = KRMeshQuad.cpp; sourceTree = ""; }; @@ -922,6 +928,8 @@ E460292716681D1000261BB9 /* KRTextureAnimated.cpp */, E43F70E31824D9AB00136169 /* KRTextureStreamer.mm */, E43F70E41824D9AB00136169 /* KRTextureStreamer.h */, + E404701E18695DD200F01F42 /* KRTextureKTX.cpp */, + E404701F18695DD200F01F42 /* KRTextureKTX.h */, ); name = Texture; sourceTree = ""; @@ -1301,6 +1309,7 @@ E499BF1E16AE751E007FCDBE /* KRSceneManager.h in Headers */, E499BF2016AE755B007FCDBE /* KRPointLight.h in Headers */, E499BF2116AE75A7007FCDBE /* KREngine-common.h in Headers */, + E404702218695DD200F01F42 /* KRTextureKTX.h in Headers */, E4F027D016979CE200D4427D /* KRAudioSample.h in Headers */, E450273B16E0491D00FDEC5C /* KRReverbZone.h in Headers */, E4FE6AA816B21D660058B8CE /* forsyth.h in Headers */, @@ -1369,6 +1378,7 @@ E4324BA516444C0D0043185B /* KRParticleSystem.h in Headers */, E4324BAC16444DEF0043185B /* KRParticleSystemNewtonian.h in Headers */, E4C454AD167BB8EC003586CD /* KRMeshCube.h in Headers */, + E404702318695DD200F01F42 /* KRTextureKTX.h in Headers */, E414F9A91694D977000B3D58 /* KRUnknownManager.h in Headers */, E48B68181697794F00D99917 /* KRAudioSource.h in Headers */, E4F027CA16979CCD00D4427D /* KRAudioManager.h in Headers */, @@ -1626,6 +1636,7 @@ E43B0AD615DDCA0F00A5CB9F /* KRContextObject.cpp in Sources */, E4924C2615EE95E800B965C6 /* KROctree.cpp in Sources */, E4924C2B15EE96AB00B965C6 /* KROctreeNode.cpp in Sources */, + E404702018695DD200F01F42 /* KRTextureKTX.cpp in Sources */, E40BA45415EFF79500D7C3DD /* KRAABB.cpp in Sources */, E488399415F928CA00BD66D5 /* KRBundle.cpp in Sources */, E488399C15F92BE000BD66D5 /* KRBundleManager.cpp in Sources */, @@ -1742,6 +1753,7 @@ E4F027CF16979CE200D4427D /* KRAudioSample.cpp in Sources */, E4F027DF1697BFFF00D4427D /* KRAudioBuffer.cpp in Sources */, E4943232169E08D200BCB891 /* KRAmbientZone.cpp in Sources */, + E404702118695DD200F01F42 /* KRTextureKTX.cpp in Sources */, E450273A16E0491D00FDEC5C /* KRReverbZone.cpp in Sources */, E4AE635E1704FB0A00B460CD /* KRLODGroup.cpp in Sources */, E4EC73C21720B1FF0065299F /* KRVector4.cpp in Sources */, diff --git a/KREngine/kraken/KRContext.cpp b/KREngine/kraken/KRContext.cpp index aa44b5c..60a4dff 100644 --- a/KREngine/kraken/KRContext.cpp +++ b/KREngine/kraken/KRContext.cpp @@ -219,6 +219,8 @@ void KRContext::loadResource(const std::string &file_name, KRDataBlock *data) { m_pAnimationCurveManager->loadAnimationCurve(name.c_str(), data); } else if(extension.compare("pvr") == 0) { m_pTextureManager->loadTexture(name.c_str(), extension.c_str(), data); + } else if(extension.compare("ktx") == 0) { + m_pTextureManager->loadTexture(name.c_str(), extension.c_str(), data); } else if(extension.compare("tga") == 0) { m_pTextureManager->loadTexture(name.c_str(), extension.c_str(), data); } else if(extension.compare("vsh") == 0) { diff --git a/KREngine/kraken/KRMeshStreamer.mm b/KREngine/kraken/KRMeshStreamer.mm index 1fd6560..704a646 100644 --- a/KREngine/kraken/KRMeshStreamer.mm +++ b/KREngine/kraken/KRMeshStreamer.mm @@ -42,11 +42,11 @@ void KRMeshStreamer::startStreamer() #elif TARGET_OS_MAC NSOpenGLPixelFormatAttribute pixelFormatAttributes[] = { - NSOpenGLPFAOpenGLProfile, NSOpenGLProfileVersion3_2Core, + NSOpenGLPFAOpenGLProfile, NSOpenGLProfileVersionLegacy, 0 }; NSOpenGLPixelFormat *pixelFormat = [[[NSOpenGLPixelFormat alloc] initWithAttributes:pixelFormatAttributes] autorelease]; - gMeshStreamerContext = [[[NSOpenGLContext alloc] initWithFormat: pixelFormat shareContext: [NSOpenGLContext currentContext] ] autorelease]; + gMeshStreamerContext = [[NSOpenGLContext alloc] initWithFormat: pixelFormat shareContext: [NSOpenGLContext currentContext] ]; #else #error Unsupported Platform #endif diff --git a/KREngine/kraken/KRTexture.h b/KREngine/kraken/KRTexture.h index 592ac14..defc431 100644 --- a/KREngine/kraken/KRTexture.h +++ b/KREngine/kraken/KRTexture.h @@ -59,7 +59,7 @@ public: virtual void resetPoolExpiry(); virtual bool isAnimated(); - KRTexture *compress(); + virtual KRTexture *compress(); int getCurrentLodMaxDim(); int getMaxMipMap(); int getMinMipMap(); diff --git a/KREngine/kraken/KRTexture2D.h b/KREngine/kraken/KRTexture2D.h index 139e5d5..f7f4303 100644 --- a/KREngine/kraken/KRTexture2D.h +++ b/KREngine/kraken/KRTexture2D.h @@ -46,7 +46,7 @@ public: virtual bool save(const std::string& path); virtual bool save(KRDataBlock &data); - virtual bool uploadTexture(GLenum target, int lod_max_dim, int ¤t_lod_max_dim, int prev_lod_max_dim) = 0; + virtual bool uploadTexture(GLenum target, int lod_max_dim, int ¤t_lod_max_dim, int prev_lod_max_dim, bool compress = false) = 0; virtual void bind(GLuint texture_unit); protected: diff --git a/KREngine/kraken/KRTextureKTX.cpp b/KREngine/kraken/KRTextureKTX.cpp new file mode 100644 index 0000000..2270bfd --- /dev/null +++ b/KREngine/kraken/KRTextureKTX.cpp @@ -0,0 +1,277 @@ +// +// KRTextureKTX.cpp +// KREngine +// +// Copyright 2012 Kearwood Gilbert. All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, are +// permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of +// conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list +// of conditions and the following disclaimer in the documentation and/or other materials +// provided with the distribution. +// +// THIS SOFTWARE IS PROVIDED BY KEARWOOD GILBERT ''AS IS'' AND ANY EXPRESS OR IMPLIED +// WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND +// FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL KEARWOOD GILBERT OR +// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON +// ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF +// ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// +// The views and conclusions contained in the software and documentation are those of the +// authors and should not be interpreted as representing official policies, either expressed +// or implied, of Kearwood Gilbert. +// + +#include "KRTextureKTX.h" +#include "KRTextureManager.h" + +#include "KREngine-common.h" + +Byte _KTXFileIdentifier[12] = { + 0xAB, 0x4B, 0x54, 0x58, 0x20, 0x31, 0x31, 0xBB, 0x0D, 0x0A, 0x1A, 0x0A +}; + +KRTextureKTX::KRTextureKTX(KRContext &context, KRDataBlock *data, std::string name) : KRTexture2D(context, data, name) { + m_pData->copy(&m_header, 0, sizeof(KTXHeader)); + if(memcmp(_KTXFileIdentifier, m_header.identifier, 12) != 0) { + assert(false); // Header not recognized + } + if(m_header.endianness != 0x04030201) { + assert(false); // Endianness not (yet) supported + } + if(m_header.pixelDepth != 0) { + assert(false); // 3d textures not (yet) supported + } + if(m_header.numberOfArrayElements != 0) { + assert(false); // Array textures not (yet) supported + } + if(m_header.numberOfFaces != 1) { + assert(false); // Cube-map textures are only supported as a file for each separate face (for now) + } + + uint32_t blockStart = sizeof(KTXHeader) + m_header.bytesOfKeyValueData; + uint32_t width = m_header.pixelWidth, height = m_header.pixelHeight; + + for(int mipmap_level=0; mipmap_level < KRMIN(m_header.numberOfMipmapLevels, 1); mipmap_level++) { + uint32_t blockLength; + data->copy(&blockLength, blockStart, 4); + + m_blocks.push_back(m_pData->getSubBlock(blockStart, blockLength)); + + blockStart += blockLength; + blockStart = KRALIGN(blockStart); + + width = width >> 1; + if(width < 1) { + width = 1; + } + height = height >> 1; + if(height < 1) { + height = 1; + } + } + + m_max_lod_max_dim = KRMAX(m_header.pixelWidth, m_header.pixelHeight); + m_min_lod_max_dim = KRMAX(width, height); +} + +KRTextureKTX::KRTextureKTX(KRContext &context, std::string name, GLenum internal_format, GLenum base_internal_format, int width, int height, const std::list &blocks) : KRTexture2D(context, new KRDataBlock(), name) +{ + memcpy(_KTXFileIdentifier, m_header.identifier, 12); + m_header.endianness = 0x04030201; + m_header.glType = 0; + m_header.glTypeSize = 1; + m_header.glFormat = 0; + m_header.glInternalFormat = internal_format; + m_header.glBaseInternalFormat = base_internal_format; + m_header.pixelWidth = width; + m_header.pixelHeight = height; + m_header.pixelDepth = 0; + m_header.numberOfArrayElements = 0; + m_header.numberOfFaces = 1; + m_header.numberOfMipmapLevels = (UInt32)blocks.size(); + m_header.bytesOfKeyValueData = 0; + + m_pData->append(&m_header, sizeof(m_header)); + for(auto block_itr = blocks.begin(); block_itr != blocks.end(); block_itr++) { + KRDataBlock *source_block = *block_itr; + UInt32 block_size = (UInt32)source_block->getSize(); + m_pData->append(&block_size, 4); + m_pData->append(*source_block); + m_blocks.push_back(m_pData->getSubBlock((int)m_pData->getSize() - (int)block_size, (int)block_size)); + size_t alignment_padding_size = KRALIGN(m_pData->getSize()) - m_pData->getSize(); + Byte alignment_padding[4] = {0, 0, 0, 0}; + if(alignment_padding_size > 0) { + m_pData->append(&alignment_padding, alignment_padding_size); + } + } +} + +KRTextureKTX::~KRTextureKTX() { + for(std::list::iterator itr = m_blocks.begin(); itr != m_blocks.end(); itr++) { + KRDataBlock *block = *itr; + delete block; + } + m_blocks.clear(); +} + +long KRTextureKTX::getMemRequiredForSize(int max_dim) +{ + int target_dim = max_dim; + if(target_dim < m_min_lod_max_dim) target_dim = target_dim; + + // Determine how much memory will be consumed + + int width = m_header.pixelWidth; + int height = m_header.pixelHeight; + long memoryRequired = 0; + + for(std::list::iterator itr = m_blocks.begin(); itr != m_blocks.end(); itr++) { + KRDataBlock *block = *itr; + if(width <= target_dim && height <= target_dim) { + memoryRequired += block->getSize(); + } + + width = width >> 1; + if(width < 1) { + width = 1; + } + height = height >> 1; + if(height < 1) { + height = 1; + } + } + + return memoryRequired; +} + +bool KRTextureKTX::uploadTexture(GLenum target, int lod_max_dim, int ¤t_lod_max_dim, int prev_lod_max_dim, bool compress) +{ + int target_dim = lod_max_dim; + if(target_dim < m_min_lod_max_dim) target_dim = m_min_lod_max_dim; + + if(m_blocks.size() == 0) { + return false; + } + + // Determine how much memory will be consumed + int width = m_header.pixelWidth; + int height = m_header.pixelHeight; + long memoryRequired = 0; + long memoryTransferred = 0; + + +#if GL_EXT_texture_storage + + if(target == GL_TEXTURE_CUBE_MAP_POSITIVE_X || target == GL_TEXTURE_2D) { + // Call glTexStorage2DEXT only for the first uploadTexture used when creating a texture + int level_count=0; + int max_lod_width=0; + int max_lod_height=0; + for(std::list::iterator itr = m_blocks.begin(); itr != m_blocks.end(); itr++) { + if(width <= target_dim && height <= target_dim) { + if(max_lod_width == 0) { + max_lod_width = width; + max_lod_height = height; + } + + level_count++; + } + + width = width >> 1; + if(width < 1) { + width = 1; + } + height = height >> 1; + if(height < 1) { + height = 1; + } + } + width = m_iWidth; + height = m_iHeight; + + if(target == GL_TEXTURE_CUBE_MAP_POSITIVE_X) { + glTexStorage2DEXT(GL_TEXTURE_CUBE_MAP, level_count, (GLenum)m_header.glInternalFormat, max_lod_width, max_lod_height); + } else if(target == GL_TEXTURE_2D) { + glTexStorage2DEXT(target, level_count, (GLenum)m_header.glInternalFormat, max_lod_width, max_lod_height); + } + } +#endif + + // Upload texture data + int destination_level=0; + int source_level = 0; + for(std::list::iterator itr = m_blocks.begin(); itr != m_blocks.end(); itr++) { + KRDataBlock *block = *itr; + if(width <= target_dim && height <= target_dim) { + + if(width > current_lod_max_dim) { + current_lod_max_dim = width; + } + if(height > current_lod_max_dim) { + current_lod_max_dim = height; + } +#if GL_APPLE_copy_texture_levels && GL_EXT_texture_storage + if(target == GL_TEXTURE_2D && width <= prev_lod_max_dim && height <= prev_lod_max_dim) { + //GLDEBUG(glCompressedTexImage2D(target, i, (GLenum)m_header.glInternalFormat, width, height, 0, block.length, NULL)); // Allocate, but don't copy + // GLDEBUG(glTexImage2D(target, 0, GL_RGBA, width, height, 0, GL_BGRA, GL_UNSIGNED_BYTE, NULL)); + GLDEBUG(glCopyTextureLevelsAPPLE(m_iNewHandle, m_iHandle, source_level, 1)); + } else { + block->lock(); + GLDEBUG(glCompressedTexSubImage2D(target, destination_level, 0, 0, width, height, (GLenum)m_header.glInternalFormat, (GLsizei)block->getSize(), block->getStart())); + block->unlock(); + + memoryTransferred += block->getSize(); // memoryTransferred does not include throughput of mipmap levels copied through glCopyTextureLevelsAPPLE + } +#else + block->lock(); +#if GL_EXT_texture_storage + GLDEBUG(glCompressedTexSubImage2D(target, destination_level, 0, 0, width, height, (GLenum)m_header.glInternalFormat, (GLsizei)block->getSize(), block->getStart())); +#else + GLDEBUG(glCompressedTexImage2D(target, destination_level, (GLenum)m_header.glInternalFormat, width, height, 0, (GLsizei)block->getSize(), block->getStart())); +#endif + block->unlock(); + memoryTransferred += block->getSize(); // memoryTransferred does not include throughput of mipmap levels copied through glCopyTextureLevelsAPPLE +#endif + memoryRequired += block->getSize(); + // + // err = glGetError(); + // if (err != GL_NO_ERROR) { + // assert(false); + // return false; + // } + // + + destination_level++; + } + + if(width <= prev_lod_max_dim && height <= prev_lod_max_dim) { + source_level++; + } + + width = width >> 1; + if(width < 1) { + width = 1; + } + height = height >> 1; + if(height < 1) { + height = 1; + } + } + + return true; + +} + +std::string KRTextureKTX::getExtension() +{ + return "ktx"; +} + diff --git a/KREngine/kraken/KRTextureKTX.h b/KREngine/kraken/KRTextureKTX.h new file mode 100644 index 0000000..13308b8 --- /dev/null +++ b/KREngine/kraken/KRTextureKTX.h @@ -0,0 +1,51 @@ +// +// KRTextureKTX.h +// KREngine +// +// Created by Kearwood Gilbert on 2012-10-23. +// Copyright (c) 2012 Kearwood Software. All rights reserved. +// + +#ifndef KRTEXTUREKTX_H +#define KRTEXTUREKTX_H + +#include "KRTexture2D.h" + +class KRTextureKTX : public KRTexture2D +{ +public: + KRTextureKTX(KRContext &context, KRDataBlock *data, std::string name); + KRTextureKTX(KRContext &context, std::string name, GLenum internal_format, GLenum base_internal_format, int width, int height, const std::list &blocks); + virtual ~KRTextureKTX(); + virtual std::string getExtension(); + + bool uploadTexture(GLenum target, int lod_max_dim, int ¤t_lod_max_dim, int prev_lod_max_dim, bool compress = false); + + virtual long getMemRequiredForSize(int max_dim); + +protected: + + std::list m_blocks; + + typedef struct _KTXHeader + { + Byte identifier[12]; + UInt32 endianness; + UInt32 glType; + UInt32 glTypeSize; + UInt32 glFormat; + UInt32 glInternalFormat; + UInt32 glBaseInternalFormat; + UInt32 pixelWidth; + UInt32 pixelHeight; + UInt32 pixelDepth; + UInt32 numberOfArrayElements; + UInt32 numberOfFaces; + UInt32 numberOfMipmapLevels; + UInt32 bytesOfKeyValueData; + } KTXHeader; + + KTXHeader m_header; +}; + +#endif diff --git a/KREngine/kraken/KRTextureManager.cpp b/KREngine/kraken/KRTextureManager.cpp index 74b718f..2f554c8 100644 --- a/KREngine/kraken/KRTextureManager.cpp +++ b/KREngine/kraken/KRTextureManager.cpp @@ -35,6 +35,7 @@ #include "KRTexture2D.h" #include "KRTexturePVR.h" #include "KRTextureTGA.h" +#include "KRTextureKTX.h" #include "KRTextureCube.h" #include "KRTextureAnimated.h" #include "KRContext.h" @@ -127,6 +128,8 @@ KRTexture *KRTextureManager::loadTexture(const char *szName, const char *szExten pTexture = new KRTexturePVR(getContext(), data, szName); } else if(strcmp(szExtension, "tga") == 0) { pTexture = new KRTextureTGA(getContext(), data, szName); + } else if(strcmp(szExtension, "ktx") == 0) { + pTexture = new KRTextureKTX(getContext(), data, szName); } if(pTexture) { diff --git a/KREngine/kraken/KRTexturePVR.cpp b/KREngine/kraken/KRTexturePVR.cpp index 6ce9a65..419ccbe 100644 --- a/KREngine/kraken/KRTexturePVR.cpp +++ b/KREngine/kraken/KRTexturePVR.cpp @@ -173,7 +173,7 @@ long KRTexturePVR::getMemRequiredForSize(int max_dim) return memoryRequired; } -bool KRTexturePVR::uploadTexture(GLenum target, int lod_max_dim, int ¤t_lod_max_dim, int prev_lod_max_dim) +bool KRTexturePVR::uploadTexture(GLenum target, int lod_max_dim, int ¤t_lod_max_dim, int prev_lod_max_dim, bool compress) { int target_dim = lod_max_dim; if(target_dim < m_min_lod_max_dim) target_dim = m_min_lod_max_dim; diff --git a/KREngine/kraken/KRTexturePVR.h b/KREngine/kraken/KRTexturePVR.h index 974619e..ea15878 100644 --- a/KREngine/kraken/KRTexturePVR.h +++ b/KREngine/kraken/KRTexturePVR.h @@ -18,7 +18,7 @@ public: virtual ~KRTexturePVR(); virtual std::string getExtension(); - bool uploadTexture(GLenum target, int lod_max_dim, int ¤t_lod_max_dim, int prev_lod_max_dim); + bool uploadTexture(GLenum target, int lod_max_dim, int ¤t_lod_max_dim, int prev_lod_max_dim, bool compress = false); virtual long getMemRequiredForSize(int max_dim); diff --git a/KREngine/kraken/KRTextureStreamer.mm b/KREngine/kraken/KRTextureStreamer.mm index 5185c22..8731730 100644 --- a/KREngine/kraken/KRTextureStreamer.mm +++ b/KREngine/kraken/KRTextureStreamer.mm @@ -47,11 +47,11 @@ void KRTextureStreamer::startStreamer() NSOpenGLPixelFormatAttribute pixelFormatAttributes[] = { - NSOpenGLPFAOpenGLProfile, NSOpenGLProfileVersion3_2Core, + NSOpenGLPFAOpenGLProfile, NSOpenGLProfileVersionLegacy, 0 }; NSOpenGLPixelFormat *pixelFormat = [[[NSOpenGLPixelFormat alloc] initWithAttributes:pixelFormatAttributes] autorelease]; - gTextureStreamerContext = [[[NSOpenGLContext alloc] initWithFormat: pixelFormat shareContext: [NSOpenGLContext currentContext] ] autorelease]; + gTextureStreamerContext = [[NSOpenGLContext alloc] initWithFormat: pixelFormat shareContext: [NSOpenGLContext currentContext] ]; #else diff --git a/KREngine/kraken/KRTextureTGA.cpp b/KREngine/kraken/KRTextureTGA.cpp index 373385b..a94bc58 100644 --- a/KREngine/kraken/KRTextureTGA.cpp +++ b/KREngine/kraken/KRTextureTGA.cpp @@ -9,6 +9,7 @@ #include "KRTextureTGA.h" #include "KREngine-common.h" #include "KRContext.h" +#include "KRTextureKTX.h" typedef struct { char idlength; @@ -69,11 +70,20 @@ KRTextureTGA::~KRTextureTGA() } -bool KRTextureTGA::uploadTexture(GLenum target, int lod_max_dim, int ¤t_lod_max_dim, int prev_lod_max_dim) +bool KRTextureTGA::uploadTexture(GLenum target, int lod_max_dim, int ¤t_lod_max_dim, int prev_lod_max_dim, bool compress) { m_pData->lock(); TGA_HEADER *pHeader = (TGA_HEADER *)m_pData->getStart(); unsigned char *pData = (unsigned char *)pHeader + (long)pHeader->idlength + (long)pHeader->colourmaplength * (long)pHeader->colourmaptype + sizeof(TGA_HEADER); + + + GLenum base_internal_format = pHeader->bitsperpixel == 24 ? GL_BGR : GL_BGRA; + + GLenum internal_format = 0; + if(compress) { + internal_format = pHeader->bitsperpixel == 24 ? GL_COMPRESSED_RGB_S3TC_DXT1_EXT : GL_COMPRESSED_RGBA_S3TC_DXT5_EXT; + } + if(pHeader->colourmaptype != 0) { m_pData->unlock(); @@ -104,7 +114,7 @@ bool KRTextureTGA::uploadTexture(GLenum target, int lod_max_dim, int ¤t_lo pSource += 3; } //#endif - glTexImage2D(target, 0, GL_RGBA, pHeader->width, pHeader->height, 0, GL_BGRA, GL_UNSIGNED_BYTE, (GLvoid *)converted_image); + glTexImage2D(target, 0, internal_format, pHeader->width, pHeader->height, 0, GL_BGR, GL_UNSIGNED_BYTE, (GLvoid *)converted_image); free(converted_image); err = glGetError(); if (err != GL_NO_ERROR) { @@ -116,7 +126,7 @@ bool KRTextureTGA::uploadTexture(GLenum target, int lod_max_dim, int ¤t_lo break; case 32: { - glTexImage2D(target, 0, GL_RGBA, pHeader->width, pHeader->height, 0, GL_BGRA, GL_UNSIGNED_BYTE, (GLvoid *)pData); + glTexImage2D(target, 0, internal_format, pHeader->width, pHeader->height, 0, GL_BGRA, GL_UNSIGNED_BYTE, (GLvoid *)pData); err = glGetError(); if (err != GL_NO_ERROR) { m_pData->unlock(); @@ -139,6 +149,72 @@ bool KRTextureTGA::uploadTexture(GLenum target, int lod_max_dim, int ¤t_lo return true; } +KRTexture *KRTextureTGA::compress() +{ + m_pData->lock(); + + std::list blocks; + + getContext().getTextureManager()->_setActiveTexture(0); + + GLuint compressed_handle = 0; + GLDEBUG(glGenTextures(1, &compressed_handle)); + + GLDEBUG(glBindTexture(GL_TEXTURE_2D, compressed_handle)); + + int current_max_dim = 0; + if(!uploadTexture(GL_TEXTURE_2D, m_max_lod_max_dim, current_max_dim, 0, true)) { + assert(false); // Failed to upload the texture + } + GLDEBUG(glGenerateMipmap(GL_TEXTURE_2D)); + + GLint width = 0, height = 0, internal_format, base_internal_format = GL_BGRA; + GLDEBUG(glGetTexLevelParameteriv(GL_TEXTURE_2D, 0, GL_TEXTURE_WIDTH, &width)); + GLDEBUG(glGetTexLevelParameteriv(GL_TEXTURE_2D, 0, GL_TEXTURE_HEIGHT, &height)); + GLDEBUG(glGetTexLevelParameteriv(GL_TEXTURE_2D, 0, GL_TEXTURE_INTERNAL_FORMAT, &internal_format)); + + + GLuint lod_level = 0; + GLint compressed_size = 0; + GLenum err = GL_NO_ERROR; + while(err == GL_NO_ERROR) { + glGetTexLevelParameteriv(GL_TEXTURE_2D, lod_level, GL_TEXTURE_COMPRESSED_IMAGE_SIZE, &compressed_size); + err = glGetError(); + if(err == GL_NO_ERROR) { + KRDataBlock *new_block = new KRDataBlock(); + new_block->expand(compressed_size); + new_block->lock(); + GLDEBUG(glGetCompressedTexImage(GL_TEXTURE_2D, lod_level, new_block->getStart())); + new_block->unlock(); + blocks.push_back(new_block); + + lod_level++; + } + } + if(err != GL_INVALID_VALUE) { + // err will equal GL_INVALID_VALUE when + // assert(false); // Unexpected error + } + + + GLDEBUG(glBindTexture(GL_TEXTURE_2D, 0)); + getContext().getTextureManager()->selectTexture(0, NULL); + GLDEBUG(glDeleteTextures(1, &compressed_handle)); + + KRTextureKTX *new_texture = new KRTextureKTX(getContext(), getName(), internal_format, base_internal_format, width, height, blocks); + + KRResource *test_resource = new_texture; + + m_pData->unlock(); + + for(auto block_itr = blocks.begin(); block_itr != blocks.end(); block_itr++) { + KRDataBlock *block = *block_itr; + delete block; + } + + return new_texture; +} + long KRTextureTGA::getMemRequiredForSize(int max_dim) { return m_imageSize; diff --git a/KREngine/kraken/KRTextureTGA.h b/KREngine/kraken/KRTextureTGA.h index 82826d7..34edb9d 100644 --- a/KREngine/kraken/KRTextureTGA.h +++ b/KREngine/kraken/KRTextureTGA.h @@ -18,7 +18,8 @@ public: virtual ~KRTextureTGA(); virtual std::string getExtension(); - bool uploadTexture(GLenum target, int lod_max_dim, int ¤t_lod_max_dim, int prev_lod_max_dim); + bool uploadTexture(GLenum target, int lod_max_dim, int ¤t_lod_max_dim, int prev_lod_max_dim, bool compress = false); + virtual KRTexture *compress(); virtual long getMemRequiredForSize(int max_dim); private: From f6da62221f10ad068697aeec0e78076e35e618f8 Mon Sep 17 00:00:00 2001 From: Kearwood Gilbert Date: Sat, 4 Jan 2014 03:36:56 -0800 Subject: [PATCH 14/84] KTX Texture compression fixed for opaque textures without an alpha channel. KTX Texture mipmaps now loading correctly. --HG-- branch : nfb --- KREngine/kraken/KRMeshStreamer.mm | 2 +- KREngine/kraken/KRTextureKTX.cpp | 5 +- KREngine/kraken/KRTextureStreamer.mm | 2 +- KREngine/kraken/KRTextureTGA.cpp | 18 +++++- .../Shaders/ObjectShader_osx.fsh | 27 +++++++-- .../Shaders/ObjectShader_osx.vsh | 60 +++++++++++-------- 6 files changed, 80 insertions(+), 34 deletions(-) diff --git a/KREngine/kraken/KRMeshStreamer.mm b/KREngine/kraken/KRMeshStreamer.mm index 704a646..82f101c 100644 --- a/KREngine/kraken/KRMeshStreamer.mm +++ b/KREngine/kraken/KRMeshStreamer.mm @@ -42,7 +42,7 @@ void KRMeshStreamer::startStreamer() #elif TARGET_OS_MAC NSOpenGLPixelFormatAttribute pixelFormatAttributes[] = { - NSOpenGLPFAOpenGLProfile, NSOpenGLProfileVersionLegacy, +// NSOpenGLPFAOpenGLProfile, NSOpenGLProfileVersionLegacy, 0 }; NSOpenGLPixelFormat *pixelFormat = [[[NSOpenGLPixelFormat alloc] initWithAttributes:pixelFormatAttributes] autorelease]; diff --git a/KREngine/kraken/KRTextureKTX.cpp b/KREngine/kraken/KRTextureKTX.cpp index 2270bfd..029e12a 100644 --- a/KREngine/kraken/KRTextureKTX.cpp +++ b/KREngine/kraken/KRTextureKTX.cpp @@ -59,9 +59,10 @@ KRTextureKTX::KRTextureKTX(KRContext &context, KRDataBlock *data, std::string na uint32_t blockStart = sizeof(KTXHeader) + m_header.bytesOfKeyValueData; uint32_t width = m_header.pixelWidth, height = m_header.pixelHeight; - for(int mipmap_level=0; mipmap_level < KRMIN(m_header.numberOfMipmapLevels, 1); mipmap_level++) { + for(int mipmap_level=0; mipmap_level < KRMAX(m_header.numberOfMipmapLevels, 1); mipmap_level++) { uint32_t blockLength; data->copy(&blockLength, blockStart, 4); + blockStart += 4; m_blocks.push_back(m_pData->getSubBlock(blockStart, blockLength)); @@ -84,7 +85,7 @@ KRTextureKTX::KRTextureKTX(KRContext &context, KRDataBlock *data, std::string na KRTextureKTX::KRTextureKTX(KRContext &context, std::string name, GLenum internal_format, GLenum base_internal_format, int width, int height, const std::list &blocks) : KRTexture2D(context, new KRDataBlock(), name) { - memcpy(_KTXFileIdentifier, m_header.identifier, 12); + memcpy(m_header.identifier, _KTXFileIdentifier, 12); m_header.endianness = 0x04030201; m_header.glType = 0; m_header.glTypeSize = 1; diff --git a/KREngine/kraken/KRTextureStreamer.mm b/KREngine/kraken/KRTextureStreamer.mm index 8731730..4aa1230 100644 --- a/KREngine/kraken/KRTextureStreamer.mm +++ b/KREngine/kraken/KRTextureStreamer.mm @@ -47,7 +47,7 @@ void KRTextureStreamer::startStreamer() NSOpenGLPixelFormatAttribute pixelFormatAttributes[] = { - NSOpenGLPFAOpenGLProfile, NSOpenGLProfileVersionLegacy, +// NSOpenGLPFAOpenGLProfile, NSOpenGLProfileVersionLegacy, 0 }; NSOpenGLPixelFormat *pixelFormat = [[[NSOpenGLPixelFormat alloc] initWithAttributes:pixelFormatAttributes] autorelease]; diff --git a/KREngine/kraken/KRTextureTGA.cpp b/KREngine/kraken/KRTextureTGA.cpp index a94bc58..3451117 100644 --- a/KREngine/kraken/KRTextureTGA.cpp +++ b/KREngine/kraken/KRTextureTGA.cpp @@ -114,7 +114,7 @@ bool KRTextureTGA::uploadTexture(GLenum target, int lod_max_dim, int ¤t_lo pSource += 3; } //#endif - glTexImage2D(target, 0, internal_format, pHeader->width, pHeader->height, 0, GL_BGR, GL_UNSIGNED_BYTE, (GLvoid *)converted_image); + glTexImage2D(target, 0, internal_format, pHeader->width, pHeader->height, 0, GL_BGRA, GL_UNSIGNED_BYTE, (GLvoid *)converted_image); free(converted_image); err = glGetError(); if (err != GL_NO_ERROR) { @@ -168,10 +168,24 @@ KRTexture *KRTextureTGA::compress() } GLDEBUG(glGenerateMipmap(GL_TEXTURE_2D)); - GLint width = 0, height = 0, internal_format, base_internal_format = GL_BGRA; + GLint width = 0, height = 0, internal_format, base_internal_format; + GLDEBUG(glGetTexLevelParameteriv(GL_TEXTURE_2D, 0, GL_TEXTURE_WIDTH, &width)); GLDEBUG(glGetTexLevelParameteriv(GL_TEXTURE_2D, 0, GL_TEXTURE_HEIGHT, &height)); GLDEBUG(glGetTexLevelParameteriv(GL_TEXTURE_2D, 0, GL_TEXTURE_INTERNAL_FORMAT, &internal_format)); + + switch(internal_format) + { + case GL_COMPRESSED_RGB_S3TC_DXT1_EXT: + base_internal_format = GL_BGRA; + break; + case GL_COMPRESSED_RGBA_S3TC_DXT5_EXT: + base_internal_format = GL_BGRA; + break; + default: + assert(false); // Not yet supported + break; + } GLuint lod_level = 0; diff --git a/KREngine/kraken_standard_assets_osx/Shaders/ObjectShader_osx.fsh b/KREngine/kraken_standard_assets_osx/Shaders/ObjectShader_osx.fsh index 25972be..7160dbb 100644 --- a/KREngine/kraken_standard_assets_osx/Shaders/ObjectShader_osx.fsh +++ b/KREngine/kraken_standard_assets_osx/Shaders/ObjectShader_osx.fsh @@ -25,11 +25,11 @@ // or implied, of Kearwood Gilbert. // -// #extension GL_EXT_shadow_samplers : require +//#extension GL_EXT_shadow_samplers : require #if ENABLE_RIM_COLOR == 1 -uniform lowp vec3 rim_color; -uniform mediump float rim_power; + uniform lowp vec3 rim_color; + uniform mediump float rim_power; #endif #if FOG_TYPE > 0 @@ -116,18 +116,27 @@ uniform mediump float rim_power; uniform sampler2D reflectionTexture; #endif + #if ENABLE_RIM_COLOR == 1 + #define NEED_EYEVEC + #endif + #if HAS_REFLECTION_CUBE_MAP == 1 uniform lowp vec3 material_reflection; uniform samplerCube reflectionCubeTexture; #if HAS_NORMAL_MAP == 1 varying highp mat3 tangent_to_world_matrix; - varying mediump vec3 eyeVec; + #define NEED_EYEVEC + uniform highp mat4 model_matrix; #else varying mediump vec3 reflectionVec; #endif #endif + #ifdef NEED_EYEVEC + varying mediump vec3 eyeVec; + #endif + #if SHADOW_QUALITY >= 1 #ifdef GL_EXT_shadow_samplers @@ -398,4 +407,14 @@ void main() #endif + + #if ENABLE_RIM_COLOR == 1 + lowp float rim = 1.0 - clamp(dot(normalize(eyeVec), normal), 0.0, 1.0); + + gl_FragColor += vec4(rim_color, 1.0) * pow(rim, rim_power); + #endif + + #if BONE_COUNT > 0 + gl_FragColor.b = 1.0; + #endif } diff --git a/KREngine/kraken_standard_assets_osx/Shaders/ObjectShader_osx.vsh b/KREngine/kraken_standard_assets_osx/Shaders/ObjectShader_osx.vsh index 4aaad3b..904a3a4 100644 --- a/KREngine/kraken_standard_assets_osx/Shaders/ObjectShader_osx.vsh +++ b/KREngine/kraken_standard_assets_osx/Shaders/ObjectShader_osx.vsh @@ -29,13 +29,17 @@ // or implied, of Kearwood Gilbert. // -attribute highp vec3 vertex_position, vertex_normal, vertex_tangent; + +attribute highp vec3 vertex_position, vertex_normal; +#if HAS_NORMAL_MAP == 1 + attribute highp vec3 vertex_tangent; +#endif attribute mediump vec2 vertex_uv; uniform highp mat4 mvp_matrix; // mvp_matrix is the result of multiplying the model, view, and projection matrices #if BONE_COUNT > 0 attribute highp vec4 bone_weights; - attribute mediump vec4 bone_indexes; + attribute highp vec4 bone_indexes; uniform highp mat4 bone_transforms[BONE_COUNT]; #else #define vertex_position_skinned vertex_position @@ -134,11 +138,14 @@ uniform highp mat4 mvp_matrix; // mvp_matrix is the result of multiplying t varying mediump float specularFactor; #endif + #if ENABLE_RIM_COLOR == 1 + #define NEED_EYEVEC + #endif #if HAS_REFLECTION_CUBE_MAP == 1 #if HAS_NORMAL_MAP == 1 + #define NEED_EYEVEC uniform highp mat4 model_inverse_transpose_matrix; - varying mediump vec3 eyeVec; varying highp mat3 tangent_to_world_matrix; #else uniform highp mat4 model_matrix; @@ -146,6 +153,10 @@ uniform highp mat4 mvp_matrix; // mvp_matrix is the result of multiplying t #endif #endif + #ifdef NEED_EYEVEC + varying mediump vec3 eyeVec; + #endif + #if HAS_DIFFUSE_MAP_SCALE == 1 uniform highp vec2 diffuseTexture_Scale; #endif @@ -168,30 +179,28 @@ void main() mediump vec4 scaled_bone_indexes = bone_indexes; mediump vec4 scaled_bone_weights = bone_weights; - // scaled_bone_indexes = vec4(1.0, 0.0, 0.0, 0.0); - // scaled_bone_weights = vec4(1.0, 0.0, 0.0, 0.0); - highp vec3 vertex_position_skinned = - ((bone_transforms[ int(scaled_bone_indexes.x) ] * vec4(vertex_position, 1.0)).xyz * scaled_bone_weights.x) + - ((bone_transforms[ int(scaled_bone_indexes.y) ] * vec4(vertex_position, 1.0)).xyz * scaled_bone_weights.y) + - ((bone_transforms[ int(scaled_bone_indexes.z) ] * vec4(vertex_position, 1.0)).xyz * scaled_bone_weights.z) + - ((bone_transforms[ int(scaled_bone_indexes.w) ] * vec4(vertex_position, 1.0)).xyz * scaled_bone_weights.w); - - highp vec3 vertex_normal_skinned = normalize( - ((bone_transforms[ int(scaled_bone_indexes.x) ] * vec4(vertex_normal, 1.0)).xyz * scaled_bone_weights.x) + - ((bone_transforms[ int(scaled_bone_indexes.y) ] * vec4(vertex_normal, 1.0)).xyz * scaled_bone_weights.y) + - ((bone_transforms[ int(scaled_bone_indexes.z) ] * vec4(vertex_normal, 1.0)).xyz * scaled_bone_weights.z) + - ((bone_transforms[ int(scaled_bone_indexes.w) ] * vec4(vertex_normal, 1.0)).xyz * scaled_bone_weights.w)); - - highp vec3 vertex_tangent_skinned = normalize( - ((bone_transforms[ int(scaled_bone_indexes.x) ] * vec4(vertex_tangent, 1.0)).xyz * scaled_bone_weights.x) + - ((bone_transforms[ int(scaled_bone_indexes.y) ] * vec4(vertex_tangent, 1.0)).xyz * scaled_bone_weights.y) + - ((bone_transforms[ int(scaled_bone_indexes.z) ] * vec4(vertex_tangent, 1.0)).xyz * scaled_bone_weights.z) + - ((bone_transforms[ int(scaled_bone_indexes.w) ] * vec4(vertex_tangent, 1.0)).xyz * scaled_bone_weights.w)); -#endif + //scaled_bone_indexes = vec4(0.0, 0.0, 0.0, 0.0); + //scaled_bone_weights = vec4(1.0, 0.0, 0.0, 0.0); + highp mat4 skin_matrix = + bone_transforms[ int(scaled_bone_indexes.x) ] * scaled_bone_weights.x + + bone_transforms[ int(scaled_bone_indexes.y) ] * scaled_bone_weights.y + + bone_transforms[ int(scaled_bone_indexes.z) ] * scaled_bone_weights.z + + bone_transforms[ int(scaled_bone_indexes.w) ] * scaled_bone_weights.w; + //skin_matrix = bone_transforms[0]; + highp vec3 vertex_position_skinned = (skin_matrix * vec4(vertex_position, 1)).xyz; + + highp vec3 vertex_normal_skinned = normalize(mat3(skin_matrix) * vertex_normal); + #if HAS_NORMAL_MAP == 1 + highp vec3 vertex_tangent_skinned = normalize(mat3(skin_matrix) * vertex_tangent); + #endif + +#endif // Transform position gl_Position = mvp_matrix * vec4(vertex_position_skinned,1.0); + + #if HAS_DIFFUSE_MAP == 1 || (HAS_NORMAL_MAP == 1 && ENABLE_PER_PIXEL == 1) || (HAS_SPEC_MAP == 1 && ENABLE_PER_PIXEL == 1) || (HAS_REFLECTION_MAP == 1 && ENABLE_PER_PIXEL == 1) // Pass UV co-ordinates @@ -244,7 +253,7 @@ void main() #if HAS_REFLECTION_CUBE_MAP == 1 #if HAS_NORMAL_MAP == 1 - eyeVec = normalize(camera_position_model_space - vertex_position_skinned); + #else // Calculate reflection vector as I - 2.0 * dot(N, I) * N mediump vec3 eyeVec = normalize(camera_position_model_space - vertex_position_skinned); @@ -253,6 +262,9 @@ void main() #endif #endif + #ifdef NEED_EYEVEC + eyeVec = normalize(camera_position_model_space - vertex_position_skinned); + #endif #if HAS_LIGHT_MAP == 1 // Pass shadow UV co-ordinates From 656a9cadb621c9fde39db5acf03d8ff2bf8c858a Mon Sep 17 00:00:00 2001 From: Kearwood Gilbert Date: Sat, 4 Jan 2014 19:00:44 -0800 Subject: [PATCH 15/84] OSX Build memory parameter tuning Corrected bug in KTX textures that would cause a compilation failure on iOS builds. --HG-- branch : nfb --- KREngine/kraken/KREngine-common.h | 1 + KREngine/kraken/KREngine.mm | 6 +++--- KREngine/kraken/KRTextureKTX.cpp | 4 ++-- 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/KREngine/kraken/KREngine-common.h b/KREngine/kraken/KREngine-common.h index 1b60047..5dcf36b 100644 --- a/KREngine/kraken/KREngine-common.h +++ b/KREngine/kraken/KREngine-common.h @@ -103,6 +103,7 @@ using std::hash; #define glBeginQueryEXT glBeginQuery #define glEndQueryEXT glEndQuery #define glGetQueryObjectuivEXT glGetQueryObjectuiv +#define glTexStorage2DEXT glTexStorage2D #define GL_ANY_SAMPLES_PASSED_EXT GL_ANY_SAMPLES_PASSED #define GL_QUERY_RESULT_EXT GL_QUERY_RESULT diff --git a/KREngine/kraken/KREngine.mm b/KREngine/kraken/KREngine.mm index e1c8eb6..991fba0 100644 --- a/KREngine/kraken/KREngine.mm +++ b/KREngine/kraken/KREngine.mm @@ -134,9 +134,9 @@ void kraken::set_debug_text(const std::string &print_text) KRContext::KRENGINE_MAX_VBO_MEM = 256000000; KRContext::KRENGINE_MAX_SHADER_HANDLES = 4000; KRContext::KRENGINE_MAX_TEXTURE_HANDLES = 10000; - KRContext::KRENGINE_MAX_TEXTURE_MEM = 512000000; - KRContext::KRENGINE_TARGET_TEXTURE_MEM_MAX = 384000000; - KRContext::KRENGINE_MAX_TEXTURE_DIM = 2048; + KRContext::KRENGINE_MAX_TEXTURE_MEM = 256000000; + KRContext::KRENGINE_TARGET_TEXTURE_MEM_MAX = 192000000; + KRContext::KRENGINE_MAX_TEXTURE_DIM = 8192; KRContext::KRENGINE_MIN_TEXTURE_DIM = 64; KRContext::KRENGINE_MAX_TEXTURE_THROUGHPUT = 128000000; #endif diff --git a/KREngine/kraken/KRTextureKTX.cpp b/KREngine/kraken/KRTextureKTX.cpp index 029e12a..0550554 100644 --- a/KREngine/kraken/KRTextureKTX.cpp +++ b/KREngine/kraken/KRTextureKTX.cpp @@ -195,8 +195,8 @@ bool KRTextureKTX::uploadTexture(GLenum target, int lod_max_dim, int ¤t_lo height = 1; } } - width = m_iWidth; - height = m_iHeight; + width = m_header.pixelWidth; + height = m_header.pixelHeight; if(target == GL_TEXTURE_CUBE_MAP_POSITIVE_X) { glTexStorage2DEXT(GL_TEXTURE_CUBE_MAP, level_count, (GLenum)m_header.glInternalFormat, max_lod_width, max_lod_height); From 63a3fa2d9e88d995d72bf6542c2915df949d7f95 Mon Sep 17 00:00:00 2001 From: Kearwood Gilbert Date: Sat, 4 Jan 2014 20:19:06 -0800 Subject: [PATCH 16/84] Organized directory structure for 3rd party source and libraries. Added pvrtexlib library and headers Added Recast --HG-- branch : nfb --- .../{kraken => 3rdparty/forsyth}/forsyth.cpp | 0 .../{kraken => 3rdparty/forsyth}/forsyth.h | 0 .../3rdparty/pvrtexlib/include/PVRTArray.h | 565 +++++++ .../pvrtexlib/include/PVRTDecompress.h | 58 + .../3rdparty/pvrtexlib/include/PVRTError.h | 71 + .../3rdparty/pvrtexlib/include/PVRTGlobal.h | 296 ++++ KREngine/3rdparty/pvrtexlib/include/PVRTMap.h | 225 +++ .../3rdparty/pvrtexlib/include/PVRTString.h | 982 +++++++++++ .../3rdparty/pvrtexlib/include/PVRTTexture.h | 700 ++++++++ .../3rdparty/pvrtexlib/include/PVRTexture.h | 208 +++ .../pvrtexlib/include/PVRTextureDefines.h | 98 ++ .../pvrtexlib/include/PVRTextureFormat.h | 73 + .../pvrtexlib/include/PVRTextureHeader.h | 586 +++++++ .../pvrtexlib/include/PVRTextureUtilities.h | 157 ++ .../pvrtexlib/include/PVRTextureVersion.h | 7 + .../pvrtexlib/static_osx/libPVRTexLib.a | Bin 0 -> 2265832 bytes KREngine/3rdparty/recast/include/Recast.h | 1135 +++++++++++++ .../3rdparty/recast/include/RecastAlloc.h | 124 ++ .../3rdparty/recast/include/RecastAssert.h | 33 + KREngine/3rdparty/recast/source/Recast.cpp | 489 ++++++ .../3rdparty/recast/source/RecastAlloc.cpp | 88 + .../3rdparty/recast/source/RecastArea.cpp | 602 +++++++ .../3rdparty/recast/source/RecastContour.cpp | 851 ++++++++++ .../3rdparty/recast/source/RecastFilter.cpp | 207 +++ .../3rdparty/recast/source/RecastLayers.cpp | 620 +++++++ .../3rdparty/recast/source/RecastMesh.cpp | 1433 +++++++++++++++++ .../recast/source/RecastMeshDetail.cpp | 1245 ++++++++++++++ .../recast/source/RecastRasterization.cpp | 387 +++++ .../3rdparty/recast/source/RecastRegion.cpp | 1337 +++++++++++++++ .../tinyxml2}/tinyxml2.cpp | 0 .../{kraken => 3rdparty/tinyxml2}/tinyxml2.h | 0 .../tinyxml2}/tinyxml2_readme.txt | 0 KREngine/Kraken.xcodeproj/project.pbxproj | 284 +++- 33 files changed, 12809 insertions(+), 52 deletions(-) rename KREngine/{kraken => 3rdparty/forsyth}/forsyth.cpp (100%) rename KREngine/{kraken => 3rdparty/forsyth}/forsyth.h (100%) create mode 100644 KREngine/3rdparty/pvrtexlib/include/PVRTArray.h create mode 100644 KREngine/3rdparty/pvrtexlib/include/PVRTDecompress.h create mode 100644 KREngine/3rdparty/pvrtexlib/include/PVRTError.h create mode 100644 KREngine/3rdparty/pvrtexlib/include/PVRTGlobal.h create mode 100644 KREngine/3rdparty/pvrtexlib/include/PVRTMap.h create mode 100644 KREngine/3rdparty/pvrtexlib/include/PVRTString.h create mode 100644 KREngine/3rdparty/pvrtexlib/include/PVRTTexture.h create mode 100644 KREngine/3rdparty/pvrtexlib/include/PVRTexture.h create mode 100644 KREngine/3rdparty/pvrtexlib/include/PVRTextureDefines.h create mode 100644 KREngine/3rdparty/pvrtexlib/include/PVRTextureFormat.h create mode 100644 KREngine/3rdparty/pvrtexlib/include/PVRTextureHeader.h create mode 100644 KREngine/3rdparty/pvrtexlib/include/PVRTextureUtilities.h create mode 100644 KREngine/3rdparty/pvrtexlib/include/PVRTextureVersion.h create mode 100644 KREngine/3rdparty/pvrtexlib/static_osx/libPVRTexLib.a create mode 100755 KREngine/3rdparty/recast/include/Recast.h create mode 100755 KREngine/3rdparty/recast/include/RecastAlloc.h create mode 100755 KREngine/3rdparty/recast/include/RecastAssert.h create mode 100755 KREngine/3rdparty/recast/source/Recast.cpp create mode 100755 KREngine/3rdparty/recast/source/RecastAlloc.cpp create mode 100755 KREngine/3rdparty/recast/source/RecastArea.cpp create mode 100755 KREngine/3rdparty/recast/source/RecastContour.cpp create mode 100755 KREngine/3rdparty/recast/source/RecastFilter.cpp create mode 100755 KREngine/3rdparty/recast/source/RecastLayers.cpp create mode 100755 KREngine/3rdparty/recast/source/RecastMesh.cpp create mode 100755 KREngine/3rdparty/recast/source/RecastMeshDetail.cpp create mode 100755 KREngine/3rdparty/recast/source/RecastRasterization.cpp create mode 100755 KREngine/3rdparty/recast/source/RecastRegion.cpp rename KREngine/{kraken => 3rdparty/tinyxml2}/tinyxml2.cpp (100%) rename KREngine/{kraken => 3rdparty/tinyxml2}/tinyxml2.h (100%) rename KREngine/{kraken => 3rdparty/tinyxml2}/tinyxml2_readme.txt (100%) diff --git a/KREngine/kraken/forsyth.cpp b/KREngine/3rdparty/forsyth/forsyth.cpp similarity index 100% rename from KREngine/kraken/forsyth.cpp rename to KREngine/3rdparty/forsyth/forsyth.cpp diff --git a/KREngine/kraken/forsyth.h b/KREngine/3rdparty/forsyth/forsyth.h similarity index 100% rename from KREngine/kraken/forsyth.h rename to KREngine/3rdparty/forsyth/forsyth.h diff --git a/KREngine/3rdparty/pvrtexlib/include/PVRTArray.h b/KREngine/3rdparty/pvrtexlib/include/PVRTArray.h new file mode 100644 index 0000000..ad58f87 --- /dev/null +++ b/KREngine/3rdparty/pvrtexlib/include/PVRTArray.h @@ -0,0 +1,565 @@ +/****************************************************************************** + + @File PVRTArray.h + + @Title PVRTArray + + @Version + + @Copyright Copyright (c) Imagination Technologies Limited. + + @Platform ANSI compatible + + @Description Expanding array template class. Allows appending and direct + access. Mixing access methods should be approached with caution. + +******************************************************************************/ +#ifndef __PVRTARRAY_H__ +#define __PVRTARRAY_H__ + +#include "PVRTGlobal.h" +#include "PVRTError.h" + +/*!**************************************************************************** +Class +******************************************************************************/ + +/*!*************************************************************************** +* @Class CPVRTArray +* @Brief Expanding array template class. +* @Description Expanding array template class. +*****************************************************************************/ +template +class CPVRTArray +{ +public: + /*!*************************************************************************** + @Function CPVRTArray + @Description Blank constructor. Makes a default sized array. + *****************************************************************************/ + CPVRTArray() : m_uiSize(0), m_uiCapacity(GetDefaultSize()) + { + m_pArray = new T[m_uiCapacity]; + } + + /*!*************************************************************************** + @Function CPVRTArray + @Input uiSize intial size of array + @Description Constructor taking initial size of array in elements. + *****************************************************************************/ + CPVRTArray(const unsigned int uiSize) : m_uiSize(0), m_uiCapacity(uiSize) + { + _ASSERT(uiSize != 0); + m_pArray = new T[uiSize]; + } + + /*!*************************************************************************** + @Function CPVRTArray + @Input original the other dynamic array + @Description Copy constructor. + *****************************************************************************/ + CPVRTArray(const CPVRTArray& original) : m_uiSize(original.m_uiSize), + m_uiCapacity(original.m_uiCapacity) + { + m_pArray = new T[m_uiCapacity]; + for(unsigned int i=0;i= m_uiSize) // Are we adding to the end + uiIndex = Append(addT); + else + { + unsigned int uiNewCapacity = 0; + T* pArray = m_pArray; + + if(m_uiSize > m_uiCapacity) + { + uiNewCapacity = m_uiCapacity + 10; // Expand the array by 10. + + pArray = new T[uiNewCapacity]; // New Array + + if(!pArray) + return -1; // Failed to allocate memory! + + // Copy the first half to the new array + for(unsigned int i = 0; i < pos; ++i) + { + pArray[i] = m_pArray[i]; + } + } + + // Copy last half to the new array + for(unsigned int i = m_uiSize; i > pos; --i) + { + pArray[i] = m_pArray[i - 1]; + } + + // Insert our new element + pArray[pos] = addT; + uiIndex = pos; + + // Increase our size + ++m_uiSize; + + // Switch pointers and free memory if needed + if(pArray != m_pArray) + { + m_uiCapacity = uiNewCapacity; + delete[] m_pArray; + m_pArray = pArray; + } + } + + return uiIndex; + } + + /*!*************************************************************************** + @Function Append + @Input addT The element to append + @Return The index of the new item. + @Description Appends an element to the end of the array, expanding it + if necessary. + *****************************************************************************/ + unsigned int Append(const T& addT) + { + unsigned int uiIndex = Append(); + m_pArray[uiIndex] = addT; + return uiIndex; + } + + /*!*************************************************************************** + @Function Append + @Return The index of the new item. + @Description Creates space for a new item, but doesn't add. Instead + returns the index of the new item. + *****************************************************************************/ + unsigned int Append() + { + unsigned int uiIndex = m_uiSize; + SetCapacity(m_uiSize+1); + m_uiSize++; + + return uiIndex; + } + + /*!*************************************************************************** + @Function Clear + @Description Clears the array. + *****************************************************************************/ + void Clear() + { + m_uiSize = 0U; + } + + /*!*************************************************************************** + @Function Resize + @Input uiSize New size of array + @Description Changes the array to the new size + *****************************************************************************/ + EPVRTError Resize(const unsigned int uiSize) + { + EPVRTError err = SetCapacity(uiSize); + + if(err != PVR_SUCCESS) + return err; + + m_uiSize = uiSize; + return PVR_SUCCESS; + } + + /*!*************************************************************************** + @Function SetCapacity + @Input uiSize New capacity of array + @Description Expands array to new capacity + *****************************************************************************/ + EPVRTError SetCapacity(const unsigned int uiSize) + { + if(uiSize <= m_uiCapacity) + return PVR_SUCCESS; // nothing to be done + + unsigned int uiNewCapacity; + if(uiSize < m_uiCapacity*2) + { + uiNewCapacity = m_uiCapacity*2; // Ignore the new size. Expand to twice the previous size. + } + else + { + uiNewCapacity = uiSize; + } + + T* pNewArray = new T[uiNewCapacity]; // New Array + if(!pNewArray) + return PVR_FAIL; // Failed to allocate memory! + + // Copy source data to new array + for(unsigned int i = 0; i < m_uiSize; ++i) + { + pNewArray[i] = m_pArray[i]; + } + + // Switch pointers and free memory + m_uiCapacity = uiNewCapacity; + T* pOldArray = m_pArray; + m_pArray = pNewArray; + delete [] pOldArray; + return PVR_SUCCESS; + } + + /*!*************************************************************************** + @Function Copy + @Input other The CPVRTArray needing copied + @Description A copy function. Will attempt to copy from other CPVRTArrays + if this is possible. + *****************************************************************************/ + template + void Copy(const CPVRTArray& other) + { + T* pNewArray = new T[other.GetCapacity()]; + if(pNewArray) + { + // Copy data + for(unsigned int i = 0; i < other.GetSize(); i++) + { + pNewArray[i] = other[i]; + } + + // Free current array + if(m_pArray) + delete [] m_pArray; + + // Swap pointers + m_pArray = pNewArray; + + m_uiCapacity = other.GetCapacity(); + m_uiSize = other.GetSize(); + } + } + + /*!*************************************************************************** + @Function = + @Input other The CPVRTArray needing copied + @Description assignment operator. + *****************************************************************************/ + CPVRTArray& operator=(const CPVRTArray& other) + { + if(&other != this) + Copy(other); + + return *this; + } + + /*!*************************************************************************** + @Function operator+= + @Input other the array to append. + @Description appends an existing CPVRTArray on to this one. + *****************************************************************************/ + CPVRTArray& operator+=(const CPVRTArray& other) + { + if(&other != this) + { + for(unsigned int uiIndex = 0; uiIndex < other.GetSize(); ++uiIndex) + { + Append(other[uiIndex]); + } + } + + return *this; + } + + /*!*************************************************************************** + @Function [] + @Input uiIndex index of element in array + @Return the element indexed + @Description indexed access into array. Note that this has no error + checking whatsoever + *****************************************************************************/ + T& operator[](const unsigned int uiIndex) + { + _ASSERT(uiIndex < m_uiCapacity); + return m_pArray[uiIndex]; + } + + /*!*************************************************************************** + @Function [] + @Input uiIndex index of element in array + @Return The element indexed + @Description Indexed access into array. Note that this has no error + checking whatsoever + *****************************************************************************/ + const T& operator[](const unsigned int uiIndex) const + { + _ASSERT(uiIndex < m_uiCapacity); + return m_pArray[uiIndex]; + } + + /*!*************************************************************************** + @Function GetSize + @Return Size of array + @Description Gives current size of array/number of elements + *****************************************************************************/ + unsigned int GetSize() const + { + return m_uiSize; + } + + /*!*************************************************************************** + @Function GetDefaultSize + @Return Default size of array + @Description Gives the default size of array/number of elements + *****************************************************************************/ + static unsigned int GetDefaultSize() + { + return 16U; + } + + /*!*************************************************************************** + @Function GetCapacity + @Return Capacity of array + @Description Gives current allocated size of array/number of elements + *****************************************************************************/ + unsigned int GetCapacity() const + { + return m_uiCapacity; + } + + /*!*************************************************************************** + @Function Contains + @Input object The object to check in the array + @Return true if object is contained in this array. + @Description Indicates whether the given object resides inside the + array. + *****************************************************************************/ + bool Contains(const T& object) const + { + for(unsigned int uiIndex = 0; uiIndex < m_uiSize; ++uiIndex) + { + if(m_pArray[uiIndex] == object) + return true; + } + return false; + } + + /*!*************************************************************************** + @Function Find + @Input object The object to check in the array + @Return pointer to the found object or NULL. + @Description Attempts to find the object in the array and returns a + pointer if it is found, or NULL if not found. The time + taken is O(N). + *****************************************************************************/ + T* Find(const T& object) const + { + for(unsigned int uiIndex = 0; uiIndex < m_uiSize; ++uiIndex) + { + if(m_pArray[uiIndex] == object) + return &m_pArray[uiIndex]; + } + return NULL; + } + + /*!*************************************************************************** + @Function Sort + @Input predicate The object which defines "bool operator()" + @Description Simple bubble-sort of the array. Pred should be an object that + defines a bool operator(). + *****************************************************************************/ + template + void Sort(Pred predicate) + { + bool bSwap; + for(unsigned int i=0; i < m_uiSize; ++i) + { + bSwap = false; + for(unsigned int j=0; j < m_uiSize-1; ++j) + { + if(predicate(m_pArray[j], m_pArray[j+1])) + { + PVRTswap(m_pArray[j], m_pArray[j+1]); + bSwap = true; + } + } + + if(!bSwap) + return; + } + } + + /*!*************************************************************************** + @Function Remove + @Input uiIndex The index to remove + @Return success or failure + @Description Removes an element from the array. + *****************************************************************************/ + virtual EPVRTError Remove(unsigned int uiIndex) + { + _ASSERT(uiIndex < m_uiSize); + if(m_uiSize == 0) + return PVR_FAIL; + + if(uiIndex == m_uiSize-1) + { + return RemoveLast(); + } + + m_uiSize--; + // Copy the data. memmove will only work for built-in types. + for(unsigned int uiNewIdx = uiIndex; uiNewIdx < m_uiSize; ++uiNewIdx) + { + m_pArray[uiNewIdx] = m_pArray[uiNewIdx+1]; + } + + return PVR_SUCCESS; + } + + /*!*************************************************************************** + @Function RemoveLast + @Return success or failure + @Description Removes the last element. Simply decrements the size value + *****************************************************************************/ + virtual EPVRTError RemoveLast() + { + if(m_uiSize > 0) + { + m_uiSize--; + return PVR_SUCCESS; + } + else + { + return PVR_FAIL; + } + } + +protected: + unsigned int m_uiSize; /*! current size of contents of array */ + unsigned int m_uiCapacity; /*! currently allocated size of array */ + T *m_pArray; /*! the actual array itself */ +}; + +// note "this" is required for ISO standard C++ and gcc complains otherwise +// http://lists.apple.com/archives/Xcode-users//2005/Dec/msg00644.html +template +class CPVRTArrayManagedPointers : public CPVRTArray +{ +public: + virtual ~CPVRTArrayManagedPointers() + { + if(this->m_pArray) + { + for(unsigned int i=0;im_uiSize;i++) + { + delete(this->m_pArray[i]); + } + } + } + + /*!*************************************************************************** + @Function Remove + @Input uiIndex The index to remove + @Return success or failure + @Description Removes an element from the array. + *****************************************************************************/ + virtual EPVRTError Remove(unsigned int uiIndex) + { + _ASSERT(uiIndex < this->m_uiSize); + if(this->m_uiSize == 0) + return PVR_FAIL; + + if(uiIndex == this->m_uiSize-1) + { + return this->RemoveLast(); + } + + unsigned int uiSize = (this->m_uiSize - (uiIndex+1)) * sizeof(T*); + + delete this->m_pArray[uiIndex]; + memmove(this->m_pArray + uiIndex, this->m_pArray + (uiIndex+1), uiSize); + + this->m_uiSize--; + return PVR_SUCCESS; + } + + /*!*************************************************************************** + @Function RemoveLast + @Return success or failure + @Description Removes the last element. Simply decrements the size value + *****************************************************************************/ + virtual EPVRTError RemoveLast() + { + if(this->m_uiSize > 0 && this->m_pArray) + { + delete this->m_pArray[this->m_uiSize-1]; + this->m_uiSize--; + return PVR_SUCCESS; + } + else + { + return PVR_FAIL; + } + } +}; + +#endif // __PVRTARRAY_H__ + +/***************************************************************************** +End of file (PVRTArray.h) +*****************************************************************************/ + diff --git a/KREngine/3rdparty/pvrtexlib/include/PVRTDecompress.h b/KREngine/3rdparty/pvrtexlib/include/PVRTDecompress.h new file mode 100644 index 0000000..71a008b --- /dev/null +++ b/KREngine/3rdparty/pvrtexlib/include/PVRTDecompress.h @@ -0,0 +1,58 @@ +/****************************************************************************** + + @File PVRTDecompress.h + + @Title PVRTDecompress + + @Version + + @Copyright Copyright (c) Imagination Technologies Limited. + + @Platform ANSI compatible + + @Description PVRTC and ETC Texture Decompression. + +******************************************************************************/ + +#ifndef _PVRTDECOMPRESS_H_ +#define _PVRTDECOMPRESS_H_ + +/*!*********************************************************************** + @Function PVRTDecompressPVRTC + @Input pCompressedData The PVRTC texture data to decompress + @Input Do2bitMode Signifies whether the data is PVRTC2 or PVRTC4 + @Input XDim X dimension of the texture + @Input YDim Y dimension of the texture + @Return Returns the amount of data that was decompressed. + @Modified pResultImage The decompressed texture data + @Description Decompresses PVRTC to RGBA 8888 +*************************************************************************/ +int PVRTDecompressPVRTC(const void *pCompressedData, + const int Do2bitMode, + const int XDim, + const int YDim, + unsigned char* pResultImage); + +/*!*********************************************************************** +@Function PVRTDecompressETC +@Input pSrcData The ETC texture data to decompress +@Input x X dimension of the texture +@Input y Y dimension of the texture +@Modified pDestData The decompressed texture data +@Input nMode The format of the data +@Returns The number of bytes of ETC data decompressed +@Description Decompresses ETC to RGBA 8888 +*************************************************************************/ +int PVRTDecompressETC(const void * const pSrcData, + const unsigned int &x, + const unsigned int &y, + void *pDestData, + const int &nMode); + + +#endif /* _PVRTDECOMPRESS_H_ */ + +/***************************************************************************** + End of file (PVRTBoneBatch.h) +*****************************************************************************/ + diff --git a/KREngine/3rdparty/pvrtexlib/include/PVRTError.h b/KREngine/3rdparty/pvrtexlib/include/PVRTError.h new file mode 100644 index 0000000..357a1ab --- /dev/null +++ b/KREngine/3rdparty/pvrtexlib/include/PVRTError.h @@ -0,0 +1,71 @@ +/****************************************************************************** + + @File PVRTError.h + + @Title PVRTError + + @Version + + @Copyright Copyright (c) Imagination Technologies Limited. + + @Platform ANSI compatible + + @Description + +******************************************************************************/ +#ifndef _PVRTERROR_H_ +#define _PVRTERROR_H_ + +#if defined(ANDROID) + #include +#else + #if defined(_WIN32) + #include + #else + #include + #endif +#endif +/*!*************************************************************************** + Macros +*****************************************************************************/ + +/*! Outputs a string to the standard error if built for debugging. */ +#if !defined(PVRTERROR_OUTPUT_DEBUG) + #if defined(_DEBUG) || defined(DEBUG) + #if defined(ANDROID) + #define PVRTERROR_OUTPUT_DEBUG(A) __android_log_print(ANDROID_LOG_INFO, "PVRTools", A); + #elif defined(_WIN32) && !defined(UNDER_CE) + #define PVRTERROR_OUTPUT_DEBUG(A) OutputDebugStringA(A); + #else + #define PVRTERROR_OUTPUT_DEBUG(A) fprintf(stderr,A); + #endif + #else + #define PVRTERROR_OUTPUT_DEBUG(A) + #endif +#endif + + +/*!*************************************************************************** + Enums +*****************************************************************************/ +/*! Enum error codes */ +enum EPVRTError +{ + PVR_SUCCESS = 0, + PVR_FAIL = 1, + PVR_OVERFLOW = 2 +}; + +/*!*************************************************************************** + @Function PVRTErrorOutputDebug + @Input format printf style format followed by arguments it requires + @Description Outputs a string to the standard error. +*****************************************************************************/ +void PVRTErrorOutputDebug(char const * const format, ...); + +#endif // _PVRTERROR_H_ + +/***************************************************************************** +End of file (PVRTError.h) +*****************************************************************************/ + diff --git a/KREngine/3rdparty/pvrtexlib/include/PVRTGlobal.h b/KREngine/3rdparty/pvrtexlib/include/PVRTGlobal.h new file mode 100644 index 0000000..b7f64a5 --- /dev/null +++ b/KREngine/3rdparty/pvrtexlib/include/PVRTGlobal.h @@ -0,0 +1,296 @@ +/****************************************************************************** + + @File PVRTGlobal.h + + @Title PVRTGlobal + + @Version + + @Copyright Copyright (c) Imagination Technologies Limited. + + @Platform ANSI compatible + + @Description Global defines and typedefs for PVRTools + +******************************************************************************/ +#ifndef _PVRTGLOBAL_H_ +#define _PVRTGLOBAL_H_ + +/*!*************************************************************************** + Macros +*****************************************************************************/ +#define PVRT_MIN(a,b) (((a) < (b)) ? (a) : (b)) +#define PVRT_MAX(a,b) (((a) > (b)) ? (a) : (b)) +#define PVRT_CLAMP(x, l, h) (PVRT_MIN((h), PVRT_MAX((x), (l)))) + +// avoid warning about unused parameter +#define PVRT_UNREFERENCED_PARAMETER(x) ((void) x) + +#if defined(_WIN32) && !defined(__QT__) && !defined(UNDER_CE) /* Windows desktop */ +#if !defined(_CRTDBG_MAP_ALLOC) + #define _CRTDBG_MAP_ALLOC +#endif + #include + #include + #include +#endif + +#if defined(UNDER_CE) + #include + +#ifndef _ASSERT + #ifdef _DEBUG + #define _ASSERT(X) { (X) ? 0 : DebugBreak(); } + #else + #define _ASSERT(X) + #endif +#endif + +#ifndef _ASSERTE + #ifdef _DEBUG + #define _ASSERTE _ASSERT + #else + #define _ASSERTE(X) + #endif +#endif + #define _RPT0(a,b) + #define _RPT1(a,b,c) + #define _RPT2(a,b,c,d) + #define _RPT3(a,b,c,d,e) + #define _RPT4(a,b,c,d,e,f) +#else + +#if defined(_WIN32) && !defined(__QT__) + +#else +#if defined(__linux__) || defined(__APPLE__) + #define _ASSERT(a)((void)0) + #define _ASSERTE(a)((void)0) + #ifdef _DEBUG + #ifndef _RPT0 + #define _RPT0(a,b) printf(b) + #endif + #ifndef _RPT1 + #define _RPT1(a,b,c) printf(b,c) + #endif + #else + #ifndef _RPT0 + #define _RPT0(a,b)((void)0) + #endif + #ifndef _RPT1 + #define _RPT1(a,b,c)((void)0) + #endif + #endif + #define _RPT2(a,b,c,d)((void)0) + #define _RPT3(a,b,c,d,e)((void)0) + #define _RPT4(a,b,c,d,e,f)((void)0) + #include + #include + #define BYTE unsigned char + #define WORD unsigned short + #define DWORD unsigned int + typedef struct tagRGBQUAD { + BYTE rgbBlue; + BYTE rgbGreen; + BYTE rgbRed; + BYTE rgbReserved; + } RGBQUAD; + #define BOOL int +#if !defined(TRUE) + #define TRUE 1 +#endif +#if !defined(FALSE) + #define FALSE 0 +#endif +#else + #define _CRT_WARN 0 + #define _RPT0(a,b) + #define _RPT1(a,b,c) + #define _RPT2(a,b,c,d) + #define _RPT3(a,b,c,d,e) + #define _RPT4(a,b,c,d,e,f) + #define _ASSERT(X) + #define _ASSERTE(X) +#endif +#endif +#endif + +#include + +#define FREE(X) { if(X) { free(X); (X) = 0; } } + +// This macro is used to check at compile time that types are of a certain size +// If the size does not equal the expected size, this typedefs an array of size 0 +// which causes a compile error +#define PVRTSIZEASSERT(T, size) typedef int (sizeof_##T)[sizeof(T) == (size)] +#define PVRTCOMPILEASSERT(T, expr) typedef int (assert_##T)[expr] + + +/**************************************************************************** +** Integer types +****************************************************************************/ + +typedef char PVRTchar8; +typedef signed char PVRTint8; +typedef signed short PVRTint16; +typedef signed int PVRTint32; +typedef unsigned char PVRTuint8; +typedef unsigned short PVRTuint16; +typedef unsigned int PVRTuint32; + +typedef float PVRTfloat32; + +#if (defined(__int64) || defined(_WIN32)) +typedef signed __int64 PVRTint64; +typedef unsigned __int64 PVRTuint64; +#elif defined(TInt64) +typedef TInt64 PVRTint64; +typedef TUInt64 PVRTuint64; +#else +typedef signed long long PVRTint64; +typedef unsigned long long PVRTuint64; +#endif + +#if __SIZEOF_WCHAR_T__ == 4 || __WCHAR_MAX__ > 0x10000 + #define PVRTSIZEOFWCHAR 4 +#else + #define PVRTSIZEOFWCHAR 2 +#endif + +PVRTSIZEASSERT(PVRTchar8, 1); +PVRTSIZEASSERT(PVRTint8, 1); +PVRTSIZEASSERT(PVRTuint8, 1); +PVRTSIZEASSERT(PVRTint16, 2); +PVRTSIZEASSERT(PVRTuint16, 2); +PVRTSIZEASSERT(PVRTint32, 4); +PVRTSIZEASSERT(PVRTuint32, 4); +PVRTSIZEASSERT(PVRTint64, 8); +PVRTSIZEASSERT(PVRTuint64, 8); +PVRTSIZEASSERT(PVRTfloat32, 4); + +/*!************************************************************************** +@Enum ETextureFilter +@Brief Enum values for defining texture filtering +****************************************************************************/ +enum ETextureFilter +{ + eFilter_Nearest, + eFilter_Linear, + eFilter_None, + + eFilter_Size, + eFilter_Default = eFilter_Linear, + eFilter_MipDefault = eFilter_None +}; + +/*!************************************************************************** +@Enum ETextureWrap +@Brief Enum values for defining texture wrapping +****************************************************************************/ +enum ETextureWrap +{ + eWrap_Clamp, + eWrap_Repeat, + + eWrap_Size, + eWrap_Default = eWrap_Repeat +}; + +/**************************************************************************** +** swap template function +****************************************************************************/ +/*!*************************************************************************** + @Function PVRTswap + @Input a Type a + @Input b Type b + @Description A swap template function that swaps a and b +*****************************************************************************/ + +template +inline void PVRTswap(T& a, T& b) +{ + T temp = a; + a = b; + b = temp; +} + +/*!*************************************************************************** + @Function PVRTClamp + @Input val Value to clamp + @Input min Minimum legal value + @Input max Maximum legal value + @Description A clamp template function that clamps val between min and max. +*****************************************************************************/ +template +inline T PVRTClamp(const T& val, const T& min, const T& max) +{ + if(val > max) + return max; + if(val < min) + return min; + return val; +} + +/*!*************************************************************************** + @Function PVRTByteSwap + @Input pBytes A number + @Input i32ByteNo Number of bytes in pBytes + @Description Swaps the endianness of pBytes in place +*****************************************************************************/ +inline void PVRTByteSwap(unsigned char* pBytes, int i32ByteNo) +{ + int i = 0, j = i32ByteNo - 1; + + while(i < j) + PVRTswap(pBytes[i++], pBytes[j--]); +} + +/*!*************************************************************************** + @Function PVRTByteSwap32 + @Input ui32Long A number + @Returns ui32Long with its endianness changed + @Description Converts the endianness of an unsigned int +*****************************************************************************/ +inline unsigned int PVRTByteSwap32(unsigned int ui32Long) +{ + return ((ui32Long&0x000000FF)<<24) + ((ui32Long&0x0000FF00)<<8) + ((ui32Long&0x00FF0000)>>8) + ((ui32Long&0xFF000000) >> 24); +} + +/*!*************************************************************************** + @Function PVRTByteSwap16 + @Input ui16Short A number + @Returns ui16Short with its endianness changed + @Description Converts the endianness of a unsigned short +*****************************************************************************/ +inline unsigned short PVRTByteSwap16(unsigned short ui16Short) +{ + return (ui16Short>>8) | (ui16Short<<8); +} + +/*!*************************************************************************** + @Function PVRTIsLittleEndian + @Returns True if the platform the code is ran on is little endian + @Description Returns true if the platform the code is ran on is little endian +*****************************************************************************/ +inline bool PVRTIsLittleEndian() +{ + static bool bLittleEndian; + static bool bIsInit = false; + + if(!bIsInit) + { + short int word = 0x0001; + char *byte = (char*) &word; + bLittleEndian = byte[0] ? true : false; + bIsInit = true; + } + + return bLittleEndian; +} + +#endif // _PVRTGLOBAL_H_ + +/***************************************************************************** + End of file (Tools.h) +*****************************************************************************/ + diff --git a/KREngine/3rdparty/pvrtexlib/include/PVRTMap.h b/KREngine/3rdparty/pvrtexlib/include/PVRTMap.h new file mode 100644 index 0000000..fff8252 --- /dev/null +++ b/KREngine/3rdparty/pvrtexlib/include/PVRTMap.h @@ -0,0 +1,225 @@ +/****************************************************************************** + + @File PVRTMap.h + + @Title PVRTArray + + @Version + + @Copyright Copyright (c) Imagination Technologies Limited. + + @Platform ANSI compatible + + @Description A simple and easy to use implementation of a map. + +******************************************************************************/ +#ifndef __PVRTMAP_H__ +#define __PVRTMAP_H__ + +#include "PVRTArray.h" + +/*!**************************************************************************** +Class +******************************************************************************/ + +/*!*************************************************************************** +* @Class CPVRTMap +* @Brief Expanding map template class. +* @Description A simple and easy to use implementation of a map. +*****************************************************************************/ +template +class CPVRTMap +{ +public: + + /*!*********************************************************************** + @Function CPVRTMap + @Return A new CPVRTMap. + @Description Constructor for a CPVRTMap. + *************************************************************************/ + CPVRTMap() : m_Keys(), m_Data(), m_uiSize(0) + {} + + /*!*********************************************************************** + @Function ~CPVRTMap + @Description Destructor for a CPVRTMap. + *************************************************************************/ + ~CPVRTMap() + { + //Clear the map, that's enough - the CPVRTArray members will tidy everything else up. + Clear(); + } + + EPVRTError Reserve(const PVRTuint32 uiSize) + { + //Sets the capacity of each member array to the requested size. The array used will only expand. + //Returns the most serious error from either method. + return PVRT_MAX(m_Keys.SetCapacity(uiSize),m_Data.SetCapacity(uiSize)); + } + + /*!*********************************************************************** + @Function GetSize + @Return Number of meaningful members in the map. + @Description Returns the number of meaningful members in the map. + *************************************************************************/ + PVRTuint32 GetSize() const + { + //Return the size. + return m_uiSize; + } + + /*!*********************************************************************** + @Function GetIndexOf + @Input key + @Return The index value for a mapped item. + @Description Gets the position of a particular key/data within the map. + If the return value is exactly equal to the value of + GetSize() then the item has not been found. + *************************************************************************/ + PVRTuint32 GetIndexOf(const KeyType key) const + { + //Loop through all the valid keys. + for (PVRTuint32 i=0; i=m_uiSize) + return NULL; + + return &(m_Data[uiIndex]); + } + + /*!*********************************************************************** + @Function operator[] + @Input key + @Return Data that is mapped to 'key'. + @Description If a mapping already exists for 'key' then it will return + the associated data. If no mapping currently exists, a new + element is created in place. + *************************************************************************/ + DataType& operator[] (const KeyType key) + { + //Get the index of the key. + PVRTuint32 uiIndex = GetIndexOf(key); + + //Check the index is valid + if (uiIndex != m_uiSize) + { + //Return mapped data if the index is valid. + return m_Data[uiIndex]; + } + else + { + //Append the key to the Keys array. + m_Keys.Append(key); + + //Create a new DataType. + DataType sNewData; + + //Append the new pointer to the Data array. + m_Data.Append(sNewData); + + //Increment the size of meaningful data. + ++m_uiSize; + + //Return the contents of pNewData. + return m_Data[m_Keys.GetSize()-1]; + } + } + + /*!*********************************************************************** + @Function Remove + @Input key + @Return Returns PVR_FAIL if item doesn't exist. + Otherwise returns PVR_SUCCESS. + @Description Removes an element from the map if it exists. + *************************************************************************/ + EPVRTError Remove(const KeyType key) + { + //Finds the index of the key. + PVRTuint32 uiIndex=GetIndexOf(key); + + //If the key is invalid, fail. + if (uiIndex==m_uiSize) + { + //Return failure. + return PVR_FAIL; + } + + //Decrement the size of the map to ignore the last element in each array. + m_uiSize--; + + //Copy the last key over the deleted key. There are now two copies of one element, + //but the one at the end of the array is ignored. + m_Keys[uiIndex]=m_Keys[m_uiSize-1]; + + //Copy the last data over the deleted data in the same way as the keys. + m_Data[uiIndex]=m_Data[m_uiSize-1]; + + //Return success. + return PVR_SUCCESS; + } + + /*!*********************************************************************** + @Function Clear + @Description Clears the Map of all data values. + *************************************************************************/ + void Clear() + { + //Set the size to 0. + m_uiSize=0; + m_Keys.Clear(); + m_Data.Clear(); + } + + /*!*********************************************************************** + @Function Exists + @Input key + @Return Whether data exists for the specified key or not. + @Description Checks whether or not data exists for the specified key. + *************************************************************************/ + bool Exists(const KeyType key) const + { + //Checks for a valid index for key, if not, returns false. + return (GetIndexOf(key) != m_uiSize); + } + +private: + + //Array of all the keys. Indices match m_Data. + CPVRTArray m_Keys; + + //Array of pointers to all the allocated data. + CPVRTArray m_Data; + + //The number of meaningful members in the map. + PVRTuint32 m_uiSize; +}; + +#endif // __PVRTMAP_H__ + +/***************************************************************************** +End of file (PVRTMap.h) +*****************************************************************************/ + diff --git a/KREngine/3rdparty/pvrtexlib/include/PVRTString.h b/KREngine/3rdparty/pvrtexlib/include/PVRTString.h new file mode 100644 index 0000000..759aa8b --- /dev/null +++ b/KREngine/3rdparty/pvrtexlib/include/PVRTString.h @@ -0,0 +1,982 @@ +/****************************************************************************** + + @File PVRTString.h + + @Title PVRTString + + @Version + + @Copyright Copyright (c) Imagination Technologies Limited. + + @Platform ANSI compatible + + @Description A string class that can be used as drop-in replacement for + std::string on platforms/compilers that don't provide a full C++ + standard library. + +******************************************************************************/ +#ifndef _PVRTSTRING_H_ +#define _PVRTSTRING_H_ + +#include +#define _USING_PVRTSTRING_ + +/*!*************************************************************************** +@Class CPVRTString +@Brief A string class +*****************************************************************************/ +class CPVRTString +{ + +private: + + // Checking printf and scanf format strings +#if defined(_CC_GNU_) || defined(__GNUG__) || defined(__GNUC__) +#define FX_PRINTF(fmt,arg) __attribute__((format(printf,fmt,arg))) +#define FX_SCANF(fmt,arg) __attribute__((format(scanf,fmt,arg))) +#else +#define FX_PRINTF(fmt,arg) +#define FX_SCANF(fmt,arg) +#endif + +public: + typedef size_t size_type; + typedef char value_type; + typedef char& reference; + typedef const char& const_reference; + + static const size_type npos; + + + + + /*!*********************************************************************** + @Function CPVRTString + @Input _Ptr A string + @Input _Count Length of _Ptr + @Description Constructor + ************************************************************************/ + CPVRTString(const char* _Ptr, size_t _Count = npos); + + /*!*********************************************************************** + @Function CPVRTString + @Input _Right A string + @Input _Roff Offset into _Right + @Input _Count Number of chars from _Right to assign to the new string + @Description Constructor + ************************************************************************/ + CPVRTString(const CPVRTString& _Right, size_t _Roff = 0, size_t _Count = npos); + + /*!*********************************************************************** + @Function CPVRTString + @Input _Count Length of new string + @Input _Ch A char to fill it with + @Description Constructor + *************************************************************************/ + CPVRTString(size_t _Count, const char _Ch); + + /*!*********************************************************************** + @Function CPVRTString + @Input _Ch A char + @Description Constructor + *************************************************************************/ + CPVRTString(const char _Ch); + + /*!*********************************************************************** + @Function CPVRTString + @Description Constructor + ************************************************************************/ + CPVRTString(); + + /*!*********************************************************************** + @Function ~CPVRTString + @Description Destructor + ************************************************************************/ + virtual ~CPVRTString(); + + /*!*********************************************************************** + @Function append + @Input _Ptr A string + @Returns Updated string + @Description Appends a string + *************************************************************************/ + CPVRTString& append(const char* _Ptr); + + /*!*********************************************************************** + @Function append + @Input _Ptr A string + @Input _Count String length + @Returns Updated string + @Description Appends a string of length _Count + *************************************************************************/ + CPVRTString& append(const char* _Ptr, size_t _Count); + + /*!*********************************************************************** + @Function append + @Input _Str A string + @Returns Updated string + @Description Appends a string + *************************************************************************/ + CPVRTString& append(const CPVRTString& _Str); + + /*!*********************************************************************** + @Function append + @Input _Str A string + @Input _Off A position in string + @Input _Count Number of letters to append + @Returns Updated string + @Description Appends _Count letters of _Str from _Off in _Str + *************************************************************************/ + CPVRTString& append(const CPVRTString& _Str, size_t _Off, size_t _Count); + + /*!*********************************************************************** + @Function append + @Input _Ch A char + @Input _Count Number of times to append _Ch + @Returns Updated string + @Description Appends _Ch _Count times + *************************************************************************/ + CPVRTString& append(size_t _Count, const char _Ch); + + //template CPVRTString& append(InputIterator _First, InputIterator _Last); + + /*!*********************************************************************** + @Function assign + @Input _Ptr A string + @Returns Updated string + @Description Assigns the string to the string _Ptr + *************************************************************************/ + CPVRTString& assign(const char* _Ptr); + + /*!*********************************************************************** + @Function assign + @Input _Ptr A string + @Input _Count Length of _Ptr + @Returns Updated string + @Description Assigns the string to the string _Ptr + *************************************************************************/ + CPVRTString& assign(const char* _Ptr, size_t _Count); + + /*!*********************************************************************** + @Function assign + @Input _Str A string + @Returns Updated string + @Description Assigns the string to the string _Str + *************************************************************************/ + CPVRTString& assign(const CPVRTString& _Str); + + /*!*********************************************************************** + @Function assign + @Input _Str A string + @Input _Off First char to start assignment from + @Input _Count Length of _Str + @Returns Updated string + @Description Assigns the string to _Count characters in string _Str starting at _Off + *************************************************************************/ + CPVRTString& assign(const CPVRTString& _Str, size_t _Off, size_t _Count=npos); + + /*!*********************************************************************** + @Function assign + @Input _Ch A string + @Input _Count Number of times to repeat _Ch + @Returns Updated string + @Description Assigns the string to _Count copies of _Ch + *************************************************************************/ + CPVRTString& assign(size_t _Count, char _Ch); + + //template CPVRTString& assign(InputIterator _First, InputIterator _Last); + + //const_reference at(size_t _Off) const; + //reference at(size_t _Off); + + // const_iterator begin() const; + // iterator begin(); + + /*!*********************************************************************** + @Function c_str + @Returns const char* pointer of the string + @Description Returns a const char* pointer of the string + *************************************************************************/ + const char* c_str() const; + + /*!*********************************************************************** + @Function capacity + @Returns The size of the character array reserved + @Description Returns the size of the character array reserved + *************************************************************************/ + size_t capacity() const; + + /*!*********************************************************************** + @Function clear + @Description Clears the string + *************************************************************************/ + void clear(); + + /*!*********************************************************************** + @Function compare + @Input _Str A string to compare with + @Returns 0 if the strings match + @Description Compares the string with _Str + *************************************************************************/ + int compare(const CPVRTString& _Str) const; + + /*!*********************************************************************** + @Function compare + @Input _Pos1 Position to start comparing from + @Input _Num1 Number of chars to compare + @Input _Str A string to compare with + @Returns 0 if the strings match + @Description Compares the string with _Str + *************************************************************************/ + int compare(size_t _Pos1, size_t _Num1, const CPVRTString& _Str) const; + + /*!*********************************************************************** + @Function compare + @Input _Pos1 Position to start comparing from + @Input _Num1 Number of chars to compare + @Input _Str A string to compare with + @Input _Off Position in _Str to compare from + @Input _Count Number of chars in _Str to compare with + @Returns 0 if the strings match + @Description Compares the string with _Str + *************************************************************************/ + int compare(size_t _Pos1, size_t _Num1, const CPVRTString& _Str, size_t _Off, size_t _Count) const; + + /*!*********************************************************************** + @Function compare + @Input _Ptr A string to compare with + @Returns 0 if the strings match + @Description Compares the string with _Ptr + *************************************************************************/ + int compare(const char* _Ptr) const; + + /*!*********************************************************************** + @Function compare + @Input _Pos1 Position to start comparing from + @Input _Num1 Number of chars to compare + @Input _Ptr A string to compare with + @Returns 0 if the strings match + @Description Compares the string with _Ptr + *************************************************************************/ + int compare(size_t _Pos1, size_t _Num1, const char* _Ptr) const; + + /*!*********************************************************************** + @Function compare + @Input _Pos1 Position to start comparing from + @Input _Num1 Number of chars to compare + @Input _Ptr A string to compare with + @Input _Count Number of chars to compare + @Returns 0 if the strings match + @Description Compares the string with _Str + *************************************************************************/ + int compare(size_t _Pos1, size_t _Num1, const char* _Ptr, size_t _Count) const; + + /*!*********************************************************************** + @Function < + @Input _Str A string to compare with + @Returns True on success + @Description Less than operator + *************************************************************************/ + bool operator<(const CPVRTString & _Str) const; + + /*!*********************************************************************** + @Function == + @Input _Str A string to compare with + @Returns True if they match + @Description == Operator + *************************************************************************/ + bool operator==(const CPVRTString& _Str) const; + + /*!*********************************************************************** + @Function == + @Input _Ptr A string to compare with + @Returns True if they match + @Description == Operator + *************************************************************************/ + bool operator==(const char* const _Ptr) const; + + /*!*********************************************************************** + @Function != + @Input _Str A string to compare with + @Returns True if they don't match + @Description != Operator + *************************************************************************/ + bool operator!=(const CPVRTString& _Str) const; + + /*!*********************************************************************** + @Function != + @Input _Ptr A string to compare with + @Returns True if they don't match + @Description != Operator + *************************************************************************/ + bool operator!=(const char* const _Ptr) const; + + /*!*********************************************************************** + @Function copy + @Modified _Ptr A string to copy to + @Input _Count Size of _Ptr + @Input _Off Position to start copying from + @Returns Number of bytes copied + @Description Copies the string to _Ptr + *************************************************************************/ + size_t copy(char* _Ptr, size_t _Count, size_t _Off = 0) const; + + /*!*********************************************************************** + @Function data + @Returns A const char* version of the string + @Description Returns a const char* version of the string + *************************************************************************/ + const char* data( ) const; + + /*!*********************************************************************** + @Function empty + @Returns True if the string is empty + @Description Returns true if the string is empty + *************************************************************************/ + bool empty() const; + + // const_iterator end() const; + // iterator end(); + + //iterator erase(iterator _First, iterator _Last); + //iterator erase(iterator _It); + + /*!*********************************************************************** + @Function erase + @Input _Pos The position to start erasing from + @Input _Count Number of chars to erase + @Returns An updated string + @Description Erases a portion of the string + *************************************************************************/ + CPVRTString& erase(size_t _Pos = 0, size_t _Count = npos); + + /*!*********************************************************************** + @Function substitute + @Input _src Character to search + @Input _subDes Character to substitute for + @Input _all Substitute all + @Returns An updated string + @Description Erases a portion of the string + *************************************************************************/ + CPVRTString& substitute(char _src,char _subDes, bool _all = true); + + /*!*********************************************************************** + @Function substitute + @Input _src Character to search + @Input _subDes Character to substitute for + @Input _all Substitute all + @Returns An updated string + @Description Erases a portion of the string + *************************************************************************/ + CPVRTString& substitute(const char* _src, const char* _subDes, bool _all = true); + + //size_t find(char _Ch, size_t _Off = 0) const; + //size_t find(const char* _Ptr, size_t _Off = 0) const; + + /*!*********************************************************************** + @Function find + @Input _Ptr String to search. + @Input _Off Offset to search from. + @Input _Count Number of characters in this string. + @Returns Position of the first matched string. + @Description Finds a substring within this string. + *************************************************************************/ + size_t find(const char* _Ptr, size_t _Off, size_t _Count) const; + + /*!*********************************************************************** + @Function find + @Input _Str String to search. + @Input _Off Offset to search from. + @Returns Position of the first matched string. + @Description Finds a substring within this string. + *************************************************************************/ + size_t find(const CPVRTString& _Str, size_t _Off = 0) const; + + /*!*********************************************************************** + @Function find_first_not_of + @Input _Ch A char + @Input _Off Start position of the find + @Returns Position of the first char that is not _Ch + @Description Returns the position of the first char that is not _Ch + *************************************************************************/ + size_t find_first_not_of(char _Ch, size_t _Off = 0) const; + + /*!*********************************************************************** + @Function find_first_not_of + @Input _Ptr A string + @Input _Off Start position of the find + @Returns Position of the first char that is not in _Ptr + @Description Returns the position of the first char that is not in _Ptr + *************************************************************************/ + size_t find_first_not_of(const char* _Ptr, size_t _Off = 0) const; + + /*!*********************************************************************** + @Function find_first_not_of + @Input _Ptr A string + @Input _Off Start position of the find + @Input _Count Number of chars in _Ptr + @Returns Position of the first char that is not in _Ptr + @Description Returns the position of the first char that is not in _Ptr + *************************************************************************/ + size_t find_first_not_of(const char* _Ptr, size_t _Off, size_t _Count) const; + + /*!*********************************************************************** + @Function find_first_not_of + @Input _Str A string + @Input _Off Start position of the find + @Returns Position of the first char that is not in _Str + @Description Returns the position of the first char that is not in _Str + *************************************************************************/ + size_t find_first_not_of(const CPVRTString& _Str, size_t _Off = 0) const; + + /*!*********************************************************************** + @Function find_first_of + @Input _Ch A char + @Input _Off Start position of the find + @Returns Position of the first char that is _Ch + @Description Returns the position of the first char that is _Ch + *************************************************************************/ + size_t find_first_of(char _Ch, size_t _Off = 0) const; + + /*!*********************************************************************** + @Function find_first_of + @Input _Ptr A string + @Input _Off Start position of the find + @Returns Position of the first char that matches a char in _Ptr + @Description Returns the position of the first char that matches a char in _Ptr + *************************************************************************/ + size_t find_first_of(const char* _Ptr, size_t _Off = 0) const; + + /*!*********************************************************************** + @Function find_first_of + @Input _Ptr A string + @Input _Off Start position of the find + @Input _Count Size of _Ptr + @Returns Position of the first char that matches a char in _Ptr + @Description Returns the position of the first char that matches a char in _Ptr + *************************************************************************/ + size_t find_first_of(const char* _Ptr, size_t _Off, size_t _Count) const; + + /*!*********************************************************************** + @Function find_first_ofn + @Input _Ptr A string + @Input _Off Start position of the find + @Input _Count Size of _Ptr + @Returns Position of the first char that matches a char in _Ptr + @Description Returns the position of the first char that matches all chars in _Ptr + *************************************************************************/ + size_t find_first_ofn(const char* _Ptr, size_t _Off, size_t _Count) const; + + + /*!*********************************************************************** + @Function find_first_of + @Input _Str A string + @Input _Off Start position of the find + @Returns Position of the first char that matches a char in _Str + @Description Returns the position of the first char that matches a char in _Str + *************************************************************************/ + size_t find_first_of(const CPVRTString& _Str, size_t _Off = 0) const; + + /*!*********************************************************************** + @Function find_last_not_of + @Input _Ch A char + @Input _Off Start position of the find + @Returns Position of the last char that is not _Ch + @Description Returns the position of the last char that is not _Ch + *************************************************************************/ + size_t find_last_not_of(char _Ch, size_t _Off = 0) const; + + /*!*********************************************************************** + @Function find_last_not_of + @Input _Ptr A string + @Input _Off Start position of the find + @Returns Position of the last char that is not in _Ptr + @Description Returns the position of the last char that is not in _Ptr + *************************************************************************/ + size_t find_last_not_of(const char* _Ptr, size_t _Off = 0) const; + + /*!*********************************************************************** + @Function find_last_not_of + @Input _Ptr A string + @Input _Off Start position of the find + @Input _Count Length of _Ptr + @Returns Position of the last char that is not in _Ptr + @Description Returns the position of the last char that is not in _Ptr + *************************************************************************/ + size_t find_last_not_of(const char* _Ptr, size_t _Off, size_t _Count) const; + + /*!*********************************************************************** + @Function find_last_not_of + @Input _Str A string + @Input _Off Start position of the find + @Returns Position of the last char that is not in _Str + @Description Returns the position of the last char that is not in _Str + *************************************************************************/ + size_t find_last_not_of(const CPVRTString& _Str, size_t _Off = 0) const; + + /*!*********************************************************************** + @Function find_last_of + @Input _Ch A char + @Input _Off Start position of the find + @Returns Position of the last char that is _Ch + @Description Returns the position of the last char that is _Ch + *************************************************************************/ + size_t find_last_of(char _Ch, size_t _Off = 0) const; + + /*!*********************************************************************** + @Function find_last_of + @Input _Ptr A string + @Input _Off Start position of the find + @Returns Position of the last char that is in _Ptr + @Description Returns the position of the last char that is in _Ptr + *************************************************************************/ + size_t find_last_of(const char* _Ptr, size_t _Off = 0) const; + + /*!*********************************************************************** + @Function find_last_of + @Input _Ptr A string + @Input _Off Start position of the find + @Input _Count Length of _Ptr + @Returns Position of the last char that is in _Ptr + @Description Returns the position of the last char that is in _Ptr + *************************************************************************/ + size_t find_last_of(const char* _Ptr, size_t _Off, size_t _Count) const; + + /*!*********************************************************************** + @Function find_last_of + @Input _Str A string + @Input _Off Start position of the find + @Returns Position of the last char that is in _Str + @Description Returns the position of the last char that is in _Str + *************************************************************************/ + size_t find_last_of(const CPVRTString& _Str, size_t _Off = 0) const; + + /*!*********************************************************************** + @Function find_number_of + @Input _Ch A char + @Input _Off Start position of the find + @Returns Number of occurances of _Ch in the parent string. + @Description Returns the number of occurances of _Ch in the parent string. + *************************************************************************/ + size_t find_number_of(char _Ch, size_t _Off = 0) const; + + /*!*********************************************************************** + @Function find_number_of + @Input _Ptr A string + @Input _Off Start position of the find + @Returns Number of occurances of _Ptr in the parent string. + @Description Returns the number of occurances of _Ptr in the parent string. + *************************************************************************/ + size_t find_number_of(const char* _Ptr, size_t _Off = 0) const; + + /*!*********************************************************************** + @Function find_number_of + @Input _Ptr A string + @Input _Off Start position of the find + @Input _Count Size of _Ptr + @Returns Number of occurances of _Ptr in the parent string. + @Description Returns the number of occurances of _Ptr in the parent string. + *************************************************************************/ + size_t find_number_of(const char* _Ptr, size_t _Off, size_t _Count) const; + + /*!*********************************************************************** + @Function find_number_of + @Input _Str A string + @Input _Off Start position of the find + @Returns Number of occurances of _Str in the parent string. + @Description Returns the number of occurances of _Str in the parent string. + *************************************************************************/ + size_t find_number_of(const CPVRTString& _Str, size_t _Off = 0) const; + + /*!*********************************************************************** + @Function find_next_occurance_of + @Input _Ch A char + @Input _Off Start position of the find + @Returns Next occurance of _Ch in the parent string. + @Description Returns the next occurance of _Ch in the parent string + after or at _Off. If not found, returns the length of the string. + *************************************************************************/ + int find_next_occurance_of(char _Ch, size_t _Off = 0) const; + + /*!*********************************************************************** + @Function find_next_occurance_of + @Input _Ptr A string + @Input _Off Start position of the find + @Returns Next occurance of _Ptr in the parent string. + @Description Returns the next occurance of _Ptr in the parent string + after or at _Off. If not found, returns the length of the string. + *************************************************************************/ + int find_next_occurance_of(const char* _Ptr, size_t _Off = 0) const; + + /*!*********************************************************************** + @Function find_next_occurance_of + @Input _Ptr A string + @Input _Off Start position of the find + @Input _Count Size of _Ptr + @Returns Next occurance of _Ptr in the parent string. + @Description Returns the next occurance of _Ptr in the parent string + after or at _Off. If not found, returns the length of the string. + *************************************************************************/ + int find_next_occurance_of(const char* _Ptr, size_t _Off, size_t _Count) const; + + /*!*********************************************************************** + @Function find_next_occurance_of + @Input _Str A string + @Input _Off Start position of the find + @Returns Next occurance of _Str in the parent string. + @Description Returns the next occurance of _Str in the parent string + after or at _Off. If not found, returns the length of the string. + *************************************************************************/ + int find_next_occurance_of(const CPVRTString& _Str, size_t _Off = 0) const; + + /*!*********************************************************************** + @Function find_previous_occurance_of + @Input _Ch A char + @Input _Off Start position of the find + @Returns Previous occurance of _Ch in the parent string. + @Description Returns the previous occurance of _Ch in the parent string + before _Off. If not found, returns -1. + *************************************************************************/ + int find_previous_occurance_of(char _Ch, size_t _Off = 0) const; + + /*!*********************************************************************** + @Function find_previous_occurance_of + @Input _Ptr A string + @Input _Off Start position of the find + @Returns Previous occurance of _Ptr in the parent string. + @Description Returns the previous occurance of _Ptr in the parent string + before _Off. If not found, returns -1. + *************************************************************************/ + int find_previous_occurance_of(const char* _Ptr, size_t _Off = 0) const; + + /*!*********************************************************************** + @Function find_previous_occurance_of + @Input _Ptr A string + @Input _Off Start position of the find + @Input _Count Size of _Ptr + @Returns Previous occurance of _Ptr in the parent string. + @Description Returns the previous occurance of _Ptr in the parent string + before _Off. If not found, returns -1. + *************************************************************************/ + int find_previous_occurance_of(const char* _Ptr, size_t _Off, size_t _Count) const; + + /*!*********************************************************************** + @Function find_previous_occurance_of + @Input _Str A string + @Input _Off Start position of the find + @Returns Previous occurance of _Str in the parent string. + @Description Returns the previous occurance of _Str in the parent string + before _Off. If not found, returns -1. + *************************************************************************/ + int find_previous_occurance_of(const CPVRTString& _Str, size_t _Off = 0) const; + + /*!*********************************************************************** + @Function left + @Input iSize number of characters to return (excluding null character) + @Returns The leftmost 'iSize' characters of the string. + @Description Returns the leftmost characters of the string (excluding + the null character) in a new CPVRTString. If iSize is + larger than the string, a copy of the original string is returned. + *************************************************************************/ + CPVRTString left(size_t iSize) const; + + /*!*********************************************************************** + @Function right + @Input iSize number of characters to return (excluding null character) + @Returns The rightmost 'iSize' characters of the string. + @Description Returns the rightmost characters of the string (excluding + the null character) in a new CPVRTString. If iSize is + larger than the string, a copy of the original string is returned. + *************************************************************************/ + CPVRTString right(size_t iSize) const; + + //allocator_type get_allocator( ) const; + + //CPVRTString& insert(size_t _P0, const char* _Ptr); + //CPVRTString& insert(size_t _P0, const char* _Ptr, size_t _Count); + //CPVRTString& insert(size_t _P0, const CPVRTString& _Str); + //CPVRTString& insert(size_t _P0, const CPVRTString& _Str, size_t _Off, size_t _Count); + //CPVRTString& insert(size_t _P0, size_t _Count, char _Ch); + //iterator insert(iterator _It, char _Ch = char()); + //template void insert(iterator _It, InputIterator _First, InputIterator _Last); + //void insert(iterator _It, size_t _Count, char _Ch); + + /*!*********************************************************************** + @Function length + @Returns Length of the string + @Description Returns the length of the string + *************************************************************************/ + size_t length() const; + + /*!*********************************************************************** + @Function max_size + @Returns The maximum number of chars that the string can contain + @Description Returns the maximum number of chars that the string can contain + *************************************************************************/ + size_t max_size() const; + + /*!*********************************************************************** + @Function push_back + @Input _Ch A char to append + @Description Appends _Ch to the string + *************************************************************************/ + void push_back(char _Ch); + + // const_reverse_iterator rbegin() const; + // reverse_iterator rbegin(); + + // const_reverse_iterator rend() const; + // reverse_iterator rend(); + + //CPVRTString& replace(size_t _Pos1, size_t _Num1, const char* _Ptr); + //CPVRTString& replace(size_t _Pos1, size_t _Num1, const CPVRTString& _Str); + //CPVRTString& replace(size_t _Pos1, size_t _Num1, const char* _Ptr, size_t _Num2); + //CPVRTString& replace(size_t _Pos1, size_t _Num1, const CPVRTString& _Str, size_t _Pos2, size_t _Num2); + //CPVRTString& replace(size_t _Pos1, size_t _Num1, size_t _Count, char _Ch); + + //CPVRTString& replace(iterator _First0, iterator _Last0, const char* _Ptr); + //CPVRTString& replace(iterator _First0, iterator _Last0, const CPVRTString& _Str); + //CPVRTString& replace(iterator _First0, iterator _Last0, const char* _Ptr, size_t _Num2); + //CPVRTString& replace(iterator _First0, iterator _Last0, size_t _Num2, char _Ch); + //template CPVRTString& replace(iterator _First0, iterator _Last0, InputIterator _First, InputIterator _Last); + + /*!*********************************************************************** + @Function reserve + @Input _Count Size of string to reserve + @Description Reserves space for _Count number of chars + *************************************************************************/ + void reserve(size_t _Count = 0); + + /*!*********************************************************************** + @Function resize + @Input _Count Size of string to resize to + @Input _Ch Character to use to fill any additional space + @Description Resizes the string to _Count in length + *************************************************************************/ + void resize(size_t _Count, char _Ch = char()); + + //size_t rfind(char _Ch, size_t _Off = npos) const; + //size_t rfind(const char* _Ptr, size_t _Off = npos) const; + //size_t rfind(const char* _Ptr, size_t _Off = npos, size_t _Count) const; + //size_t rfind(const CPVRTString& _Str, size_t _Off = npos) const; + + /*!*********************************************************************** + @Function size + @Returns Size of the string + @Description Returns the size of the string + *************************************************************************/ + size_t size() const; + + /*!*********************************************************************** + @Function substr + @Input _Off Start of the substring + @Input _Count Length of the substring + @Returns A substring of the string + @Description Returns the size of the string + *************************************************************************/ + CPVRTString substr(size_t _Off = 0, size_t _Count = npos) const; + + /*!*********************************************************************** + @Function swap + @Input _Str A string to swap with + @Description Swaps the contents of the string with _Str + *************************************************************************/ + void swap(CPVRTString& _Str); + + /*!*********************************************************************** + @Function toLower + @Returns An updated string + @Description Converts the string to lower case + *************************************************************************/ + CPVRTString& toLower(); + + /*!*********************************************************************** + @Function toUpper + @Returns An updated string + @Description Converts the string to upper case + *************************************************************************/ + CPVRTString& toUpper(); + + /*!*********************************************************************** + @Function format + @Input pFormat A string containing the formating + @Returns A formatted string + @Description return the formatted string + ************************************************************************/ + CPVRTString format(const char *pFormat, ...); + + /*!*********************************************************************** + @Function += + @Input _Ch A char + @Returns An updated string + @Description += Operator + *************************************************************************/ + CPVRTString& operator+=(char _Ch); + + /*!*********************************************************************** + @Function += + @Input _Ptr A string + @Returns An updated string + @Description += Operator + *************************************************************************/ + CPVRTString& operator+=(const char* _Ptr); + + /*!*********************************************************************** + @Function += + @Input _Right A string + @Returns An updated string + @Description += Operator + *************************************************************************/ + CPVRTString& operator+=(const CPVRTString& _Right); + + /*!*********************************************************************** + @Function = + @Input _Ch A char + @Returns An updated string + @Description = Operator + *************************************************************************/ + CPVRTString& operator=(char _Ch); + + /*!*********************************************************************** + @Function = + @Input _Ptr A string + @Returns An updated string + @Description = Operator + *************************************************************************/ + CPVRTString& operator=(const char* _Ptr); + + /*!*********************************************************************** + @Function = + @Input _Right A string + @Returns An updated string + @Description = Operator + *************************************************************************/ + CPVRTString& operator=(const CPVRTString& _Right); + + /*!*********************************************************************** + @Function [] + @Input _Off An index into the string + @Returns A character + @Description [] Operator + *************************************************************************/ + const_reference operator[](size_t _Off) const; + + /*!*********************************************************************** + @Function [] + @Input _Off An index into the string + @Returns A character + @Description [] Operator + *************************************************************************/ + reference operator[](size_t _Off); + + /*!*********************************************************************** + @Function + + @Input _Left A string + @Input _Right A string + @Returns An updated string + @Description + Operator + *************************************************************************/ + friend CPVRTString operator+ (const CPVRTString& _Left, const CPVRTString& _Right); + + /*!*********************************************************************** + @Function + + @Input _Left A string + @Input _Right A string + @Returns An updated string + @Description + Operator + *************************************************************************/ + friend CPVRTString operator+ (const CPVRTString& _Left, const char* _Right); + + /*!*********************************************************************** + @Function + + @Input _Left A string + @Input _Right A string + @Returns An updated string + @Description + Operator + *************************************************************************/ + friend CPVRTString operator+ (const CPVRTString& _Left, const char _Right); + + /*!*********************************************************************** + @Function + + @Input _Left A string + @Input _Right A string + @Returns An updated string + @Description + Operator + *************************************************************************/ + friend CPVRTString operator+ (const char* _Left, const CPVRTString& _Right); + + + /*!*********************************************************************** + @Function + + @Input _Left A string + @Input _Right A string + @Returns An updated string + @Description + Operator + *************************************************************************/ + friend CPVRTString operator+ (const char _Left, const CPVRTString& _Right); + +protected: + char* m_pString; + size_t m_Size; + size_t m_Capacity; +}; + +/************************************************************************* +* MISCELLANEOUS UTILITY FUNCTIONS +*************************************************************************/ +/*!*********************************************************************** +@Function PVRTStringGetFileExtension +@Input strFilePath A string +@Returns Extension +@Description Extracts the file extension from a file path. +Returns an empty CPVRTString if no extension is found. +************************************************************************/ +CPVRTString PVRTStringGetFileExtension(const CPVRTString& strFilePath); + +/*!*********************************************************************** +@Function PVRTStringGetContainingDirectoryPath +@Input strFilePath A string +@Returns Directory +@Description Extracts the directory portion from a file path. +************************************************************************/ +CPVRTString PVRTStringGetContainingDirectoryPath(const CPVRTString& strFilePath); + +/*!*********************************************************************** +@Function PVRTStringGetFileName +@Input strFilePath A string +@Returns FileName +@Description Extracts the name and extension portion from a file path. +************************************************************************/ +CPVRTString PVRTStringGetFileName(const CPVRTString& strFilePath); + +/*!*********************************************************************** +@Function PVRTStringStripWhiteSpaceFromStartOf +@Input strLine A string +@Returns Result of the white space stripping +@Description strips white space characters from the beginning of a CPVRTString. +************************************************************************/ +CPVRTString PVRTStringStripWhiteSpaceFromStartOf(const CPVRTString& strLine); + +/*!*********************************************************************** +@Function PVRTStringStripWhiteSpaceFromEndOf +@Input strLine A string +@Returns Result of the white space stripping +@Description strips white space characters from the end of a CPVRTString. +************************************************************************/ +CPVRTString PVRTStringStripWhiteSpaceFromEndOf(const CPVRTString& strLine); + +/*!*********************************************************************** +@Function PVRTStringFromFormattedStr +@Input pFormat A string containing the formating +@Returns A formatted string +@Description Creates a formatted string +************************************************************************/ +CPVRTString PVRTStringFromFormattedStr(const char *pFormat, ...); + +#endif // _PVRTSTRING_H_ + + +/***************************************************************************** +End of file (PVRTString.h) +*****************************************************************************/ + diff --git a/KREngine/3rdparty/pvrtexlib/include/PVRTTexture.h b/KREngine/3rdparty/pvrtexlib/include/PVRTTexture.h new file mode 100644 index 0000000..114549f --- /dev/null +++ b/KREngine/3rdparty/pvrtexlib/include/PVRTTexture.h @@ -0,0 +1,700 @@ +/****************************************************************************** + + @File PVRTTexture.h + + @Title PVRTTexture + + @Version + + @Copyright Copyright (c) Imagination Technologies Limited. + + @Platform ANSI compatible + + @Description Texture loading. + +******************************************************************************/ +#ifndef _PVRTTEXTURE_H_ +#define _PVRTTEXTURE_H_ + +#include "PVRTGlobal.h" + +/***************************************************************************** +* Texture related constants and enumerations. +*****************************************************************************/ +// V3 Header Identifiers. +const PVRTuint32 PVRTEX3_IDENT = 0x03525650; // 'P''V''R'3 +const PVRTuint32 PVRTEX3_IDENT_REV = 0x50565203; +// If endianness is backwards then PVR3 will read as 3RVP, hence why it is written as an int. + +//Current version texture identifiers +const PVRTuint32 PVRTEX_CURR_IDENT = PVRTEX3_IDENT; +const PVRTuint32 PVRTEX_CURR_IDENT_REV = PVRTEX3_IDENT_REV; + +// PVR Header file flags. Condition if true. If false, opposite is true unless specified. +const PVRTuint32 PVRTEX3_FILE_COMPRESSED = (1<<0); // Texture has been file compressed using PVRTexLib (currently unused) +const PVRTuint32 PVRTEX3_PREMULTIPLIED = (1<<1); // Texture has been premultiplied by alpha value. + +// Mip Map level specifier constants. Other levels are specified by 1,2...n +const PVRTint32 PVRTEX_TOPMIPLEVEL = 0; +const PVRTint32 PVRTEX_ALLMIPLEVELS = -1; //This is a special number used simply to return a total of all MIP levels when dealing with data sizes. + +//values for each meta data type that we know about. Texture arrays hinge on each surface being identical in all but content, including meta data. +//If the meta data varies even slightly then a new texture should be used. It is possible to write your own extension to get around this however. +enum EPVRTMetaData +{ + ePVRTMetaDataTextureAtlasCoords=0, + ePVRTMetaDataBumpData, + ePVRTMetaDataCubeMapOrder, + ePVRTMetaDataTextureOrientation, + ePVRTMetaDataBorderData, + ePVRTMetaDataPadding, + ePVRTMetaDataNumMetaDataTypes +}; + +enum EPVRTAxis +{ + ePVRTAxisX = 0, + ePVRTAxisY = 1, + ePVRTAxisZ = 2 +}; + +enum EPVRTOrientation +{ + ePVRTOrientLeft = 1< +class CPVRTMap; + + +/*!*********************************************************************** + @Function PVRTGetBitsPerPixel + @Input u64PixelFormat A PVR Pixel Format ID. + @Return const PVRTuint32 Number of bits per pixel. + @Description Returns the number of bits per pixel in a PVR Pixel Format + identifier. +*************************************************************************/ +PVRTuint32 PVRTGetBitsPerPixel(PVRTuint64 u64PixelFormat); + +/*!*********************************************************************** + @Function PVRTGetFormatMinDims + @Input u64PixelFormat A PVR Pixel Format ID. + @Modified minX Returns the minimum width. + @Modified minY Returns the minimum height. + @Modified minZ Returns the minimum depth. + @Description Gets the minimum dimensions (x,y,z) for a given pixel format. +*************************************************************************/ +void PVRTGetFormatMinDims(PVRTuint64 u64PixelFormat, PVRTuint32 &minX, PVRTuint32 &minY, PVRTuint32 &minZ); + +/*!*********************************************************************** + @Function PVRTConvertOldTextureHeaderToV3 + @Input LegacyHeader Legacy header for conversion. + @Modified NewHeader New header to output into. + @Modified pMetaData MetaData Map to output into. + @Description Converts a legacy texture header (V1 or V2) to a current + generation header (V3) +*************************************************************************/ +void PVRTConvertOldTextureHeaderToV3(const PVR_Texture_Header* LegacyHeader, PVRTextureHeaderV3& NewHeader, CPVRTMap >* pMetaData); + +/*!*********************************************************************** + @Function PVRTMapLegacyTextureEnumToNewFormat + @Input OldFormat Legacy Enumeration Value + @Modified newType New PixelType identifier. + @Modified newCSpace New ColourSpace + @Modified newChanType New Channel Type + @Modified isPreMult Whether format is pre-multiplied + @Description Maps a legacy enumeration value to the new PVR3 style format. +*************************************************************************/ +void PVRTMapLegacyTextureEnumToNewFormat(PVRTPixelType OldFormat, PVRTuint64& newType, EPVRTColourSpace& newCSpace, EPVRTVariableType& newChanType, bool& isPreMult); + +/*!*************************************************************************** +@Function PVRTTextureLoadTiled +@Modified pDst Texture to place the tiled data +@Input nWidthDst Width of destination texture +@Input nHeightDst Height of destination texture +@Input pSrc Texture to tile +@Input nWidthSrc Width of source texture +@Input nHeightSrc Height of source texture +@Input nElementSize Bytes per pixel +@Input bTwiddled True if the data is twiddled +@Description Needed by PVRTTextureTile() in the various PVRTTextureAPIs +*****************************************************************************/ +void PVRTTextureLoadTiled( + PVRTuint8 * const pDst, + const unsigned int nWidthDst, + const unsigned int nHeightDst, + const PVRTuint8 * const pSrc, + const unsigned int nWidthSrc, + const unsigned int nHeightSrc, + const unsigned int nElementSize, + const bool bTwiddled); + + +/*!*************************************************************************** +@Function PVRTTextureTwiddle +@Output a Twiddled value +@Input u Coordinate axis 0 +@Input v Coordinate axis 1 +@Description Combine a 2D coordinate into a twiddled value +*****************************************************************************/ +void PVRTTextureTwiddle(unsigned int &a, const unsigned int u, const unsigned int v); + +/*!*************************************************************************** +@Function PVRTTextureDeTwiddle +@Output u Coordinate axis 0 +@Output v Coordinate axis 1 +@Input a Twiddled value +@Description Extract 2D coordinates from a twiddled value. +*****************************************************************************/ +void PVRTTextureDeTwiddle(unsigned int &u, unsigned int &v, const unsigned int a); + +/*!*********************************************************************** +@Function PVRTGetTextureDataSize +@Input sTextureHeader Specifies the texture header. +@Input iMipLevel Specifies a mip level to check, 'PVRTEX_ALLMIPLEVELS' + can be passed to get the size of all MIP levels. +@Input bAllSurfaces Size of all surfaces is calculated if true, + only a single surface if false. +@Input bAllFaces Size of all faces is calculated if true, + only a single face if false. +@Return PVRTuint32 Size in BYTES of the specified texture area. +@Description Gets the size in BYTES of the texture, given various input + parameters. User can retrieve the size of either all + surfaces or a single surface, all faces or a single face and + all MIP-Maps or a single specified MIP level. +*************************************************************************/ +PVRTuint32 PVRTGetTextureDataSize(PVRTextureHeaderV3 sTextureHeader, PVRTint32 iMipLevel=PVRTEX_ALLMIPLEVELS, bool bAllSurfaces = true, bool bAllFaces = true); + +#endif /* _PVRTTEXTURE_H_ */ + +/***************************************************************************** +End of file (PVRTTexture.h) +*****************************************************************************/ + diff --git a/KREngine/3rdparty/pvrtexlib/include/PVRTexture.h b/KREngine/3rdparty/pvrtexlib/include/PVRTexture.h new file mode 100644 index 0000000..a03043b --- /dev/null +++ b/KREngine/3rdparty/pvrtexlib/include/PVRTexture.h @@ -0,0 +1,208 @@ +#ifndef _PVRTEXTURE_H +#define _PVRTEXTURE_H + +#include "PVRTextureDefines.h" +#include "PVRTextureHeader.h" +#include "PVRTString.h" + +namespace pvrtexture +{ + class PVR_DLL CPVRTexture : public CPVRTextureHeader + { + public: + /******************************************************************************* + * Construction methods for a texture. + *******************************************************************************/ + /*!*********************************************************************** + @Function CPVRTexture + @Return CPVRTexture A new texture. + @Description Creates a new empty texture + *************************************************************************/ + CPVRTexture(); + /*!*********************************************************************** + @Function CPVRTexture + @Input sHeader + @Input pData + @Return CPVRTexture A new texture. + @Description Creates a new texture based on a texture header, + pre-allocating the correct amount of memory. If data is + supplied, it will be copied into memory. + *************************************************************************/ + CPVRTexture(const CPVRTextureHeader& sHeader, const void* pData=NULL); + + /*!*********************************************************************** + @Function CPVRTexture + @Input szFilePath + @Return CPVRTexture A new texture. + @Description Creates a new texture from a filepath. + *************************************************************************/ + CPVRTexture(const char* szFilePath); + + /*!*********************************************************************** + @Function CPVRTexture + @Input pTexture + @Return CPVRTexture A new texture. + @Description Creates a new texture from a pointer that includes a header + structure, meta data and texture data as laid out in a file. + This functionality is primarily for user defined file loading. + Header may be any version of pvr. + *************************************************************************/ + CPVRTexture( const void* pTexture ); + + /*!*********************************************************************** + @Function CPVRTexture + @Input texture + @Return CPVRTexture A new texture + @Description Creates a new texture as a copy of another. + *************************************************************************/ + CPVRTexture(const CPVRTexture& texture); + + /*!*********************************************************************** + @Function ~CPVRTexture + @Description Deconstructor for CPVRTextures. + *************************************************************************/ + ~CPVRTexture(); + + /*!*********************************************************************** + @Function operator= + @Input rhs + @Return CPVRTexture& This texture. + @Description Will copy the contents and information of another texture into this one. + *************************************************************************/ + CPVRTexture& operator=(const CPVRTexture& rhs); + + /******************************************************************************* + * Texture accessor functions - others are inherited from CPVRTextureHeader. + *******************************************************************************/ + /*!*********************************************************************** + @Function getDataPtr + @Input uiMIPLevel + @Input uiArrayMember + @Input uiFaceNumber + @Return void* Pointer to a location in the texture. + @Description Returns a pointer into the texture's data. + It is possible to specify an offset to specific array members, + faces and MIP Map levels. + *************************************************************************/ + void* getDataPtr(uint32 uiMIPLevel = 0, uint32 uiArrayMember = 0, uint32 uiFaceNumber = 0) const; + + /*!*********************************************************************** + @Function getHeader + @Return const CPVRTextureHeader& Returns the header only for this texture. + @Description Gets the header for this texture, allowing you to create a new + texture based on this one with some changes. Useful for passing + information about a texture without passing all of its data. + *************************************************************************/ + const CPVRTextureHeader& getHeader() const; + + /******************************************************************************* + * File io. + *******************************************************************************/ + + /*!*********************************************************************** + @Function setPaddedMetaData + @Input uiPadding + @Description When writing the texture out to a PVR file, it is often + desirable to pad the meta data so that the start of the + texture data aligns to a given boundary. + This function pads to a boundary value equal to "uiPadding". + For example setting uiPadding=8 will align the start of the + texture data to an 8 byte boundary. + Note - this should be called immediately before saving as + the value is worked out based on the current meta data size. + *************************************************************************/ + void addPaddingMetaData( uint32 uiPadding ); + + /*!*********************************************************************** + @Function saveFile + @Input filepath + @Return bool Whether the method succeeds or not. + @Description Writes out to a file, given a filename and path. + File type will be determined by the extension present in the string. + If no extension is present, PVR format will be selected. + Unsupported formats will result in failure. + *************************************************************************/ + bool saveFile(const CPVRTString& filepath) const; + + /*!*********************************************************************** + @Function saveFileLegacyPVR + @Input filepath + @Input eApi + @Return bool Whether the method succeeds or not. + @Description Writes out to a file, stripping any extensions specified + and appending .pvr. This function is for legacy support only + and saves out to PVR Version 2 file. The target api must be + specified in order to save to this format. + *************************************************************************/ + bool saveFileLegacyPVR(const CPVRTString& filepath, ELegacyApi eApi) const; + + private: + size_t m_stDataSize; // Size of the texture data. + uint8* m_pTextureData; // Pointer to texture data. + + /******************************************************************************* + * Private IO functions + *******************************************************************************/ + /*!*********************************************************************** + @Function loadPVRFile + @Input pTextureFile + @Description Loads a PVR file. + *************************************************************************/ + bool privateLoadPVRFile(FILE* pTextureFile); + + /*!*********************************************************************** + @Function privateSavePVRFile + @Input pTextureFile + @Description Saves a PVR File. + *************************************************************************/ + bool privateSavePVRFile(FILE* pTextureFile) const; + + /*!*********************************************************************** + @Function loadKTXFile + @Input pTextureFile + @Description Loads a KTX file. + *************************************************************************/ + bool privateLoadKTXFile(FILE* pTextureFile); + + /*!*********************************************************************** + @Function privateSaveKTXFile + @Input pTextureFile + @Description Saves a KTX File. + *************************************************************************/ + bool privateSaveKTXFile(FILE* pTextureFile) const; + + /*!*********************************************************************** + @Function loadDDSFile + @Input pTextureFile + @Description Loads a DDS file. + *************************************************************************/ + bool privateLoadDDSFile(FILE* pTextureFile); + + /*!*********************************************************************** + @Function privateSaveDDSFile + @Input pTextureFile + @Description Saves a DDS File. + *************************************************************************/ + bool privateSaveDDSFile(FILE* pTextureFile) const; + + //Legacy IO + /*!*********************************************************************** + @Function privateSavePVRFile + @Input pTextureFile + @Input filename + @Description Saves a .h File. + *************************************************************************/ + bool privateSaveCHeaderFile(FILE* pTextureFile, CPVRTString filename) const; + + /*!*********************************************************************** + @Function privateSaveLegacyPVRFile + @Input pTextureFile + @Input eApi + @Description Saves a legacy PVR File - Uses version 2 file format. + *************************************************************************/ + bool privateSaveLegacyPVRFile(FILE* pTextureFile, ELegacyApi eApi) const; + }; +}; + +#endif //_PVRTEXTURE_H + diff --git a/KREngine/3rdparty/pvrtexlib/include/PVRTextureDefines.h b/KREngine/3rdparty/pvrtexlib/include/PVRTextureDefines.h new file mode 100644 index 0000000..cadbd25 --- /dev/null +++ b/KREngine/3rdparty/pvrtexlib/include/PVRTextureDefines.h @@ -0,0 +1,98 @@ +#ifndef _PVRTEXTURE_DEFINES_H +#define _PVRTEXTURE_DEFINES_H + +//To use the PVRTexLib .dll on Windows, you need to define _WINDLL_IMPORT +#ifndef PVR_DLL +#if defined(_WINDLL_EXPORT) +#define PVR_DLL __declspec(dllexport) +//Forward declaration of various classes/structs used by this library. This exports their interfaces for DLLs. +struct PVR_DLL PVRTextureHeaderV3; +struct PVR_DLL MetaDataBlock; +template +class PVR_DLL CPVRTMap; +template +class PVR_DLL CPVRTArray; +class PVR_DLL CPVRTString; +#elif defined(_WINDLL_IMPORT) +#define PVR_DLL __declspec(dllimport) +//Forward declaration of various classes/structs used by this library. This imports their interfaces for DLLs. +struct PVR_DLL PVRTextureHeaderV3; +struct PVR_DLL MetaDataBlock; +template +class PVR_DLL CPVRTMap; +template +class PVR_DLL CPVRTArray; +class PVR_DLL CPVRTString; +#else +#define PVR_DLL +#endif +#endif + + +#include "PVRTTexture.h" + +namespace pvrtexture +{ + /***************************************************************************** + * Type defines for standard variable sizes. + *****************************************************************************/ + typedef signed char int8; + typedef signed short int16; + typedef signed int int32; + typedef signed long long int64; + typedef unsigned char uint8; + typedef unsigned short uint16; + typedef unsigned int uint32; + typedef unsigned long long uint64; + + /***************************************************************************** + * Texture related constants and enumerations. + *****************************************************************************/ + enum ECompressorQuality + { + ePVRTCFast=0, + ePVRTCNormal, + ePVRTCHigh, + ePVRTCBest, + eNumPVRTCModes, + + eETCFast=0, + eETCFastPerceptual, + eETCSlow, + eETCSlowPerceptual, + eNumETCModes + }; + + enum EResizeMode + { + eResizeNearest, + eResizeLinear, + eResizeCubic, + eNumResizeModes + }; + + // Legacy - API enums. + enum ELegacyApi + { + eOGLES=1, + eOGLES2, + eD3DM, + eOGL, + eDX9, + eDX10, + eOVG, + eMGL, + }; + + /***************************************************************************** + * Useful macros. + *****************************************************************************/ + #define TEXOFFSET2D(x,y,width) ( ((x)+(y)*(width)) ) + #define TEXOFFSET3D(x,y,z,width,height) ( ((x)+(y)*(width)+(z)*(width)*(height)) ) + + /***************************************************************************** + * Useful typedef for Meta Data Maps + *****************************************************************************/ + typedef CPVRTMap > MetaDataMap; +}; +#endif //_PVRTEXTURE_DEFINES_H diff --git a/KREngine/3rdparty/pvrtexlib/include/PVRTextureFormat.h b/KREngine/3rdparty/pvrtexlib/include/PVRTextureFormat.h new file mode 100644 index 0000000..48627ec --- /dev/null +++ b/KREngine/3rdparty/pvrtexlib/include/PVRTextureFormat.h @@ -0,0 +1,73 @@ +#ifndef _PVRT_PIXEL_FORMAT_H +#define _PVRT_PIXEL_FORMAT_H + +#include "PVRTextureDefines.h" +#include "PVRTString.h" + +namespace pvrtexture +{ + //Channel Names + enum EChannelName + { + eNoChannel, + eRed, + eGreen, + eBlue, + eAlpha, + eLuminance, + eIntensity, + eUnspecified, + eNumChannels + }; + + //PixelType union + union PVR_DLL PixelType + { + /*!*********************************************************************** + @Function PixelType + @Return A new PixelType + @Description Creates an empty pixeltype. + *************************************************************************/ + PixelType(); + + /*!*********************************************************************** + @Function PixelType + @Input Type + @Return A new PixelType + @Description Initialises a new pixel type from a 64 bit integer value. + *************************************************************************/ + PixelType(uint64 Type); + + /*!*********************************************************************** + @Function PixelType + @Input C1Name + @Input C2Name + @Input C3Name + @Input C4Name + @Input C1Bits + @Input C2Bits + @Input C3Bits + @Input C4Bits + @Return A new PixelType + @Description Takes up to 4 characters (CnName) and 4 values (CnBits) + to create a new PixelType. Any unused channels should be set to 0. + For example: PixelType('r','g','b',0,8,8,8,0); + *************************************************************************/ + PixelType(uint8 C1Name, uint8 C2Name, uint8 C3Name, uint8 C4Name, uint8 C1Bits, uint8 C2Bits, uint8 C3Bits, uint8 C4Bits); + + struct PVR_DLL LowHigh + { + uint32 Low; + uint32 High; + } Part; + + uint64 PixelTypeID; + uint8 PixelTypeChar[8]; + }; + + static const PixelType PVRStandard8PixelType = PixelType('r','g','b','a',8,8,8,8); + static const PixelType PVRStandard16PixelType = PixelType('r','g','b','a',16,16,16,16); + static const PixelType PVRStandard32PixelType = PixelType('r','g','b','a',32,32,32,32); +} + +#endif diff --git a/KREngine/3rdparty/pvrtexlib/include/PVRTextureHeader.h b/KREngine/3rdparty/pvrtexlib/include/PVRTextureHeader.h new file mode 100644 index 0000000..52e318c --- /dev/null +++ b/KREngine/3rdparty/pvrtexlib/include/PVRTextureHeader.h @@ -0,0 +1,586 @@ +#ifndef _PVRTEXTURE_HEADER_H +#define _PVRTEXTURE_HEADER_H + +#include "PVRTextureDefines.h" +#include "PVRTextureFormat.h" +#include "PVRTString.h" +#include "PVRTMap.h" + +namespace pvrtexture +{ + //Wrapper class for PVRTextureHeaderV3, adds 'smart' accessor functions. + class PVR_DLL CPVRTextureHeader + { + protected: + PVRTextureHeaderV3 m_sHeader; //Texture header as laid out in a file. + CPVRTMap > m_MetaData; //Map of all the meta data stored for a texture. + + public: + /******************************************************************************* + * Construction methods for a texture header. + *******************************************************************************/ + /*!*********************************************************************** + @Function CPVRTextureHeader + @Return CPVRTextureHeader A new texture header. + @Description Default constructor for a CPVRTextureHeader. Returns an empty header. + *************************************************************************/ + CPVRTextureHeader(); + + /*!*********************************************************************** + @Function CPVRTextureHeader + @Input fileHeader + @Input metaDataCount + @Input metaData + @Return CPVRTextureHeader A new texture header. + @Description Creates a new texture header from a PVRTextureHeaderV3, + and appends Meta data if any is supplied. + *************************************************************************/ + CPVRTextureHeader( PVRTextureHeaderV3 fileHeader, + uint32 metaDataCount=0, + MetaDataBlock* metaData=NULL); + + /*!*********************************************************************** + @Function CPVRTextureHeader + @Input u64PixelFormat + @Input u32Height + @Input u32Width + @Input u32Depth + @Input u32NumMipMaps + @Input u32NumArrayMembers + @Input u32NumFaces + @Input eColourSpace + @Input eChannelType + @Input bPreMultiplied + @Return CPVRTextureHeader A new texture header. + @Description Creates a new texture header based on individual header + variables. + *************************************************************************/ + CPVRTextureHeader( uint64 u64PixelFormat, + uint32 u32Height=1, + uint32 u32Width=1, + uint32 u32Depth=1, + uint32 u32NumMipMaps=1, + uint32 u32NumArrayMembers=1, + uint32 u32NumFaces=1, + EPVRTColourSpace eColourSpace=ePVRTCSpacelRGB, + EPVRTVariableType eChannelType=ePVRTVarTypeUnsignedByteNorm, + bool bPreMultiplied=false); + + /*!*********************************************************************** + @Function operator= + @Input rhs + @Return CPVRTextureHeader& This header. + @Description Will copy the contents and information of another header into this one. + *************************************************************************/ + CPVRTextureHeader& operator=(const CPVRTextureHeader& rhs); + + /******************************************************************************* + * Accessor Methods for a texture's properties - getters. + *******************************************************************************/ + + /*!*********************************************************************** + @Function getFileHeader + @Return PVRTextureHeaderV3 The file header. + @Description Gets the file header structure. + *************************************************************************/ + const PVRTextureHeaderV3 getFileHeader() const; + + /*!*********************************************************************** + @Function getPixelType + @Return PixelType 64-bit pixel type ID. + @Description Gets the 64-bit pixel type ID of the texture. + *************************************************************************/ + const PixelType getPixelType() const; + + /*!*********************************************************************** + @Function getBitsPerPixel + @Return uint32 Number of bits per pixel + @Description Gets the bits per pixel of the texture format. + *************************************************************************/ + const uint32 getBitsPerPixel() const; + + /*!*********************************************************************** + @Function getColourSpace + @Return EPVRTColourSpace enum representing colour space. + @Description Returns the colour space of the texture. + *************************************************************************/ + const EPVRTColourSpace getColourSpace() const; + + /*!*********************************************************************** + @Function getChannelType + @Return EPVRTVariableType enum representing the type of the texture. + @Description Returns the variable type that the texture's data is stored in. + *************************************************************************/ + const EPVRTVariableType getChannelType() const; + + /*!*********************************************************************** + @Function getWidth + @Input uiMipLevel MIP level that user is interested in. + @Return uint32 Width of the specified MIP-Map level. + @Description Gets the width of the user specified MIP-Map + level for the texture + *************************************************************************/ + const uint32 getWidth(uint32 uiMipLevel=PVRTEX_TOPMIPLEVEL) const; + + /*!*********************************************************************** + @Function getHeight + @Input uiMipLevel MIP level that user is interested in. + @Return uint32 Height of the specified MIP-Map level. + @Description Gets the height of the user specified MIP-Map + level for the texture + *************************************************************************/ + const uint32 getHeight(uint32 uiMipLevel=PVRTEX_TOPMIPLEVEL) const; + + /*!*********************************************************************** + @Function getDepth + @Input uiMipLevel MIP level that user is interested in. + @Return Depth of the specified MIP-Map level. + @Description Gets the depth of the user specified MIP-Map + level for the texture + *************************************************************************/ + const uint32 getDepth(uint32 uiMipLevel=PVRTEX_TOPMIPLEVEL) const; + + /*!*********************************************************************** + @Function getTextureSize + @Input iMipLevel Specifies a MIP level to check, + 'PVRTEX_ALLMIPLEVELS' can be passed to get + the size of all MIP levels. + @Input bAllSurfaces Size of all surfaces is calculated if true, + only a single surface if false. + @Input bAllFaces Size of all faces is calculated if true, + only a single face if false. + @Return uint32 Size in PIXELS of the specified texture area. + @Description Gets the size in PIXELS of the texture, given various input + parameters. User can retrieve the total size of either all + surfaces or a single surface, all faces or a single face and + all MIP-Maps or a single specified MIP level. All of these + *************************************************************************/ + const uint32 getTextureSize(int32 iMipLevel=PVRTEX_ALLMIPLEVELS, bool bAllSurfaces = true, bool bAllFaces = true) const; + + /*!*********************************************************************** + @Function getDataSize + @Input iMipLevel Specifies a mip level to check, + 'PVRTEX_ALLMIPLEVELS' can be passed to get + the size of all MIP levels. + @Input bAllSurfaces Size of all surfaces is calculated if true, + only a single surface if false. + @Input bAllFaces Size of all faces is calculated if true, + only a single face if false. + @Return uint32 Size in BYTES of the specified texture area. + @Description Gets the size in BYTES of the texture, given various input + parameters. User can retrieve the size of either all + surfaces or a single surface, all faces or a single face + and all MIP-Maps or a single specified MIP level. + *************************************************************************/ + const uint32 getDataSize(int32 iMipLevel=PVRTEX_ALLMIPLEVELS, bool bAllSurfaces = true, bool bAllFaces = true) const; + + /*!*********************************************************************** + @Function getNumArrayMembers + @Return uint32 Number of array members in this texture. + @Description Gets the number of array members stored in this texture. + *************************************************************************/ + const uint32 getNumArrayMembers() const; + + /*!*********************************************************************** + @Function getNumMIPLevels + @Return uint32 Number of MIP-Map levels in this texture. + @Description Gets the number of MIP-Map levels stored in this texture. + *************************************************************************/ + const uint32 getNumMIPLevels() const; + + /*!*********************************************************************** + @Function getNumFaces + @Return uint32 Number of faces in this texture. + @Description Gets the number of faces stored in this texture. + *************************************************************************/ + const uint32 getNumFaces() const; + + /*!*********************************************************************** + @Function getOrientation + @Input axis EPVRTAxis type specifying the axis to examine. + @Return EPVRTOrientation Enum orientation of the axis. + @Description Gets the data orientation for this texture. + *************************************************************************/ + const EPVRTOrientation getOrientation(EPVRTAxis axis) const; + + /*!*********************************************************************** + @Function isFileCompressed + @Return bool True if it is file compressed. + @Description Returns whether or not the texture is compressed using + PVRTexLib's FILE compression - this is independent of + any texture compression. + *************************************************************************/ + const bool isFileCompressed() const; + + /*!*********************************************************************** + @Function isPreMultiplied + @Return bool True if texture is premultiplied. + @Description Returns whether or not the texture's colour has been + pre-multiplied by the alpha values. + *************************************************************************/ + const bool isPreMultiplied() const; + + /*!*********************************************************************** + @Function getMetaDataSize + @Return const uint32 Size, in bytes, of the meta data stored in the header. + @Description Returns the total size of the meta data stored in the header. + This includes the size of all information stored in all MetaDataBlocks. + *************************************************************************/ + const uint32 getMetaDataSize() const; + + /*!*********************************************************************** + @Function getOGLFormat + @Modified internalformat + @Modified format + @Modified type + @Description Gets the OpenGL equivalent values of internal format, format + and type for this texture. This will return any supported + OpenGL texture values, it is up to the user to decide if + these are valid for their current platform. + *************************************************************************/ + const void getOGLFormat(uint32& internalformat, uint32& format, uint32& type) const; + + /*!*********************************************************************** + @Function getOGLESFormat + @Modified internalformat + @Modified format + @Modified type + @Description Gets the OpenGLES equivalent values of internal format, + format and type for this texture. This will return any + supported OpenGLES texture values, it is up to the user + to decide if these are valid for their current platform. + *************************************************************************/ + const void getOGLESFormat(uint32& internalformat, uint32& format, uint32& type) const; + + /*!*********************************************************************** + @Function getD3DFormat + @Return const uint32 + @Description Gets the D3DFormat (up to DirectX 9 and Direct 3D Mobile) + equivalent values for this texture. This will return any + supported D3D texture formats, it is up to the user to + decide if this is valid for their current platform. + *************************************************************************/ + const uint32 getD3DFormat() const; + + /*!*********************************************************************** + @Function getDXGIFormat + @Return const uint32 + @Description Gets the DXGIFormat (DirectX 10 onward) equivalent values + for this texture. This will return any supported DX texture + formats, it is up to the user to decide if this is valid + for their current platform. + *************************************************************************/ + const uint32 getDXGIFormat() const; + + /*!*********************************************************************** + * Accessor Methods for a texture's properties - setters. + *************************************************************************/ + + /*!*********************************************************************** + @Function setPixelFormat + @Input uPixelFormat The format of the pixel. + @Description Sets the pixel format for this texture. + *************************************************************************/ + void setPixelFormat(PixelType uPixelFormat); + + /*!*********************************************************************** + @Function setColourSpace + @Input eColourSpace A colour space enum. + @Description Sets the colour space for this texture. Default is lRGB. + *************************************************************************/ + void setColourSpace(EPVRTColourSpace eColourSpace); + + /*!*********************************************************************** + @Function setChannelType + @Input eVarType A variable type enum. + @Description Sets the variable type for the channels in this texture. + *************************************************************************/ + void setChannelType(EPVRTVariableType eVarType); + + /*!*********************************************************************** + @Function setOGLFormat + @Input internalformat + @Input format + @Input type + @Return bool Whether the format is valid or not. + @Description Sets the format of the texture to PVRTexLib's internal + representation of the OGL format. + *************************************************************************/ + bool setOGLFormat(const uint32& internalformat, const uint32& format, const uint32& type); + + /*!*********************************************************************** + @Function setOGLESFormat + @Input internalformat + @Input format + @Input type + @Return bool Whether the format is valid or not. + @Description Sets the format of the texture to PVRTexLib's internal + representation of the OGLES format. + *************************************************************************/ + bool setOGLESFormat(const uint32& internalformat, const uint32& format, const uint32& type); + + /*!*********************************************************************** + @Function setD3DFormat + @Return bool Whether the format is valid or not. + @Description Sets the format of the texture to PVRTexLib's internal + representation of the D3D format. + *************************************************************************/ + bool setD3DFormat(const uint32& DWORD_D3D_FORMAT); + + /*!*********************************************************************** + @Function setDXGIFormat + @Return bool Whether the format is valid or not. + @Description Sets the format of the texture to PVRTexLib's internal + representation of the DXGI format. + *************************************************************************/ + bool setDXGIFormat(const uint32& DWORD_DXGI_FORMAT); + + /*!*********************************************************************** + @Function setWidth + @Input newWidth The new width. + @Description Sets the width. + *************************************************************************/ + void setWidth(uint32 newWidth); + + /*!*********************************************************************** + @Function setHeight + @Input newHeight The new height. + @Description Sets the height. + *************************************************************************/ + void setHeight(uint32 newHeight); + + /*!*********************************************************************** + @Function setDepth + @Input newDepth The new depth. + @Description Sets the depth. + *************************************************************************/ + void setDepth(uint32 newDepth); + + /*!*********************************************************************** + @Function setNumArrayMembers + @Input newNumMembers The new number of members in this array. + @Description Sets the depth. + *************************************************************************/ + void setNumArrayMembers(uint32 newNumMembers); + + /*!*********************************************************************** + @Function setNumMIPLevels + @Input newNumMIPLevels New number of MIP-Map levels. + @Description Sets the number of MIP-Map levels in this texture. + *************************************************************************/ + void setNumMIPLevels(uint32 newNumMIPLevels); + + /*!*********************************************************************** + @Function setNumFaces + @Input newNumFaces New number of faces for this texture. + @Description Sets the number of faces stored in this texture. + *************************************************************************/ + void setNumFaces(uint32 newNumFaces); + + /*!*********************************************************************** + @Function setOrientation + @Input eAxisOrientation Enum specifying axis and orientation. + @Description Sets the data orientation for a given axis in this texture. + *************************************************************************/ + void setOrientation(EPVRTOrientation eAxisOrientation); + + /*!*********************************************************************** + @Function setIsFileCompressed + @Input isFileCompressed Sets file compression to true/false. + @Description Sets whether or not the texture is compressed using + PVRTexLib's FILE compression - this is independent of + any texture compression. Currently unsupported. + *************************************************************************/ + void setIsFileCompressed(bool isFileCompressed); + + /*!*********************************************************************** + @Function isPreMultiplied + @Return isPreMultiplied Sets if texture is premultiplied. + @Description Sets whether or not the texture's colour has been + pre-multiplied by the alpha values. + *************************************************************************/ + void setIsPreMultiplied(bool isPreMultiplied); + + /*!*********************************************************************** + Meta Data functions - Getters. + *************************************************************************/ + + /*!*********************************************************************** + @Function isBumpMap + @Return bool True if it is a bump map. + @Description Returns whether the texture is a bump map or not. + *************************************************************************/ + const bool isBumpMap() const; + + /*!*********************************************************************** + @Function getBumpMapScale + @Return float Returns the bump map scale. + @Description Gets the bump map scaling value for this texture. If the + texture is not a bump map, 0.0f is returned. If the + texture is a bump map but no meta data is stored to + specify its scale, then 1.0f is returned. + *************************************************************************/ + const float getBumpMapScale() const; + + /*!*********************************************************************** + @Function getBumpMapOrder + @Return CPVRTString Returns bump map order relative to rgba. + @Description Gets the bump map channel order relative to rgba. For + example, an RGB texture with bumps mapped to XYZ returns + 'xyz'. A BGR texture with bumps in the order ZYX will also + return 'xyz' as the mapping is the same: R=X, G=Y, B=Z. + If the letter 'h' is present in the string, it means that + the height map has been stored here. + Other characters are possible if the bump map was created + manually, but PVRTexLib will ignore these characters. They + are returned simply for completeness. + *************************************************************************/ + const CPVRTString getBumpMapOrder() const; + + /*!*********************************************************************** + @Function getNumTextureAtlasMembers + @Return int Returns number of sub textures defined by meta data. + @Description Works out the number of possible texture atlas members in + the texture based on the w/h/d and the data size. + *************************************************************************/ + const int getNumTextureAtlasMembers() const; + + /*!*********************************************************************** + @Function getTextureAtlasData + @Return float* Returns a pointer directly to the texture atlas data. + @Description Returns a pointer to the texture atlas data. + *************************************************************************/ + const float* getTextureAtlasData() const; + + /*!*********************************************************************** + @Function getCubeMapOrder + @Return CPVRTString Returns cube map order. + @Description Gets the cube map face order. Returned string will be in + the form "ZzXxYy" with capitals representing positive and + small letters representing negative. I.e. Z=Z-Positive, + z=Z-Negative. + *************************************************************************/ + const CPVRTString getCubeMapOrder() const; + + /*!*********************************************************************** + @Function getBorder + @Input uiBorderWidth + @Input uiBorderHeight + @Input uiBorderDepth + @Description Obtains the border size in each dimension for this texture. + *************************************************************************/ + void getBorder(uint32& uiBorderWidth, uint32& uiBorderHeight, uint32& uiBorderDepth) const; + + /*!*********************************************************************** + @Function getMetaData + @Input DevFOURCC + @Input u32Key + @Return pvrtexture::MetaDataBlock A copy of the meta data from the texture. + @Description Returns a block of meta data from the texture. If the meta data doesn't exist, a block with data size 0 will be returned. + *************************************************************************/ + const MetaDataBlock getMetaData(uint32 DevFOURCC, uint32 u32Key) const; + + /*!*********************************************************************** + @Function hasMetaData + @Input DevFOURCC + @Input u32Key + @Return bool Whether or not the meta data bock specified exists + @Description Returns whether or not the specified meta data exists as + part of this texture header. + *************************************************************************/ + bool hasMetaData(uint32 DevFOURCC, uint32 u32Key) const; + + /*!*********************************************************************** + @Function getMetaDataMap + @Return MetaDataMap* A direct pointer to the MetaData map. + @Description A pointer directly to the Meta Data Map, to allow users to read out data. + *************************************************************************/ + const MetaDataMap* const getMetaDataMap() const; + + /*!*********************************************************************** + Meta Data functions - Setters. + *************************************************************************/ + + /*!*********************************************************************** + @Function setBumpMap + @Input bumpScale Floating point "height" value to scale the bump map. + @Input bumpOrder Up to 4 character string, with values x,y,z,h in + some combination. Not all values need to be present. + Denotes channel order; x,y,z refer to the + corresponding axes, h indicates presence of the + original height map. It is possible to have only some + of these values rather than all. For example if 'h' + is present alone it will be considered a height map. + The values should be presented in RGBA order, regardless + of the texture format, so a zyxh order in a bgra texture + should still be passed as 'xyzh'. Capitals are allowed. + Any character stored here that is not one of x,y,z,h + or a NULL character will be ignored when PVRTexLib + reads the data, but will be preserved. This is useful + if you wish to define a custom data channel for instance. + In these instances PVRTexLib will assume it is simply + colour data. + @Description Sets a texture's bump map data. + *************************************************************************/ + void setBumpMap(float bumpScale, CPVRTString bumpOrder="xyz"); + + /*!*********************************************************************** + @Function setTextureAtlas + @Input pAtlasData Pointer to an array of atlas data. + @Input dataSize Number of floats that the data pointer contains. + @Description Sets the texture atlas coordinate meta data for later display. + It is up to the user to make sure that this texture atlas + data actually makes sense in the context of the header. It is + suggested that the "generateTextureAtlas" method in the tools + is used to create a texture atlas, manually setting one up is + possible but should be done with care. + *************************************************************************/ + void setTextureAtlas(float* pAtlasData, uint32 dataSize); + + /*!*********************************************************************** + @Function setCubeMapOrder + @Input cubeMapOrder Up to 6 character string, with values + x,X,y,Y,z,Z in some combination. Not all + values need to be present. Denotes face + order; Capitals refer to positive axis + positions and small letters refer to + negative axis positions. E.g. x=X-Negative, + X=X-Positive. It is possible to have only + some of these values rather than all, as + long as they are NULL terminated. + NB: Values past the 6th character are not read. + @Description Sets a texture's bump map data. + *************************************************************************/ + void setCubeMapOrder(CPVRTString cubeMapOrder="XxYyZz"); + + /*!*********************************************************************** + @Function setBorder + @Input uiBorderWidth + @Input uiBorderHeight + @Input uiBorderDepth + @Return void + @Description Sets a texture's border size data. This value is subtracted + from the current texture height/width/depth to get the valid + texture data. + *************************************************************************/ + void setBorder(uint32 uiBorderWidth, uint32 uiBorderHeight, uint32 uiBorderDepth); + + /*!*********************************************************************** + @Function addMetaData + @Input MetaBlock Meta data block to be added. + @Description Adds an arbitrary piece of meta data. + *************************************************************************/ + void addMetaData(const MetaDataBlock& MetaBlock); + + /*!*********************************************************************** + @Function removeMetaData + @Input DevFourCC + @Input u32Key + @Return void + @Description Removes a specified piece of meta data, if it exists. + *************************************************************************/ + void removeMetaData(const uint32& DevFourCC, const uint32& u32Key); + }; +}; + +#endif diff --git a/KREngine/3rdparty/pvrtexlib/include/PVRTextureUtilities.h b/KREngine/3rdparty/pvrtexlib/include/PVRTextureUtilities.h new file mode 100644 index 0000000..20da6db --- /dev/null +++ b/KREngine/3rdparty/pvrtexlib/include/PVRTextureUtilities.h @@ -0,0 +1,157 @@ +#ifndef _PVRTEXTURE_UTILITIES_H +#define _PVRTEXTURE_UTILITIES_H + +#include "PVRTextureFormat.h" +#include "PVRTexture.h" + +namespace pvrtexture +{ + /*!*********************************************************************** + @Function Resize + @Input sTexture + @Input u32NewWidth + @Input u32NewHeight + @Input u32NewDepth + @Input eResizeMode + @Return bool Whether the method succeeds or not. + @Description Resizes the texture to new specified dimensions. Filtering + mode is specified with "eResizeMode". + *************************************************************************/ + bool PVR_DLL Resize(CPVRTexture& sTexture, const uint32& u32NewWidth, const uint32& u32NewHeight, const uint32& u32NewDepth, const EResizeMode eResizeMode); + + /*!*********************************************************************** + @Function Rotate90 + @Input sTexture + @Input eRotationAxis + @Input bForward + @Return bool Whether the method succeeds or not. + @Description Rotates a texture by 90 degrees around the given axis. bForward controls direction of rotation. + *************************************************************************/ + bool PVR_DLL Rotate90(CPVRTexture& sTexture, const EPVRTAxis eRotationAxis, const bool bForward); + + /*!*********************************************************************** + @Function Flip + @Input sTexture + @Input eFlipDirection + @Return bool Whether the method succeeds or not. + @Description Flips a texture in a given direction. + *************************************************************************/ + bool PVR_DLL Flip(CPVRTexture& sTexture, const EPVRTAxis eFlipDirection); + + /*!*********************************************************************** + @Function Border + @Input sTexture + @Input uiBorderX + @Input uiBorderY + @Input uiBorderZ + @Return bool Whether the method succeeds or not. + @Description Adds a user specified border to the texture. + *************************************************************************/ + bool PVR_DLL Border(CPVRTexture& sTexture, uint32 uiBorderX, uint32 uiBorderY, uint32 uiBorderZ); + + /*!*********************************************************************** + @Function PreMultiplyAlpha + @Input sTexture + @Return bool Whether the method succeeds or not. + @Description Pre-multiplies a texture's colours by its alpha values. + *************************************************************************/ + bool PVR_DLL PreMultiplyAlpha(CPVRTexture& sTexture); + + /*!*********************************************************************** + @Function Bleed + @Input sTexture + @Return bool Whether the method succeeds or not. + @Description Allows a texture's colours to run into any fully transparent areas. + *************************************************************************/ + bool PVR_DLL Bleed(CPVRTexture& sTexture); + + /*!*********************************************************************** + @Function SetChannels + @Input sTexture + @Input uiNumChannelSets + @Input eChannels + @Input pValues + @Return bool Whether the method succeeds or not. + @Description Sets the specified number of channels to values specified in pValues. + *************************************************************************/ + bool PVR_DLL SetChannels(CPVRTexture& sTexture, uint32 uiNumChannelSets, EChannelName *eChannels, uint32 *pValues); + bool PVR_DLL SetChannelsFloat(CPVRTexture& sTexture, uint32 uiNumChannelSets, EChannelName *eChannels, float *pValues); + + /*!*********************************************************************** + @Function CopyChannels + @Input sTexture + @Input sTextureSource + @Input uiNumChannelCopies + @Input eChannels + @Input eChannelsSource + @Return bool Whether the method succeeds or not. + @Description Copies the specified channels from sTextureSource into sTexture. + sTextureSource is not modified so it is possible to use the + same texture as both input and output. When using the same + texture as source and destination, channels are preserved + between swaps (e.g. copying Red to Green and then Green to Red + will result in the two channels trading places correctly). + Channels in eChannels are set to the value of the channels + in eChannelSource. + *************************************************************************/ + bool PVR_DLL CopyChannels(CPVRTexture& sTexture, const CPVRTexture& sTextureSource, uint32 uiNumChannelCopies, EChannelName *eChannels, EChannelName *eChannelsSource); + + /*!*********************************************************************** + @Function GenerateNormalMap + @Input sTexture + @Input fScale + @Input sChannelOrder + @Return bool Whether the method succeeds or not. + @Description Generates a Normal Map from a given height map. + Assumes the red channel has the height values. + By default outputs to red/green/blue = x/y/z, + this can be overridden by specifying a channel + order in sChannelOrder. The channels specified + will output to red/green/blue/alpha in that order. + So "xyzh" maps x to red, y to green, z to blue + and h to alpha. 'h' is used to specify that the + original height map data should be preserved in + the given channel. + *************************************************************************/ + bool PVR_DLL GenerateNormalMap(CPVRTexture& sTexture, const float fScale, CPVRTString sChannelOrder); + + /*!*********************************************************************** + @Function GenerateMIPMaps + @Input sTexture + @Input eFilterMode + @Input uiMIPMapsToDo + @Return bool Whether the method succeeds or not. + @Description Generates MIPMaps for a source texture. Default is to + create a complete MIPMap chain, however this can be + overridden with uiMIPMapsToDo. + *************************************************************************/ + bool PVR_DLL GenerateMIPMaps(CPVRTexture& sTexture, const EResizeMode eFilterMode, const uint32 uiMIPMapsToDo=PVRTEX_ALLMIPLEVELS); + + /*!*********************************************************************** + @Function ColourMIPMaps + @Input sTexture + @Return bool Whether the method succeeds or not. + @Description Colours a texture's MIPMap levels with artificial colours + for debugging. MIP levels are coloured in the order: + Red, Green, Blue, Cyan, Magenta and Yellow + in a repeating pattern. + *************************************************************************/ + bool PVR_DLL ColourMIPMaps(CPVRTexture& sTexture); + + /*!*********************************************************************** + @Function Transcode + @Input sTexture + @Input ptFormat + @Input eChannelType + @Input eColourspace + @Input eQuality + @Input bDoDither + @Return bool Whether the method succeeds or not. + @Description Transcodes a texture from its original format into a newly specified format. + Will either quantise or dither to lower precisions based on bDoDither. + uiQuality specifies the quality for PVRTC and ETC compression. + *************************************************************************/ + bool PVR_DLL Transcode(CPVRTexture& sTexture, const PixelType ptFormat, const EPVRTVariableType eChannelType, const EPVRTColourSpace eColourspace, const ECompressorQuality eQuality=ePVRTCNormal, const bool bDoDither=false); +}; +#endif //_PVRTEXTURE_UTILTIES_H + diff --git a/KREngine/3rdparty/pvrtexlib/include/PVRTextureVersion.h b/KREngine/3rdparty/pvrtexlib/include/PVRTextureVersion.h new file mode 100644 index 0000000..38ca378 --- /dev/null +++ b/KREngine/3rdparty/pvrtexlib/include/PVRTextureVersion.h @@ -0,0 +1,7 @@ +#ifndef PVRTEXLIBVERSION_H +#define PVRTEXLIBVERSION_H +#define PVRTLMAJORVERSION 4 +#define PVRTLMINORVERSION 6 +#define PVRTLSTRINGVERSION "4.6" +#define PVRTLVERSIONDESCRIPTOR "" //"BETA" //"ALPHA" //"ENGINEERING DROP" +#endif diff --git a/KREngine/3rdparty/pvrtexlib/static_osx/libPVRTexLib.a b/KREngine/3rdparty/pvrtexlib/static_osx/libPVRTexLib.a new file mode 100644 index 0000000000000000000000000000000000000000..2615b1f07a7cf93387eb45912738914f78cf6576 GIT binary patch literal 2265832 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

DNOP)z|o0+NTS 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%0DSR^6GC?6xp5)#bEur?V-S1Pi?p~KE%GU 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 z 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

8j z)^(}rBS8$ujTAA$#R#mau8%EmvLj-II(JM76&a({HNGgHO4Gp@C`#-Y=AA|84raY2 z+k_syB_@4NXr>R3&e0)VFh+k7k&OL?x>)QElVg%Aba^rFns$1#2viK3sLdnb1v9nY z>N`A`_C>cESvPxnHO;)mmBp9_qUBPK7Gvnpp~=MzSxovi=e#Psp2}N2ht{WZ-j9*nsJK}jjH_GY zjHwe}V6Sbg_Rg0bLQD_Q{YWUb;W^)48+u)kQ7bl|SR!JNYN(rCELSliHaf4#G#0uY zZWx_*ERD)$$VGgt(P@mSbp;w`m_lVSW7Mr#OgTUk#*7#(q{!LwtG%)p(F7tJ)ud`8 zVru!l*qB<+jhAK~$;xnZ>KddZPK$^oMU=b@i*gbuF)B9CFvaA$>O(p-E=y#RK%u=L zv$-Q%>^g<>3YDy>vAT8!cOWQcQuamnpvai|QjU&Y%xs~)8xoAlEeaZ2k1}Ck$T4R7 zm{~Sz@Y-rXCtI|BWM0omTCZS-lq9`y(HhBAxsPC=^+kh(2{DN3%cilRKba<I1^d<;+1<}xzdrtjloyC7!2G41F!^XV_T_@eF2X3*>y zETStQHj5YwLPHls4>0dN(Mv;P&xe8-v+Eldd9)7IG_q)!UrdX)lIYc2wqT-bKv!8! zCGCHU87^T9nl0v_2+L+W)YzfcQ9g!*#GHRr-`+oOs>6REiJ=&5M$0nwuaY= zVv%W72#l&z97!44Jr;93c4w>SR9Dc(N|F-8Y&}IQm&6rs7L%4RiDSlGlOF;{1a z>paIG+-Y=p8a!B5mDP}_la)R_!31ICL8B~O>oLa}WKH-V-o&f@ix%O6J;ooi5-T?d zu-dv?XaP8?rmjregp_Zv)o(0in?;7`N;0xu`iA#yM&~Edq9#$rayE&m+A=kAVXN;9 z=1bh{m`mIw;>KR=$~=d5CryT~aZRWRSrU%=gySwk@M>Ybo}urG$qrp3htOOdn(Z`k z8*h0Vk&O_O(0rF1lLYgwfl2I%Gse=_JTI}?42xI}=<7B-%;PBc+0jQLa{n*fy5SPV zkftSgaGAmb zOEg+^^_cMqb>t54iBP|EMWO|Ka%D=ae1$V4hn6UmN$m)o#;{^GsQo6{fjW_xkG@p2 zU^n4o!bD7su`|OO9!-$4wPGzua*Q*SUJl0)VVdkP%M_^?=t5A9!n>kw?3B72o2E~v zvu+%UyXQiU<2@>jd$FY(*1I$v>>{}f=5AB&CLF_Ge9+F23@xf@RY}yMadXvVSr_hP z2}h4njGHR2F}d-Q(NLe79&2U?Gc?m&Eig8kTU3i%Qy|4;sB1S27%h$HN*-zy-?3&j zvl5xiar_B@=r!>$dP`^Wea{^^%4mMq6-i%#(Pc_1v2a&Rl$c2ZG1RPh&5|gUxUEFr zWWX_o6Pue+;yf27c-mSQgFh-_{a2nqxl!K#?Jm*l-Y-sOTU%IFYvkjBP z=0%Gp&jR^qGtMP9Ct)vPqpXcKPb)bFw#nlsTzvA`i2X%UN1c+Tm=~oetpl%!Po!W8 zTNM&N(RHWAM#R!OuplESL=aXdOg)nNrM;n{Y<__)yU61$bCh|@#8!>dmRaJNT{p*5 zNX9DK3zXx~=Zu8iX92i7ov_p$=PoF9i%%$_uf&NpOePXCMvX(N`@B*-Dw|nmb484z zvigFVk>yNS(0FmV&{^>d9gjnt3WygCt1MJ9?g&fMXj8mf;-PVFFqt=vx%hHky}<#QH&qXl(a4$>TfaQ)HhVmfz4rI zU0H?0F%Dl8!igk4FVi@xpit}9$;7?6bkaq~#n|jfT=9Ci@@O%juM0t=4dxj62aWhL zkZriG5?%BKnFbQ=2@^oSG_{)6aYGV`_`rPO=0t@dn#6p*Kw-onOIVn!M1@J*M|FcH zMyr&KqXBQ0m*{4a!hvbyjBe1$>NC8!-#BTswolm5}L_!S?!UIhf*bgaNG>iQ~Z7d?Y3r*?cG_8QFX+#*R#8 zgQ0AXl%vNum<^R822~txu*V8<3)QTVUkB?oS8J%q_DPVHJoE<^2BPnj7Q~(3iCY9o zoDL~ELj?Iu={YWOGDebw$r#BwnF)!LF_I)q#z-!ciLVp=SMy?(G+y1tQ5NDw`>)<4 zl>JzGQzmV*H#N{!7%KsK?SVeZXtMsx8jagG}ySn!LMbe$B14t_CJw z_zq{sk{yO*#V1AODXI1uPs8*@IgXPwu_!4TCl>G*nfnB@um1ScHk@+o!PPrD3%I!S zvNFK~pBk;LC~K&2c;-}#@8)#4()03ba0@VDea9I|n(h!Gts_@g|CRqS?Tv6{hpwU~S?V|yA1}|EU^41h@nV=aTih5OZbIDxPgnf^@UBSoTxH9|UBmpQ z+4a~Ob5=$#N%X_tWIw=W!?sFjPX!A-;~rv^5Hn?g9N4A=an=YRGxEOxCS~pS4b`xE zkm?F&Lg$rqCUn;$X*WDPvB`~uL>-pyOOI@tj$6?6V;g8ktK)s1SfIle6%-eG=6Gt7 z?X(oFRPIRHz4%BqE=DESZ*vi`ps~2YBR=L_UsH`sRnZr7%*F}DL{7#2evya1wU}&X z^F*4o!Jr*}B%3Iu>A>aLs^rEpk&DZl$&pjO^6NyyX`4|ATAX6I#23qxO948VO?)_1 zHkI7!n2( zoNUw<;t+=PgE%yJKY|>aIvSj4MV}po2#tGp(M@c#&5--3<8VjPSv_60VN$U|vxn*K zYmB5qGpgAJuUQ-%bayzREGkoI9!@ePilm!{=~g5D3HhI}Iw)>vwPdNZt^?5wb9sl_fby!FxSe@IFR{<7 zZcMHXDL&7G9UBYujuqUuYlOLs=mW_XSHzPluAK6dtR`K&akx$A=}3A86{7>`X{{-0 zrUUT|`be_zsw0$&B;yUMZj4Y2l8l&(28>ikl8j2H4tnK@PNb4-k1T3OB&(0EDMQJn zPWjfWG>N1D(d%`ZMD$cRR%(R(WwPQ>9>rA8sDdqVQe!W#x=~x}iIV?!?~ayK=2jt8 zp`{7;?rhY##$r23x2~Lzb#U}zEo5$_TX~_ibKRkTq6v-KEOoqU*!FotlWfV&9bq5M zQuJlPGCU)GQJ&Nfi}Ci72G}cz_trosaF)z3F{_1W1-E3n=>k5ADa}~PGsUX{hq>`D zss^&?K--vG)I^6kGf&+YUr9ZvLk;fE7p$B{TFM9aj}*aGT!J3w;HXDhvMC%Fw7f6GffT6 z-IY$~blta0VRlOIDzv#N09z`Rup@KkHr7{8tg7~kJ=4T2t1dmunU|iWx1%hLMB_}W>sp`nvUXE=K6P_D7fv~k$o%bE>s z8kyuMk@aJ)W(hT>t~PeL&_X(sG-|J0rxvlU0q;nwA=~EY;^1gbv+57+UqX>JvNJtc zisF@(8NPBJH?Pq#hl?)>&Gz8?KwdN+w_?cTFNxfgz^1w+d@ng{$C)#V-u@olsUpk= z^gF#GikWZ^EuIoaHW3QRM>dfK$wxL30m(!jii;UAwTrH&sK%0%ajPd*oOrboD^9%1 zh!rPZ9Yn<$rX7LPpfOw@g7l6>ZC3oyoES5bN%<2;GO2*XkxXhJP9z834n&u%tBeE9 zwER+|G3hAO%d*}0C+ab-=961e2ZLNsUZx3~@X(4r4++JKQT-+zJsuPD=I9_3^XBOw z6Z7Uuib-BbT$)s-u{}-BgrX-sYCW^5Lsn#*y=LvSsN)AbahQr3D}v+2o(CmN=KsZu zxtIK`DG@`Hz8|S68V|1$F*F$mUyPy2n268Ns0qBLlMEmOfYVgu<|>OR78_y42~TVW_4(o&G;|CMyHC8ld3q$0^_PG zne6EJjQ=8h=+Ut~*Pcj5--H))g7eA}>zfv$90qdB+=kz16i%NtHS) zTf}rU;c7=YrsImxu6e==%fA9YVhI zEMW%eHbt~89Nlq^?$h!<5&lm!(7-g|gqj2o{r%7IIweA!U^e{{3Lvasbn8TuBoY=t z|85$ig=vHY#e2MH^J^T|c34qXm)u+h6;!Z~8_kARrdwvZ0Uw`!ToqpLGK)74ZmyK+ zASu(k4iR}Iq0Rj;mZ(ac<6)y4>gEaoURtMRgNmDEOd+$wF=r+fGgnvY(mFYNKsaAS zTsdVV88?rLs+Ey)ih8E1rlMxq$f{MoYzNg!w%T<1iOmDKs*)jsa#)y>W@Ne0hXeu% ztY+?rq+xE(dE#>mb+slevva~MDWQ5{btB}enDc8Z#-ehfa+0mf33UaCPkf*bZwr#< zuXPRci_5%K2_l-Lhdq@BuW5>740*sk*rw_DIFlEzZ>1McLE!U>MPFwIs`7FZikw<} zVwJad^f7bjbH`A4Shned@LErt?rXE0Aq2aD_`>Dv*`fm*@;j_d-eC{DBTFAo7Z@ih z#GgrSw73)+FX7qzZZ2cG_;%q8e8pIMaaW8$(@e>zu`(47HP4g`H_cR~FiECNiT-SC z_hEQpC0)oei#Z%&o(6ob%Ot;{oXI6SDL}slmqSt zn)Wq$XV9c~BfuorxWs2t=k)-XvZf6{qdv(wpTvW`tN-j~T%{|FTW+uH-q@bxn=Ql_60a({jX% z6tgVxBAJ#4AN8r9?<_7i9iTZC;awZ!&=uoHh(oiy%g|Vv%&GBIdRb^ka23eS5zM&Yqo4e)CbKHYon1E19kVq&w})m|W9AVM zBTJ!a+8CjRR<))TRU4~@5uioEp0-}kpz1Ci7wt76%B#Kxm2a{jsjrFfEO^Zh9J3SWry4wGF2QSNOyiVR$Jn9a&_#i-F>u^|m}C5xIyA zF9esLW|my|YLdKQA9<_PyTP(DDHU2W3I)8V zy4F!WyKz45Ji29TNdji;@PrtTlOGN-1k-DnOR#08*zCl^6nC@Pe#3|$Mi-euR`O!WWA#|JO2A3wiuU!6IP6_KSW-y`TxLmMC04J zY_<}-)LVRl2lHaFwqG8n2%&Xc9GcJ@QFJtMU!4+hsu)>qLLwXAF?_KHk6+{>n!TuW zByIrW{R~xg6&81ir`}#M6QA>TJMs3>a{8*b%`s8DX2>)!D-<{mn?2=Kkcq-{5qxNu zLA)1hjt6hhD3_lQf~6u(LEx^({#CWNx~?|lW3Q+fB~L3#>*RYlJQex*b@ME4gcO*E zh!1{5k{8rg^NTz*29ZA4(8LxTy#tC~3Ttr}RnHSAI+%jSR#xK0O2+g_@Q$rC`eFC6 zrl1z@?Uqv1;%1LYrFu_cc7&S1ds+ zkM=Nn6lfj>J|QxLFKESCo4p1fsTx_=RO>|nc`_b9!Qw_g#M{TlPRyc?T~;wZ8xNw^ z#r88l3t!Cj`u>uSFRB-n!AKXK94rgVnrh3dN~;>c1hLAhNimR4gyO0;K2%E9yyD7} zFs7iLJ5*>cF5K45#RL8?t;@v&eZEG}Qgw?K5-xh3ZL|-vt0zLM37`7;qhxa4h9+tR z@%0jszRp8rLwq|0o!?W|h)+w1gD;DlZUb``#@&MP;izr|6Fxm4jRy|0pDV3H-_f9DN>p*bw{ z(XqO69;+7GKq#MG@21@NTMZoSV^#J5v#OKXci~}PX_OtMUNzs+u;uVcd&$U!e1|jZp2n3B$VAYcjL0 z0R$l#ZuUY?(>TLXXpM!G#q_cRw@nwj@wCljBC@rdb3T-TBN6Godd zvyxMhE6_Sah1OgETfeB{CD&E5GNCzm z8tUs%Te_A?HgDOXs_zbsJxmn;K)pe1Xii_YD`pBntDwvs|mx^;y zLp>&&ajaO2=@Lb_1(J;!KAlXIyYPu+Ldwu7rj9aecy!z5&dYH(!8?}>ox*v?mZAJt z%$tX$1&IfSQ>>uJB}+IVNkTKSoQsfe5plk-e2e54EurFxgv7C9jyYr07}6y9v|}(x zI{Avww?i+5IEpM>beF}%k!;??M^hsuwOhOX6h|l-bCDeuYhfYO=q{#Zrnjo0u4x9| zK^;@T8F?ikl8g~LB9bCvc0eQ=toMj;oWW5F6sGmN(5Jjo!bqo>u_ zk@GtoIV-0TdrfqmS+73jU}h$LN2F~u)r?A9Xpyw3XAd1;>U&CTdec!Z&NoJCYAG~Z z`lK>%A<2zG%9qJdjEaaOMONa%`lyL28yPQ#epEbaj-gd>BqskA1L&?P_b_8KM==k7 zDm|1b?C59;9$nQ7_gZjFBEM#8NtKDYdSq72B|sFF>FAH5(nh|qt3;C7hL(%n-@>NZ z*h<1jx=ed(0v@^1^F@SaqqWS;nI&i&lP(;YudK|*+naERE}Mk5-^?%^iLrrAiMN=ooSGNEWjUxviZS>Li z)W(UPP!Y)=jbCCW4I>*17P#0@)^$j{F@Q>(R6pd%{GId!-6C&p172822M^Af;$S{f zfsf3Z>AYr66fvin5*Y+tmiWxYNbyw>xerf$OKu`3+?_}ed1!>%ErdpG*9HAVJ5%!#8D+?fu#r}|X3#I%52G!Lt|9*OZ`AK zj2zog+OWW3nH(%d6P?9#(Dg7iOrI`>wUIin3}-O&VP#%1!_!z$EBm~0U#Pg!R29iH zq(j?>`n1fFGDIMa))>Je|Gs1UL00B~@SiPvSl;mALx&H|9xz~NrVRly*p`_&EGK8c z0Gll{S5n`0nN2M6vAi5o$J{KIt!F>J(8Xf0rozXHA7L>)48RY6U&rsTcryQQ1<~TK zo>6myr-gng6o@~!+Y75t`7GU~&Pk78!3r^;Q~fJ0#E>vkLY zxtLECM_Pr9P}#JM^>%sl0^?NjSS=!D6BE`M*MiTZ58$ zi%=@BtGUJNux%9icPJ~nsGEETyV#W{w>743Nc$;RwYPnBz2m1T7W`+mtWWzXJ*2i5 z21Z+bXS)OlfpHar-jIH%x{ilf}s6und`fsMD02?;she_hFtwenCIJ}2= zOja#%*;5RsOAfkBD&A%I>!>`+8eJ(J`vK0hFSYN1?iuUWWe% z^q0;IvsZn-u!6~2p*$SATJ*Xv8Yrhr1%{V z7#M)$h0+IfEGDFY2MXZty)}3g`IwNVtaNPca<#|>QXG}$FWuTTc(gZCrA#1Iki6j7 z+Vg6W3qb-FC`rCQ?OvIW&Lp_+)n}x0zQ6F^uE8U{7atI8wpXfSK=p#-Ugpz^q&h?D z>g6rXC-(97(sBMS{;_GnBYlI1`{o7hOIsUK?MqwcrYsFAD}xTdyQ@9e*4Li5*12@g zT!d`2D=Q(+*EXa&m#!@=-vP0|7G4`i`70hy8|ANgxNE+@_rw0t-HBc6(zSC_9ZNR{ zkM%F5QfP4=a`D8&u-;75J)QKM<9a|?LptszHO)wyky zzuyDM&R%8Z8hhS4=hFR@jcxYe$9<9R26$0c&f|HVJ?}N=(j(+NFv`DRS67+U*GBoh zySnH5`|U#d4|hjKccsk*&1)z#C|=78E^Vb;9;R|CE4Sxur_61n{3t88m5r^z_E#HkE)2WX4vYfKC74?c7zhTeY zOkpPxlbR!M)6(q}+d-}5Sh`OnAEe~>P;4|--X;pzglTn+Q$;EVy4ur&4z;J~teE0* zTa+c)7}JhHEhm+0S_*;(Q=GPT`%4)ZJtrMXMyGM#kPkh^(1Vh3#Z3~g9wqVGe2GJE zlz77rCH5Ewbg1W?>IMD$@3ej3RR7^nPlWXo#~)KEb(eC@wv%>c^0t%8v~82NNN=@^(pIWg``@Y9ZS!>c(#cWE;NB0| zg9pLvpkS7-3{LxLaDVF6#LAJrO;Frt4<1dm`wpT|YiuoTy+h-_(s5mHyDKol>g|uQ zxZs#GFv%)v3VK`0U6&V>w^TaR-gc+DyP!R2_nlGh3^>%5scZG}!?rQ3zh$<7=C1B5 ztiJ0Xu($#R)~1h1$1{{aByU`cKRS#O6{r_nexVS%{1z9+c@dPpPSE_CdiuYvEdPex zmuo#T98%GxKJS8F=J5Z}r9RY`p479S?REGExzxY_;XLx$9wv`J2c^k-GlKik_H-)aDWJR<}wryR!Ve zD|jeHEMi>h1}C)AJZJOA-W*$t(%)j+sIF1hhs&q#u>1S{LOrGSd%#%;c4yw9LcieOXeTDu`C))`sp%7^^?<{Az~25EvlAQ+fUiS6=uqEKTiZ6f)YTdA7#T>r%P*=G z65JX@rBg*&cZ>>jzXM?vF12|eJZvqx_Ewvz_K~qw=(!eUNncFl`tqrw{C0oWBK5U^ z{k-pVN<+4-#TUHbJ7eXVntOAh`kBiwn_fLwsIDziw~FQ{QqL5qyV0KqMNn)O!3F9b zTMIMM`YU8!_j{CyB+M|#dO4bx!Kd&ZYuIZ^GwJm9!1ZYe@!pf-3b?HHKyQnrSdU0c znll7Nj@5UDh^E|=sy^1d4dsNR-z$=Et>FFy1;4`F$!kgTZ4{S$Q^JHxng>YV1+S#4 zFE{U(tgc@vDK-dJD-dbHN@hh~OPcFB(n@9}eKV!6cV(*jT=Ox>D(4sB>^quUz{>jz zW<@1(G%pff%tsb-gyd8Di&XW&W(yoHwN+_;2@E0TXD@?@dL7dR6b%w#O2+T0+TgiP zlRfFJ@O_-B3cj0zM^b$U`lkn3mIbzej}T`?>S3BlZpgxttgQ~D-oUcphg}zcDESV4 zFhGoh2U8axB#D-O0wm7`h;8tbRNsgF)8Dr3XzQc3Z{>B*jVIQwK#A3RW!{Lwb^4#?ijjlR>$U448niw1=V8)0oaN)pJf zJQ(zyO;MJ-M9j`wy|;>@I8m|6z@5s#Ey}>x;D8!5abH*-plFlU(0|+Mzumsm8Oo9# z&?Qkd4~lMthxBp1r)gvA z?%$}itHHLfRI1I=TNT_Y)%EIeu(Y+<+Qa4Zy}VlFrzxE@^cHn(TRz&SboDaKNIW4a zgCAQhn(+P9#qWzI+zFBgSBoY*nuD{z;! z?N?~}PT$8W2wh}y3;nl^{@X43SKFY7{A4_)^_!=(ep4g+4YZ>vZ5Yip&G*yeK|DUy zTu5obwHvO-n!Ce=KG?L%)`EVz5x-OTeE?7_J6gCZ4W;L(%H@9KzN|tlu56hRBi!ILtRfJH5CGoQU_=JHwjNu zkPp4v)4(EK#20OQNz+R*$2KW=D# zB<#72A8k7zebAr|W#DEKOcUGDv~awSAG$_HkKNF4@O)g8m+%{ipNsN!|3frP;VOLy zJwR0Eb_%1s-Tx2`SCny+T^X`v&MGeF72JU$j{|r1IFMLutN=@?lpdokjmW zskpadgl;GINzr@VpTPZj@KD#2PIN=e8WeO8l>pLD5p*i}Vd~-!Neap71Ogir?(K^Y z@icbz|3Hu_Lq4jszhpUGX;(&{L#$28kWVXL+TW#88GRDP2ka!is9(xLD=7Amdv|C) zREE4saj6YJur~c{ur1a1et(MlYJUsjzn8u<9UXLVtDF~=A?x6|HT}4d&M%x%Uf8KL zUygaAGH;hF@Ho}7OMMrEfa=Soj6PnVZY%_Uq?%zx|3R7O$ z-R$yhM#W}y1BC(#SDG>q{c`dSTe~t4jUz_xUgB{Js(Z?WuF60N4UAZ2();MX7%tN= z&Zn&-<{~=&EADELMlYc?EgU*R&NWK&K=d$wGZh5WP`Bn%dN`1gzFoK`Y2H(`hugG=3hkjv zd#KSK>a_>2_ApO-SfD*D(jJy*4@rEWp}$oB@-`w$yO#grd_rV1T{r1?hZ zFaY#f(G2AzD5<6#(%dFDY_#5D?oE)T#nz+8JA%w z4ZPqAT=6F?u-XPh|FQAK%s?s+MTra;}B{BL;F zJWrkEFkFyhlQ7CN9($fU3H9W6;0f6|`90)d5`^{ObH4V}xsSDV5-R~$`SmZktnalS z3MX$lvH;C2JFS)_%aJPhg=rQYnPfuaFwnFd$q_V4^S2ECmLvV(*7TE`x(WrQI~0@* z%yKT2TCV(jEoqLt{mR{+o*-heekuy&+~XI^CIregYTj<;?!SQoYg7>RMQn#Z*X0+h zGH2d-_zi$xQ`&WXy_eCki7Yi%eDBzOH(H^%?rhVou&;A&Q<^_O1a-G-@j6<8>$WP; zM*9%dhM$50WaFpThqUu2v0q5*cZ_OUF@vl?-CCf2>QWaj)un~iyvR^d^9N1c?0!XA ze%X-|kiJ;J4?rA;Kg+2ew|}+=L2Bz~yVW&Vx?4BVrU#bpPV4bPR2Jy4Z0}-gFI4xp zU8(Luxz5m%+^)U_87CSTOFf5r6w7nJLkW;|PV1t4Blq=BtK4dVH9)kx`&|tZEv~4U5OvQR~H5Jz7AHP)T zKfY^U1#k-saDpMkLM4u3gV?0KRekM|I>0FuJqUJ0ml^<^=$BH4>~kc&tM}FTSW)3W<%(9R5XS4 zQZEEQNx_oK(!~(~qkG8YfWY!JMeNfeM_{-&UIzIsIQzoh^axJ&DH^ z6xw(3hs2pemmqG@I(wk_JXTE&1E{o0^M~kcv`}bAyu};EL(#e;+u*A{PGe17Jq7PVQq0*-?N@`Sl=kDZq?ZPK0NPx|GeFI z=zL*yPp+3R)`vgDi;~3Ps{jkH@>f3)lxW5l&M0Gx`Ha(+G zUExp{KIgCo2c6x1)Oyx;@O>NH>~sZ>4$u-?p*o-2 z;1t_5)X&wTm#L=r_f?%w_(f%l=cl;;JN%+i9DdnEe$ill(RzN_lBa`*Gn5y$<^`3z zZ$SI{AEJIVYfYb7o3F73gGV~g+HuXSwSBDHsH3%8{8JarI@!lQ>#b|7CxS;iqrX9m zNZ%AZ+}Xcy$*j*1vhf=0aYVzOb^7+JDax#q*MPo{j$SQlOUngM&}>0Av`*K`y>N?Ntg>AGsmmZmX_&hL-BU3Dr(%l~`vDI1Q1DPcY=!v_D#3$& z)6b@_aj2gOuVcRb3e`#P|4l#bPzwEu-as!bmRK5eh0K2DXly>0RPRB%V?>_ztN zAJZ5|CE3&e;Ycl7V&9G(J{;4eA6A{M8=Pc&AldfdlC~b{1s+H#O}V=Vo}B72r}ET( zzm^3hX1WiU7hN>wp6G3sTzcMc0o24rbFYbm`PV9!@zfydMKz<11?>W@>gKgo@N_daCR8dMxk)jEf&<8Wd6QHMJ1bdlQTFAO}Z%z2+!Ltg!tXnXQ5m@iRzNk z(^6gjfDrwG?%mK4Y)1Lr8$;FD))0cd(bh*((6qh_Y2JH55bg>7uh@M*vU+iJ@P6=p zbfiPRLrU;a-}Lv=cRAFP!s~?WTk7>I()T*lUBQ24glntY<$}5FNNtZIYZ+9uK-yBj zn0Zi~>jM4YrQCh{8Jd>FPNKt~iE&DkU7=r0eb5!eMCcdulr!(RznLO6U1qKcZix`&km&dF(%c zeB$qN3{CN;*?F*YFc4zEviqQI*eWG;#jPoE$_m_xFe167wALHGOcQ7_E z&SWfO{1GDeOhX;8eF8PJ-u}i04+Y_UIsCUAzD%4)SuFqH@D>g))xwD<{_f-OdJaz^ zH-5#aFZ!dsUgCd@!|#M2h2O^fU*PzUaQIamJ^=poH<80faQIpHagYc<5aFibqYr;0 z!X0IBi@PjzqeOl$taxUzH`=x^D@%~*@9k*huzifCw`G;n%S8CfgcBBynQr_m#-;j5 zl@k(7>E}!L|flC3n6YsvYwv66crwgeptFEDd zin3ypn`+rez}z6p)PyRkzRWD45>%O4l$f<kB!PW+Abq$4yw%x=VA(nv0b{@6R@cGH^?;@E?Y zct*yk;us^Ar;(ft{pop=rz~%IU0IF0)+^k`{HXoDsO!I zGE1Iphh^7GewOPy34ZK~0@$m`w?%MBWc@8`lKd`X;kIm^k)OWo#3GTs+7ojmKUaU5 z;1lyZ8QKGLl2)~XfcW>tZ`PLSg~~b&O_-N{Lei^i;cid89P5r zzu(2e@#}MEZ0ljv9=m_Meq_0P$RSy6C56vU*{I59tr4PLvxGB}X^B@eP zb{;_9b{_IC5ev)$bz1?p&C)*$-1JpP`c_B!F0ef9JKMcbUDI?o4xOk)T>f;|bw#I} z(xHA*O;24*)24;?a}4`i#V|@JP`B9CQw1tEC5{D8_YWSv(qYBQ@4O@Ln6l&@v@%k& zt7%u+)$tarm6YYMo+{er^bfuY&#wi#k5F4hv@5K@R9E?Fb*D(IKwXDDF=a^$STyfb zcdO1f{n(fL=^b!8uzGi>JA>!?2ivZ2Sn-gG)tNk>Za#IV*Qvs~Yx#Pd!!EP?&URfe z1=cMvh+e+QkQUZ+<;MzAha2kOp&q6@T2JHuHCUvb?T?)s>l#*1cf(zbg-{8pmtm$Da&-_Ji+VeIl&D0Fo>iX$c)EO+vDGSQ|Pr_jjjKapw zHhuqe=KcGw>Gt-uekbCtZ8~=3m!Q-2-}d#6yiXRsTj2jup>?Zm<1(ju7)4uaw_lCTqTVZYM>k8a}>{__VoN5l5d8#F|#pPSqRkvngG7+=wfT6w( z{izU3^TP6t$f$L@BQT{PC6G1;?d}LPq}T&?m^*HRZ5=iMZTpbfC6qF{ltX=2S6{C3 zQ-Lwx5moW{c(zKb#jy&#mN%vnb5~|s7()I?`8xlWD6m7t6G5;)w$08_jV9M2&&E{ z8=TKjEYU?yh5E&v)pKkwsZM>)r8-wB%bicBI#)oB1PVu_P{RI&Pr&p^ah0D4 zI7V1u2O~O*?E_`y^}}DueBuXWB?9?}jj(MFe{Y|E>N<2I`)W#YUQhJ1w#DZtD^6wX z5!*fo`i#@M4M(IDtYB+)w|((x4&UQQ9lvFsf)ebtFFs42c4h1y`=HM;qCd6|+6ma_ zw6+b}40Krc<(*WI#)o*YJcy=>bC8NefzEcsn0Fl^D4xn zW#^&KcGHOqG_6g~w68^ttOB>|oR2B@(?N!R;UlRFAM`s{ zw700v71ueJDEF^}H^?#^D$Y@yeI071-}#JgNNe?L=+BqFeN9@{IG)t_?c04spp@Eo zhs)pe1X`(kAGCG2fjdK(pn-e7vgpRt8-=~13%11$f4{~4BDC)w*?zs0m3tPSp@tH@ z4UOfreJq=Jx19`)Ec1G!iMMeR3nGUyb~{AN77@~Bmz_fkIWp)R+O~T0_t9APL9mEg zzQbO<_h#r&*3HgAuZw;#>-7ffp1k8q{&9zrA8Pxlm$dD-nzua}Q^afgnDR`t|1*ik zH{@hThB*3uMkgl(6pl2%W*34g;|k}mh5PEEaNA#^rjb1bce|ho=;UpT{-Lf>mXCiX zb^MAzT1lX=#3%t;0;MI806UbGIhQ+aJJ4;e>SEhrR|X!g#3s0No4?;OzjK{+_p74m zyDKYC-@v2z9FC?O{)Me}>w7F3Xzc&XzF3_3LpHQgmmqO*G9)feItCq5mbYW-`OH4( z4Yd6!9MN8L0cW*mF{_-k5BfLt2l9IzemH2pb>j(~@(YuVTR(FJ?y$mn95c@;WymKE zW%LeTI}V7a{@bOVRR(?{?Li94p}|OTk+WDmv7Hp+NO5sm2#IfiKKL@)H;^CQvWdsL*1nPRLGR?|gYI%Z*qCrW zXyqxN<^yfY*Q7q?{USLZ9I@-B6U-FG`9LTD_Ue7weDKzPc0Mp~`==7Nz0}8}^#L&- z#2?=soe^NCH{vllAzUx0T=KwXOkUEN(5O#$1?-=911M|T?o)2Jh2V0Q%^T8RR zD9HKXmC)G#EIc0^*X9GA`e+{nn^QRp!2W3R2{}K&ARmT+lVNQ)>4IJA2s8y?T7dP> zWSH(v`b^6Iy+ZhtF(8bVApdU_D#O}j<^S1`{6AqT|2LETH}4})g!%~20T~8`aS>I3 zp?|o1ALSO}P#Wt#I!}kaih2^)E9@_mz70K|>Nfxb8P#!BS3JDw!af3SGN|vumc?K6 za-rYZ;#ALwWhIP+77oAy97mQJ>Sa*k@X%Mi4A(}spTT3RdYN6tjie1Jc9=^q_!Exq zu}00k*!+>Y*x~#&ep&fzLH(_=(>{!>KPXC_G9RV!(aX67+0^T6G3yft&8W{+T77~? z(H3P$>zr?6X97xbhH8c8gNMGV)d6do(|BxAVOohKLe=^^*p)fAL{zL%U;l6WU;6%O z*8VvU|6kJnu<3f*u#*d&GouTB6Uof+>iS=#!913!?X7gFS`*oBRRU$RjEYODE&< z(ZIvxfE_1{>_H2A__O2z**_VE$tURf7@o;+{Q|LVr~f`z2JR>74=@${6O$9R29Ytk zTXW3`N(lRUCq$88F?tR1JEiC;l*_XM1icnM2dni%c$#`1q?54Zgf%?kd_tKMd3cRX zpONl|NS9Lhl!Dh&a0?}RjB-F`u~?%_A$wCQDH%ymfrUI>f`B50qgQddZLz;fM-TRw>3GGynht76%B-S%I$y@)!WQM>wNRGn766=0>tIMu z6%YGX@JGK8mbheaK6L|%gVVCu6`$2zCS6~!or8#MB3y|c;R24g@ z&~MSxM*KGkw}yxG_}`zWS?1gbt1PuAH2k;4#rBpq@@N|jk4?gRecN|K&)eH@4S8c5 zE;ny%`-FVkUJHc7||H`)852>IcNvJM*m?%Z*LC@i#rU` zGyPZfM*}EhXlxDM!BbFH4Kj_$8c8yV_-c}nuMNz9-ZZL+<88e66pOJ(_&a1Mevk1 z(CtENPr*hsHsk5-Fp^|Dj6Tn%1p0k0BJ2e1HFWI?=G+(r15bht#v}eaPL&8QG~UF0x;L=z zFN^q(P!War_>{;_qTi1fm!*%ZSHFD)4O7A39?Wx5eD1s3k; z^unl|t^rM<4GSTMc7csfa6lBTCwdG+VK*;il(_MC^zWf@Z+|7;z*lS5eh5n~?I(3YqEQyGE|(5p!s z^Y17_-a|Od;-{X2I0y~;J+8pl)}rEE<=yXdS+}hecV~oQ!YG?f4s*a&S zxv}jAjLbG9#QvMxJ`6p#wtbxfF{oR_pkC9KO2e3=E121YYP`?S}1ex`9Wc!zN`Yg4Wm%{YPL(Hx$ik3$s5;fz79 z*k9sldyhlRxODvY{{O??_rOP0UHNB{jARUPCQ4egSamCHqJf1jw53Yr>5NX?q*&Y3 z#1eFuYW3sV+S;Jqf+QWLoyX%@+L}sR(q*@@WxKlIACOkjOoB=HpAf|e6$2;`qY_Xt z0Zo42bIyHl-kT(l3ANkqub&U(y?O7RbI-l++L z%b)_x+Kdfa;IM=K{O75pH5wpIFo6BD@Spb!ZN=|5sDO+R0MozCfw^l4(Ml z%SrucrxCo%8o}i>KVjzwTDF(&UIkus0)``g93bR`GdILj!JSWpX!L|IYVZbw3%Uvs zCL5o~7yIsFX$bnV42`A^>7^3<2lFeG0RTcz6T;DoGN?-!nV@vvv-X&OYRDl!f1Uq9 zq7NS__m-#e=T1x>G~U-@Rd3>9Y~n%%<;_$JvzMGpwm8tyso-F2va0OkeA%2Kp?F>kKaKC7;Q!C@e;NJ{ zS6*A=d$f_l_=f(iJxhXyH0=vC7Qv9qwC}YLL%dE@@C&qOl)&!hA+9v@gd9h*2gCwE zl8$4+oGih84M2tWNLRs<7QwhI<1T-fs`hQyd>c^VCD=rL3H13M#w2?-WMbHhw`wAw z^<0?=+NGF?Ntz)({0U)=?wk<*Tm9k*;Xm->uV+GdC-$eC5I8eUlJ5VZ&IkT$pkNI( z9W=vh)(EBw&&jL~dz$+@916fq3E^%j02&@Ob#>Vk~RaQhW~rtyZG-*!)G2NZ7_k0Zn?za=r4Q zBK-qoyI9qzhaNC9({f$QT5n{$j!( z8=kY<{4(M-u)MbRyLnhXUt4^Bn)&5EJPCPdmH#VwX`XIRYosBA18wGQ3wibz;r6om zE9X6Cls;fixgNH zO9(1y2u?dOZ&S##52+o4msPBs_dlSg38akwWi^ws0l!}-`ns`Xhk%jtckurWY)Qw@ z{rLYfOb=XM$2uP#m5;xJ!Dg%LjXUvfJ&4>UXg z7GNu%FkY~zYaupM-|#9hyf*htfKIr8K(pvBM_{nRSe|pFiU^*qu7xisn7s^>(}lMT zlk*bpP|ps)RPx9U^@WTDpgfk zU^(B!9jc;k^Ik77a)p4|tLkcIAn!j~K;CJ%LvD^WWYQwa#Md1QtTtF!y$Vx&$6s3d!`?6gWkkOSA0_@^&A)qFugaCUyTnNZJ z=0>2`(k3meKYdO|9A3rq3D3Sh?qYD+V@;uj0K)7|Ul-0l2S@4g>J`LIv_++wnF+oU z+z~ec#9z()M2QuvnWeBlgVNc8o&1pl=o~Bs=%FgC3}CY@79Ivv?X1(mQCOt8`y(Jr zSgJ)Ysltj4BEGORa|#qTPhpv2S~KzGLSd@}5#3yspMyK|pn1}tr{J{WxG zQQE1kmsaU0vW6y}Y@TAX*U(&)y@q%xdkt}!G5>+c!BN;{2w#a2S{&~PjHhjyzUJ&T zJdYjpl5vxT3`UqhX9RT*5(IH9v+q59Js~z&y$DjMo)KCeH1C3nky%U34Fh8GF}VE< z7K2zXgy~>ZJe-i<5YpJvhoCPA@2}*L@cxQ74fzG|MDw)K&m2haot_sXbxL?|MZ!0X zs-*~!L!{tR3KIRD5qb!5vyBklui*bq65kvr+W5W9G;DOo##h-m=;1AtkWS4Rp@$JV zggaI{eB2xjyV8TcD7C0$P9ryG@($5J?D&|VzS*dB(ZAdLM@9cErl0hq?}Pq+4N3>J zkLjR)7nBbuNQf2gn@98)A-MIj(8_s_1`%&b)Yg**G8WW=BGEvGt$@6KFRKCL<(i~` zkoqvb10`4$7{3j7mdf#%z)g^AA(Fy4RgQ-(m#Q|LnZR^6+o?So#REk z=8o__hdVG4!daKb;rB_tlXZD_x5W0tYgEeX=$+BUbsOubtG1ASL4DoeI!SAmKM)*(#{l(Aa`t zsDr)|3nA)t*2P)H`o&m4Pe^-9!_c0kxqUjR4flM67IsP4Z-B5@+KHw&CE$nF7?8q| z=69tsB(R(uJTbQ`aGBYM3!a$M6}Zgk!v#;wNrhLH)vz=b*tt9o&Qs@oe)O zyot&G!`%?JL8EjjC18LQGiqrIhy(qKJU68A&rpUw}WK(PjbdLFCBTVN^VT zga`2h>5&8D;`_b6;~eGg+w6Ngve~2Q=Y|c=WS}FJK?$rQSjFYA$G*+>SWv$xsNc@! z8JvMAaG^d8+nQFQJ&x!fgR{73y3Y_Z&_B7qt4-r^(E0IqvE(`U`p93se`Lb9StWq4 z;vuF3w9PZ22lNAd)Q zm6hIau`-Y&*JNeoZM+r#$X?u8{*mVdCQq)({*h-bn7l`D=TKJe7qA?;Ci_QX7Ff<) z+*$sS8wJM8*Ma_#FIzxf4R`;a`A7a9@rCz|dWymfPscwp|8)H$jNo+qBaGm5{3DD& zBwGqD9$T_aYi9%^*it}X3pRy7{Y`LZo{3ZBDh@JfA>G?|M5mur#&!v2v%C?Wolow&19jz0+81i2>rN1m`?Cj5uMu*&g6fyt3;vdU3q z!Q^~XVAwx$y};zjHQ7H>Y{BGx9(N9v<6{Dr!`FfSkuerrPCD)^o#Vi*+!tQH4)l+_ zg4g07*^0ZrD*p)0?bAtZSa5p&5t`pm!#~2{BlnLm_{jYu3@$Ud+r?n}mInBS5_9;p z9tIz|e}uth<{obCww3^kmcjo-{t@N|`ZND@{Ubz`S`SIVs;6&o2>;V)+Y4bhE&m8( zI4%DOV-Utjp%o6^kXD?Qe}rlH6Zl8w=Z?TXa{vFI{t+4iC-;vugGmuT@;SJ55I-^r z7rkVmUKuMwu3m{Bsp9yNLh}320uQD9;3bmcIjzNd4LOgnwOd122C_w9I~^_eIQDyD zy#z~}nvUcQRWu+dp5!X5mr$_AIEt^y9B2NctoBeCedg$(XOE|e7E5qcQ2o3Xgpvg_ z*!fe`Umu!weBS>UB~|9=A}pLNz6P{lx#vavZ=^+(tyn}k{}VT}l0ab#4fx-NxQ~$e zO;EPGSdzR>?w@>2(;F2%gx?iU5=J~pD-s9ElGK>dAZpAQWEZj*q{e{67d<=GdQ{iE zt-%cL4>YW0B*pt~)Z|D0PaU25lgd44;jBOk~F?AMRvZ$VVE$&1wp!QF=R4{Y`!-P-q zeIa5|I3DF)ibq*btI`xoavotoJc|9mg9o4kl?A51Hk%2@4)@v9_tHI-h3A0gO+KRV3IwoNi8$c*j001l5G}Mv8wvjG#&DF=t9m$H`|IDDZR5cU)3y zYdZ3(Zq{F63=-~07#`#-wAdelbUN5R5{#Ap6bl43z8(z+x-^A&Y-zr?!)D3D!C1x5 zVj&#O5<)5$+L@`3#-h?eqcUccE;dTAi7y;D-Ogt)L?9Uf3{vr zp=4V8z&YsWui%z-tNXIFXK705Ix}cguEaVnEgad^m?3N>#s*N3<*d%N-q15Y7sOJ0 zK}r}B@>K7g);vD|93)W{4m}>ii4iBIL({jR<=XG@iIia0k0?YWSdkNgn!_Oqs?!E* zjfEw#^k8El3RA?A5U(~C60j*^aahl25l$oI-l2Bdy>qIr+0I>)(8cUNCT+~d0!FwP zjG&wO^tU#Mzs2VKCxCplTC=*GzWq;|IKLJkAy=5`I2)RS@2Xc2%TYGNYGyl1YG?(y zq>ZXxL7Yd2nN+VJ#-l9m(~0T)<^@P#XOyg#Xh+cXgpOU)Nwp`{>0mxMqRtiCB)la> zJT?pJFM)9sCQ<90m?+4_-5*Yh5Io@GLMJX!@KVNQ^P&@%sCp^mvN_U;OO(EpaoK$7 z#6@iB{(|v12ReOhINfG*D4`4*>nV;!mci!xwP)FMn7^9&B5u^sLREGRaZolN3uHuz zd$FmK9;E7+Mb|9~Je6JUi;#kes z=xfEEwe`g2k$vNQn>n71=hZoe~<#u|B^`F9xBB0buyg^jt&*>#1SPC~u^kL%VEA*y4 z7J3jbH|yMv#LI*9vIB3n=)8Ow3;i_l@-V%G94&l7uc3zX#)eqvXV}|NLPAB>mT~j6 zHQOukLyV40UX6N#Th1Vut(P78SsEwm>Iz^VyUr_unCp;LVB4TrfsPiONn$xhF~WW3 ze^iMOTKeY5XF=0=YBcsB#-0R>VpwJ@M_hu#a1c-v=V3BQS=Q*kzu0Qzyb&)NdAir78x?-<8W6u?@nvi zU$P{J$RH;wk{l;NrFhQfVCS@IIh+!74G$T8&_gtZIgtqur%suhhn0X7HclOxn~fDR z;pEhjxj9)O6J~ZHi?5*Bmtrta{T*BkYs(@M9SK=f$1DO_L~N8@L&&pPE=f29%OYZB zTNe2mMPi9861nB;CV`W|tS|F8gikil%tojx(lILD;q|pRWl?>x!C~hZC?p|ut{B=I zOR;Dn9tDwM50RP+gXXrzIPbv zyzS#&3v|M*xU)?2YXy!?^Q-B8plN=E1vq_z0J3SGD}Zd8=P1A=)BFqzaOyi>oCb{-6R*GRBu!z*FzT9ik@-j>`TI z94p1f_-2fJhcW&%ezGzCIBuY*IE?Xsr&nZ*Bh5LC@wfpv7vN?^yh;5VKK?y zq)CthH($hWhe@8YB`zlT^9d7NB}Y2B2~P6^L=*-(qNCV@AUj3G5+@fM&cuQx{s_%4 zNKQrCRM5Dy%4LtY7!}CQ@Fr%jq=ZZ-x5oq8dS#9i0x{@Mg}@d<hmXJ(HVOezgxPlZ2y6kP5P)4$LtqOPg@Brrpg{a5Lw<{jSVFb6B?%$=h_+rC z-5kl!+Bv=n=24mn;q+XHBgCqJ7kfHNnq9gNzP+t7n>6d%ejdG1nb&M`vv$KK_dFB= z)iAk<#l++$4kMFWtL_t{dnvlwsDQaOpSF>pO3`(!%}GBMU5E5^t~5ziSKOr#!0kFb z91aHSwiql`91b319IV$!LxxRX59IU+9bi`NCJVz$< zFg}li-53??*)}Tp1k;1re8j22>FvcF&SpP7DPQ`D7zM6egLHWFtSw4+GQhk(Wc+$fUC?do>nH zwvlgG-QUfk!kWywvm^MN6ei6P_fQlF_%Zw8kp=; z<#}9%y5VGi%H>AMLvm&z1~l6j3fUlt9D|L*tn8UQf#Z}2?2*PM08ps8+<}5@i6Lkh z)DQ*|HJ_)1#2#xFC2CGj35h+}EF@}PPYH=V+AJh$Zg(OP;}(W5@dgZEVjdX2#9%Od zi3!Q@9nfuaJ1@D)4#JWIYQJi`7gudzjTnvUogu=Y%v+;IyL*Vy(_c7 zdoLl+`Mj5q=q7vDp8aEB2kgWK*{BnHuGH+m%x&qOa`tBgGV2Zlhs~4Z>{054B4XG$ z2A};?qr>J%MTgA(so}60QsI!y{Qk7xRV#eC51u@r%-rER=-{LD%QX26ZY##*1wUcC~5usDA}u> zSojWJR{GGqCyWqinStFGIu`lN%pw@D$XtMxX4um>;UbWClg5S`Z0qt}_+PJiknn+4 z~DTx$1V9Zipg{f=kHP*t>j@eUJb6Hw|rlbgzM*T({ByvL?mTenp z>6(vfg;1aRAPUk7u>IgZJU~HUr8R%=O^j%^x)$yM63WNW&1=8ja)PF{U+*=z157Vq zre7*Ba)p75TDj&XS?#{q0-Am{?kKab|3=ufZG4@rt{g^5-BZu-d0X+8 zyC?4{g2&1O9n%6Falm?iD>%GA798qYcpNlP)(VwLLbq^u@8NQVEt=k{={;CQucfIX zh^_G0f61O;#J0Q???ey_udM5Pe9e?>;si4DB5fJvDeJ@vedcrM2W)?)tP`zVT5=QO z!ISXl;t70bj#7^~ablO0-iacy(Br@)js*ZB5k)AVPYUWYGWF|dcj;JQ!q(F=?0mW{ zk!=FAxMf+bX$sK+o3e_!(w%Vvu$S^iDD3?%Xfrqx0tXR5*~W_gBQ$5n_XqS^a%t-g z@x5c?2eLK&-T1z9upPYm1TV&%i8vqGVByCd=b%{1IVfn2XMi1n928i~Ra7LYouE^^ z+1MVilygu#z-s5Ikl12gwg@Z}FLsJZY&oxxi1#{0B(|VeNLbKQN{v*SV@rC4ge85d zNNiEBkg%vv6^Sk66%rQmE+l&G46p`1^z+ap;q6H*p&%;-ohYWbZeUgl;w@RaC+6a% z`&$;G{3trj8H@H{KN@J&w^WfAp}JR`2>Wmx9S-KqH1rWd`U{6}!PUkj4h5T_;b0<0 z8w3;cTWT~Bw+(_x)XS7;B9WBSv6Pb?VHcXF5UTC3W8 zI{$2ZNl`i#YG^YY-ltc@WFE_2!#ZbWm6fQ17%dxHV?z&M6RJ_N)W25FPd7@I$%?%1 zh_A&cU1CnDL9J=ngR1X}@55^T4m(c;PN=FfqN2lL=_6!T@Vdv2$;XO~M@TWU_mA@W zhbzBB()!0}d;Np5JB-Hq!Y0o)wf=EJtuH(mn)Wi*KT1{xX-76rn}$Y2$tia6%11`Y z!v;qTW<+TeN;D;@Z_7W+BGXpiI2XrQAIH;_%xQqOS(F|rsRHS|JfhBb-;9Hw68aqJ zmsUOs4qhBRPO^1rp3Ow>7+Me6M(ZIsUag1dJtD~{pkG?b8wFG%E+H`9F9Z#xy!Cv> zp}f6KKR9}D5ao?kp}i;}s?c`aiI`_~Z8d|=_R5u2A&0v5I}6Oa6n9V!SY4YZFw^-8 zwX55$KUG>l)92vMp{@l5ZYo~^*W}iprdx1RFJ(AeT^lWM)A652$C;D1*&{Ejig9Y6K6*ku4L}A+oes?NtbGfqe@w25; zzN!k9!ZypQaQwBnb1H0~7udYN6Ii*TrIZ-Np|O3;LXhVp1W6j(yHl8jy)W~1wz~3$ zSck^;BHnVVz0Ywu(%A41(G?r@(uc%%j{G$=@S(VUod$`hZ_TWY+3Fe6ZydoB=vfkK z+03c6Id&4do0-XKn>m_wJuKzKyV9ZMS(&DH`Fefblr)}$G6g3^T0V&&wl&4(of{C{ zhTI&F#Y!kWN3lMS(sL9_BxRC1#(I?OKFQUXHZiCEh8r08&%$oz9SB}a_2H!Ke7^OuOS;>P1d;#PKy3IMzUPw?XT z=V+WrLGa-TVx>s!QJ9E4MU@+y%@wRVX`Iq`2pogL+Z>_bMW@Ebk=Otl28FkoLcz0o zLupEe32*a-f)`DC`0zGsD0o(7QmEhN4!|R78ZJl)F05Rq+8@lgbGc-sKukf{sA2Ds zWSH{b|0?EW`0$ZfgXgaTB-TLKAndY^NXG^7j%=jK$gW&U$uw$7^9bs@BKt0YY38dB z8Z|W6A%%xgOG

J643T98>uKrnG|}qvc`$liLvT;d>F8I7mwhBCNb%Pvih{Zv=dO z@s6?V!NbXLk%Qy@zzXyHWk~H6Kj`&!V~qv}2{&hGx+iQziDWq0$SB2OaAbwv%vNbo z#}f36Oj?8pgJ!fYbfF&5n+HkB5vl?zgS8Vqee#a3=QSP#ofo|0(5Hs*w-NESzipYy zSGwK5s?W^)yoRkON>@|+O+qWh?M6g9-EGLgNG`1b`%Q-Mx6R_@ph4v4Qaet_;})d+ zVu>v$M`=Gt$x89LEuXjgDl_xzE{~g?V2nI&-n@w{?+~A9<>z3J8|jrMQA;UIce~?P z>IUvNN%-3&1=_snNP$K>PCR{}a2`>(L-U-lU2Zs$E(8+kP~7G79ueg-FkYX)v~0j{ zPGE{akKrURy^+CYkC*FU`(v*|2^QDmzkoYvVDQeBPxw25Q`h8_9OEpQ31e5D^v?gbE;C79>QC$Cub+_^hVUgJ7m`hRGwT5*39zAv zqMT|0Q`baN4kQm{=TpwT7G%z?xI=Aq0P|ieVCrh`$8FZdW#zrng7Qw{l7-Epwsm{j z$<21Y?PfcN+-zs*%>^x_!+GIhn>WVO+S(iRG`047-flgiZz3<%9t~O#dvGMFo=z)7 zY#*?4W9v4YZET=}swI9X-5f0rDjcIjr;R`#;>6ssk|&Wobr+r}9f(4BAH%U1^xy-s zmFNK{9~;c&(-c}tWgHo(mW6a2=_9*GjFQ-;JWCn8DqPTNa9%;J;2oqu#mvDgV)4CL zBt{lgn%3Zh(zyscQgB2ul*)t(UsOzzq2}OD@}UNeTLuj{aAreY=8&Aoh6*piQri=S z%tp6t8PX4Br&t3DTU;s>wjXNvC~S$TP}p85g(9fmLO~mYdBmKQ*n)Ffd$D9pq)eJi zMSj8^wS>KERl{nPEkq?vEX6}XETxFVmZS=aNKz*fy_VL$htW1$r~=iwXq4@gvKkdt zhz4^>aSC2xMD`CYv$#-&t^gVMv3jp;B_VD@YZ2E%?q7*(p*1buN+L;8JJ3DEY9+`q zu~1w}+^d86Hth7QN$i$LE|Ve>C&LjEVYn2LI9ZO62+O62#KCZ!OISE9MI=d__JD9& zib#?;?E&F5CzALI(usz0Ksi5>&1xEwnNMps<}akF;Q{8j`O592VV43Y{d9-6P!-vw zv20evjLItLRgxD~Hj5zbs1(BW;uvK^&F zr9fLaoCf4GN4%}ef0OVxsbuC*22{i?=M&{@<_?z=^c7MDHiV|VGw)GSJoU;Y@HuUR z8h`+rOLka3r(gyTRk)<8NOSqxc(>-FoK2yaq&_82)53I8uULU`#NQ6>ZxZFj&JQoi z*YhZSE1XZZ%Sq{1lmHd_6)42oAJz#kww0H+h~i-?FSQIdd%Ro+Q(k_C5~93R>jaYErZ*$qgcBQ@qJ#Z|Iy659CKSX00L;8zfhZBmI2w8zSFX-9adV*rVTaRm= zZmqRP!zNiB=}a4u$yR5D04^I@c4HBb)wjiZ>GA=(jC*BGHnnp2EqiX*WrL%-Sg#2} zW9Wibqt5nHj+^b`xM5AlXFG1fW+k?TI2<=&a=nBppJpPjsx3Y^qw3tzl3o?Y-O{cc z14qyqw`{=4ND>QMXT`i&yE}$bK;TZjJD@!y%M^@bcnEB1pb*FcMG6RPNuUtOxH zY$>1+uoRGP7+w=>37`WC&fiuOi`sGTvy2R2l=xEyN_eFD9ithtXfh%CX!#kAN4TohcTcum#&`&A1df+MI z4h!0uWtEAMO-xA$9PB{|h&xh4;9w6zK-`fU0tb5#0wO3i1P=Bf1jHSwA#ktq!~BV~WV z_Q>7~;(O2W#f{L)g2s3U{4^XxORNUZjIWs;Faci^?HfhZtn%qB)BI01YVnSAcx8~K z-`7Xk#}oQRchFY{XARN}=*V_1fk{Z8%3dFAtQp)TBMDKFjkWWi5glb2Rh<_jLF!QX zQJc{f$@xDPbN&zPNN5fssXDAloF7w&)+F+OP@g0sp~NNg0!2Wv-5Q$wDtTqB^(h<% z?Ova<^M8zjcO&xx%Nw(Se)-+bm=+}epvu@E!}4U_BXG&gKotDKYs}!C@Fpbke{6vw zasGdgV@1N=Q-m0(ka>$U28whV{oNA1&ws%TtXIPy)0LplOv?X3hk}mzh!y|DTZ+zd z=Kt8w`9Fk2_+xk}bKdW;3Qj?lAkN-lbdJz9{2fNGe~cT#JB$wS{SZ5hUcyV(A*MFr zPBaKk1+r2wbi(5@;UT(L>sGY5_AYA!6KXBk2?p*&X9-OImH_e=qdWM%@MUNpj^rP= zSa8$7EO5NT=(7UHJBXo~dX)x-3vAFqBx0K6qb=hbScJV6>DIGvbRlM+fvjXDCxmCwV< zckn;l(=#*Q-cO0k5VmB@ZZkq217O5VNmHaGLN?|)I zCG4Cc#r5|%-v?~l#g-Jq!H+p_f(tot3M=)L@xq^v)3tKhRq2N3O+yJB4nYPqT%OP* zl6jn*e9rpH8<)VX+5V4CjlMst;K0?GUE=@u# zI{OF_yBIbJF|}sIeeB}hB*dcQ4@z--MHn9;kraSJyRkRoE4XCKPKk3){e4}Wo>VvW zV}v z8?9t1976DEYonEtj3fXl83|U*Nu75~!Gn3$>_dr2=(89VPrF5-LTqok!c6v-AWE8g zZUZz;J(M7W=baIubWJ)=lsw~%2&HSfaiZk8W<)4m6O9uk&oU!I>6&7kD0zMv5lYw0 z;Y7*P#_&;w^uxTN>LhZKK03P^2FI$`=7T#ZmI(8mIFdMoXt+}H3JgyrCF_jPhQXwy z8|#2Mi`d7O?n$$#NXZc*b}?)cVv&*~MC@YQB*Y>mM~K+Pyh(^fN{$e*i-nUAi=PV6A0*>%hSt{L!sUA&%Dw@ArB>sC^dcLY-Pa=M_BlE{F=l9P7? z5*@T7FusC>0qrt71;?czCF_jR4NfUZb!D%odejbNo|;KYayBk3CesQZ4yKD|OV+~u z2=Z;nX6fo{h9I zcn+-JEKK+haPC_oU&2EB+=|908z9C@QiSZqQWhcfr6>L(Ply6wDQ%iA&Xg$EPALBCu%YKYc^L?@(qtF7K zeje_eevIQjZY%pSj`2MQ3p@N6-FPb9vdlk`UNVbRzpwMduQFPIMyiQbp$ybxw35=Tb%I zl3q@9)CZB9h0$1ss?x^NXvd2x(oc zuj2MFM7=UIU`21aND~}UEJAdkuu@024#eo58hCAqrW=~d#MO|DTShe(JPA_g+~7jW zB5MG_!$azvB3wwt-7qproil|CDT}WWQtF&ATu8;`FfvM=lY$E=^~*peI9Z>jAj(cg zs~K80AWh8BqU`zfgeXHgkXx1wBFdIpp)ij0FBba`SClbsX+4X_Vc>MKt2NOnQ8of> zPCm7;DN!~8Y)%HXuqjbC0&Gssw6G~r=E5d2Ea70MDGm;Hng-!IpsA8v2gB%EC)ZjP zQU^hlxhhlwjQs`d8{mw&R4od_U%*0*{RJ%4*k3@?Ir$5QLB>f$nMH;}l-&!&5M?AZ z0J>->`s#i6g^W?ima6adUyugZ z!A8#!Y#j+VWi}I1%_vEWZoKb0&G;zZwR(<*=fEruWcEznLH+_R--_}@Mg?-B1V(Sx zjI;c!HZhJOV>X_ZkA}mc3<)`MGkf~$3E#9$^P1=|dz@(zpJ!@x@JXwG@wd@S#W=z@ zvLVZnwI^&&Co-6R-p$5$w+@I$`O25W`ibH>-_I+bu6 z8OHM9=&f168tL);fv#cy%+|1RH#TU#$LOh7f|Kh)_!sTJ@9&G|w1lyWj^NuRVdMK* zVb6<3HX>v_akLx3rh!ZZ%jRaj7w&J=roC7B$B?HdOxvS7DzTwr4z@;J2E5dy+1gDN zozxol_tm@OJ(<|4zdb|hPSWkMqH$CWI zd&4CB5BS&Kb78TWLpY1g^XcNHAr+p}vyQIXrI}B8x+&YPky%|GmsdxAFRw;F?x7E- zWn}7FbsarJJFRy=3)(L$8XWnEh#v=|*UbF18B8M*&Czq22DA9gbaTx0=2t$F4l477 z>Y3v}`b|@cO`4WKavohA)c(Puwkm!+y}DiqKKchCc+6u#&#v&C9iVuL)tFX`;Ge=V zCasBqU_0KJ$In;4JQ&I7V(C;SSCbBtstVxnS?dpZT8Y|BMP{)%Rj6&Z`u5jBPrG#P z4~piz1hRfPt@)m>nN!w~crizR6>YZY+=#AFAIfv|8EMZH-M@Bb7DkGNcw5T z3SF8>MUXIwI1Mkqrp>0Wk@P*Z|Lg6$LHnv!s(t0l6l~>b&IkoA3yt;vXclx>{q6V( z9y)vUHoC*m*@cfs9~S$y@nCPd@1=qx=B!84gXWkoYv#DD_zC217#+r!{ni-#`NqW> zx}3iASoML06RI2NBNfj%@f2OJx^&z##WE8GJzYqWfRFue8osY4AGl=BMyuTYLC=1x zfN*fhoK23GyVXl>1MQFsS`0JgY|!WndXAaniacmox<$iobKLbo4_sY5(1V^fPb;&~ zRhLfr1!jTIDF&sv`6!)j9(&G%z z#*+w-JQ5z`E#YzQU#sw#mYVRGa5CXBeQ@D1KwtUW_ZV83|1W%xA(Z1&%s`*U40JYT zpuhWb%s?)E{{I5Kre(v% zVG`leF4ot#^ts$QxDRew@&pQQ0Ik3-#0vpwLv2XlKBA zQ#5&FqCWGwl)=LK+V4(P&*;cN*-t(AJ%Tu(%Y%{A1UR@dPv^?R}2$4|n@ z1!&C40$d>TVr+5ajEcDKxaVwiQC{c%4DiCl`g=0L5iRJiniznoG>+`Si)JpCQ!mBh zTnve;a?Ry-v4DkDiat1?=t>kVG8SYpJ;l0E3Z_)_y~0=NW%TZ-$s4hHLSNtZ_nUX2 ze~-EQ0o{A1c^A6Z{XA@b`$F^H3{V(66TOQ==-O59&N@F0EMWI8l8B-RbuPNy>Tr65 zzCBWD{wey_+~?}srTxD}DVHVDPxq~q#fXu4FdN-^K$6D(~ z7)DVZH2&G%|6ml(MDxq>gyS#0_u~#_dEQ32OWs`ijTlqpNtIlwc*(;`vfO{e0?aFA zz}Yz=z7}p$<*~RHsG;UaVzSR#<#WbM`Mf+SPw6~37c1eXu_*XQS>^NIeT4{{o%0%9 zL6EBg>H?`?Cth<)ayBqPpjP;GcHYDMo3oyAMN+|kTIKT|;PtDS|Kr12x3! zT>kvIR{8V4F6Hz7Manlg8$b*%_^-Cg=S`9FIhRZMqgDqA-50C#xSdBRKb zS2fY{QJU1D_p~0;)}_B%RTcb1I+6}))%)@8K27h{khwO56mZr1($YL>A>Xd?c;N~9 zS~QAszb{Sm#Wnr77Jq$I2zQZv9wdv?;)l+_B1)uV9+&H=g1pmwb$Uq< zfFMHQ51t!;Ym}yUBd5R>db)-T1MgKIM1&d8H|gEu>n=d!d$8rKV^kVk&XbML>%G-` zv3$G0(@*8ns(jn4-=nKMU(heltJ36o6=I%GXz?x_DnKNS4(ajGMK9@n@wc)f2RzzE zNA%u!r!Uq##RCPtC3Mjy?V`=vMQ;aVbyGb4wcWvs_5^)9gBQgkdp*7*)dxTL82Wo# z{J@#MzV}|@Y?(QDd;}3GY5JLiua{B9kq!?U-f;%?TNO3j%)COY?s&f+oZ+i8GuPvJ z8eWd2UO@^>ZrK7~leA2}z&X-_F(JJn1RA-&1Jv1}i`q23Pm8~m8;o@m)$PHHc84xH z61u3Bn_)G~w<~l}qlQ+8d^^#S{*U#C(CzV#v(f3=dwcL^6cRqseX?iIN$2?G_2rT zeZGYHJ7|u+!WwyL?}KfaQ!uW;Ecifb1g2rQz6~Znm#C%Thv@~Q6vL9aPK{-ZB=8&! zEgA12UvtRUHa-oHg1&aebs1@t{bLjjzRzR$RRKspO5;B5eM~RiDD6-4wa^yO?hId} z;lYID>()JBeWq9M0jm>-U@EEZ=!3GY#(X*mK7+aYFmrcL2G=kdfUfuoYp}E)8V@}m zlRzEP6mS=wVF9?xTlS*{+XN%otLgp7D7zqpO!grjB4>m!A?$r04UjLK4VT`b>_|sO zNPkC*yqcl;wot;SkB59XY4s@Pu(2TzVkt`MaL@n_ieNzBR>GYd*_)wNACM>8upHdw zt=><+d<`_KovT_NKXf)Vohrn12h`_a?x0FXkEe&eX2SLH_~EhD2N`%fzF0>5t+NF) zW}AXOz4CdSe-kWtTi+8ZcozvoHe6-SDoQ^ZLIlq>X5rUD1vqAN{uZs^ePnBFMz%J3 z(+nYc$&1CtEddC)^Pg-dt>OmS>M`Ld+~Yd+zv)@Re$Xpgf_kfeRmlc|dIpQVNH>R$T0V2lm^0)u0pQQ>h_*G|n0 z;{@f6Gh&w`Z$DStLe)a~_%N$|5!FJ+PgIL4+o;}_52@agIdxaYE{7BGP%r~0-+CG~ z&wE<`Mt?MhL4w@}$XKT#-YIe%fm$?fijBf6C|Q@i5ACbBitij9hVO>T`4L=c@W z;j|~8C0MZ2 zzv`IhACik__iwLUt05gQLa($)Z)W8$92yC@$H9**U##)WdIE{=$o>UL?XxkY@50!` zVBS5R2cZ7G7Jnm~24Qw&KgRNFIJpyJ`8gVB7|W1|)>sbV2xS>+8fFj-)LaaHt@>3N z#~U>L7(}Pn9@u+^9?L*|4oPxUI=Xs44`v?uj=|hCa4-i88bSpJLInpgWSg&{gE4~U z7kS)XO^jn2!`Uw+#;_mvG=}@1&q(uMP(|MplhpG_)$Lyu z;-TNJ1~5I?LJ@YWN?=*r1DNtYZ`Rh!H{Qqt7=LlZn(dEEW&HL}u8Z~$Iez;&YN?t- zE(aaINFK;@MXUKe4B!4(=I4hqdYcd=7p=SZ^Fs`>CW)q*&a+0|fLQ}3hE?qj1Og(M zDj3U5=Xy^yd{4Kvm?~nKX9hjJn&)lJ^HRujRO{dBj~W=ML1c8*B5@3l4O--V4Bt=U zp=*fP-^VpR;GfHaWdQ$N4~=&{+Kr2D&Y5(4+g*noSql*M zH&>5Np>Rk(EpROEg66+MpV-McvM$GO@CtHd)h)rX6v$|s;1}olErp|Lju>ZtpFXEL zzXkCMaL1O?NTF8mCzKKLyJCNI)nuKho{h%KOPbBC! zHMnZ(%x{l^Hz(v@J8lY^keO+I8`;c{X`XueH3dzA|eCcZ?(Exau?na#r{j^tQ@<6-&K#%5Pq&20_u4`AN)Y`Py#+S3Q`W6wWV~? zcZu)HTu)f>lA@ZerxIT*XD1?&cefrLgSY04Ok~RR|LA?lf}pXU1CcpwkwE-hHXB$wCQp%DXft zadt~HsP{{9sSo5C=bAz)C?t`(-`A_xTnvB^&I4fhE1jS}<`Yrk`!n=L+J0HG2x)pu z--&ofMpN{2fMR^z*evi+EM&wgXxAEcy3je_>ezbbeWNCfvIdU^YZF|21=Wf`bZR5^ zR8j9B$=!i$Fi$4#aa=1>-kpQvWckcF9ND-&K#%#{&Mus2&p(Zd<-9v4AFmR1o1ur) zDYGUVjK%;O9Ghe4FjJ)dp|+4=Pxx?t0dE?Y17lTlPK}aga^?pIO0wop10_D`0FNy` z_oSs*jlG3{=pQG%K)d0bnDr=Om82ZtvxmX)$e5PM{<+A(XEgn|c^&!=Ht=CDAMv^* z8Ygx~9oWl9%r1NRh`(tsAD;!|H+#t`qjR&7cxxrHPCm?u#!)d1Frq@zehyx_gUP40 zH*sD9RG2n@jNB8qj|{EN=zQ+9LPwl#qoZTLPe~;X0Otuo`@PNPPK3ngBSmO4xf3C4 zT2?=%NTJQ*PK3ncBc;%0Z6`wNhXlg-3Yy1hM`sFMY4frZFEMihuTew1m(8Lko2Pyw zO5=x9O3^%;NGVlA6u|w24CUC@*@R?LHN>MCCJzQv@DTqRCQYDnq#ow*v6`>Z*NP!q zh^I)t%ubTXzHz=ykxl8Ho6yby0`+>7v_vOf-bFu?1gbjCi)ga z@SZZ67wo4$R>GL+wx2FC%vl6$Qp@b8hLW@G@s3Pidn`nPO>))Y<_SxT)!a8h>?(wZKvJ@LBM)v?$ z7+Hvo6r+2DD~uwr9N!7Y2D4BY6JJ4N5L@tA4~nlKZVVXw36at5RGkeLvL;h|25@~u zk!P-obsohKFf@R{CP+!`&S@4@X34hBl59AgER!-q4nwl(FU`+OoOPDKM>G5>@g8Jqnqu4QCR*oFuK_u7=`6e1EZVmfl*lAg;69MZSYLt1KE5{ zAIMlwf@;2Bovt>29kWo}P(qDFM0O3apyXp>(MD`;i#A_lHj6<%d!tFjnh%Qr2gW*u z8^o9;Az8k4jEZ-7eJxIjR*zIrV}Tjth~{3shN^)QN+cLjGVGGfL0g#~O|za>?M%0bMgGd4?Z&wxW!iozo1vI&bgv=B`yN-vqSoz5lM zNZGoppkY8)#knXh`V;x1#xecl%)3Fq!TEAi@U|!(0$4TV(`@>?vIk5rI}GrPefiCKp7?HW>j$_5?17 zsHF*t@abt*)oV%Q5A_K#wFU7ys2_9seH`hcf%#lXcT1S*yjwQl^(bKlFU{Z!vkvOm z0$@+2ST{MPp!b=hIye=_bUgWO2jGM2D~{X<>dF z0ggrmQsK-;$?<22t^EwVMelaled4P?lxnEuPrHIS;uD^6*$0pna&P< zjCJAoVPZ}ig?S<_oETF?ml%rrk!j8I$5?|gL84BE!70gem}rSHX`z%+c^W7agVRDO z!}ByyCPt}+QpV|NpiB%`3#ANLCrXhm7(g(Ii9ul!6GtLP4Y42=3TXV0_jAx5PnJCi z1J54WY~Y=z(yf;)6~~XwZ+cvW5%d^RT~H5B$0%8nqWmEm#Wp6Q#WogMLn&{lS!5fN zTNu|Q;qwY6dnE*Mo;PF=2nc}yksJ@4LUNF#V4YS?V6v#eXoT^HjVZzSP8l>3ObLD$ zOv3FW!IWTg!6ZyR5=;s17EHq1Bf*qlYQZEdZDESDE<(2Mush!7RF@305Gol&(#NQo zfxp=`$kS928%^0pB6Mr@T%ooCj$t&8qhM7h9W;Tb7C@po< zN#N9Z2y98?NY}3-(v_CN9gF7T?_ndLGw&^1#}jWT((HGhhp^eiuh=mzLIXx%Mjcce z8-|Wg!TNhUcpnSzX@pDPt|44Ze+P+y9OPk7%(H<{sR0uHJyDJLPnZuJYmXSU_d}k; z_(jph5K-5dUs;fjMeMI*Njzub<79^Bnltc@feROET`xC zn!Dse0owRgg$u2(A9)POg238MH`su|S3K|@<_Ed87jntm+OcvqKc}vfa8>rUiVteV zrWcL}HNS))iXDgmItQlnaq~;$D)8sy6shtK_3(9nK2CSSGJXDgSh6oa;?IAXt}h++ z&q8L!vMu2`tyGf3)GpcQ&wmGnyYT|4s&ePzCUd!a^pxIG z2jT5z!FsQ}Roxaa>8s%F7QtL4Z&%P$1P@>z(9QkZ{n5?%W+6{~I8rwjC!$j{lt21g zJOlciZ#^oXI{*;)K_B;EGZl?CXktB#EfA&FqUWMQQLg$Oh<;8zCkjEa`W=YQP|t}9 zdQQK!^|aXtzsxNB>WpLZu;VX}HF*3BX>p9_b9_?{&DgV=2%C-ja#JWMOt_)q}>!LNzqoba~koKBCceB2K{Bs19*b2 z1$gJXn38DvNW`1KTjVb&S&je8@qfu=sMiNv@7ESjuS2@9BJe};)+OkG`&*bha`_jY zEv0AecosHij}4nQc<>SUDc&td@E^TK&@Fv!Jn#b_PIGYLBfF7b0=tEe)D9;eLPDIE zK+^0&@{$XD*7t-FiW)tH)`$a2+-mcBkg zpcMk4*sqq@4<9&CvW_@lll`rOu{NzAOrIOgLJ2L>rgthV{weA8*ztmF}ZH? zF?bOOzJ{o$n_8v?H!HXb+&m&=2TUTStI)+ijlro~()zL0CI2mS@^j%x(^#Z|i}Yps z7v@0}f#lx`zXbk`c*LLoJSNOpFJRzOtX+AFKOc$WXJKwFKL+@t^l~45P>Aq@#5Ml> z*Xda&p1p|zbha_3TRcIo`EvX(`y>5$9zS;AM;oTQ*XQ~`R2_)w;u{ZWLw$i&!e@cs z5^u?*KZ|DmS(wHNH?RSByFVY<=V$feH~PEm1U)-MrS{_qQS~ITp+A2QJ;PUL8eT!k z*XRMs3Tg+gWjJqm;+}g(ftC%Pu57wUKS9U@z6G8Md}ApQ1}OexQDJq9lZxXg<)-4E z<^KGg1PeanmcKX{2?%4KxRXg}7806;1Vk$bz5%We+|=XGe}mAxNqTQ{OH7RlU0qvxYpXJxkFvWIX-nUd_d@+Dm?RZpVoK<9eH7l&w zv`G18TxdWMEM)+ZVmeL8~o8V$NM23Lz6ak{^)&-wtj@%_MZtCcrTCI6{{IC z@W?v3di(y1Nu<7%pMMY6S@uVi zq>W~;GVJ(jf&C-HVi@GEp#Gyy>V^rU4b%y#x^*NMcUPmBEvORI$5VVmJNAoMqlWO4Ku+eEs)L{_K_U`@-kEU|5*f1H2 z;R7iXWmDoUm=egBEVCFa2nf)U<@85v2y4F%>HB}PAJE^heg2~DhY;Hj_4xMkfnh(; zw;flqf`B>3syl0}ZTK+~^MQet{!7@(Um`^g*gwEM z1{o4vvT2;w11U)WDtiV0*?NGkHv;Q{4JwNW)hR9_$W`Sc!ZsdAMWS}w#)FfB z;TsQZL8xA5ORAG>2S+zhcQ||9wjHd`V%rWD0k~SW!!GcP*bcjK2X!bunB4%jWzHi0 zwRkPp;=WvqAH_Q~9K!*s;)iB4_@j-sxv

F5EPPx$yVDCh;WZ!pCqwh`BIEicr4| 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%0AgR)y&19>eaFoSZnrbG<5DUZ(5M?MK$AFZ zJ|PWY#-&%7L})~N-+XOHC%UA>!_5iy^Z0=*8@H0yOiIvd74%|DToxKv(2f+i==87^ zmkF`(Bos&+kE<~BZVff#(kcECZ$OfJ#pc^&{9Owms?AYg#QuQ0D>Ne^3k{*bA192l zsTP*%8P8*r?h5NB6MDdJg*;VgT>Xc`M?y2U)GDgW6}=+p`<#)xbFX4&pH2a_zSdVT7gFpJUI}wfXeBB@6dz0Q&*rxmuL{Uvb~T}LUv@S?ASwAgRE=L z7*UWRh2W8Db2kcZ0~Jq5m5iUwXAHCBlv-P=_0cS~->b9@eI#7Ae%-sJejHSWvtcF~_yXV}P>s2enwoOo1guT{2CY@5d%Kv5hN5ZI-PSz92* z{6QD9p+FTwe{`ic#=cPb&n5qfAEJQ$18eS==Gs9EB~KUW7pe4tD^+IQ%29V^4$O~U zDVgc>iaziz_|UgY!COUjYbC>hVynYeofa*(Xv*9T&Y+LIEcVuV$th$vO3Re*4B7S? z+Kkbg-R9Lc2`!99J3mtiwSWq_K%RLK{@yv0<4D#68La8eR>N>iBHyjvhLk&oVo|Uj{I4Ec;5Z0*;Gj`#C%<#c-=TvBE2g90p))LP^1bEHWQ~!5~8@< zYzpZ_ZIWn(|o76T50?MO6nOO^lEM;=@#VGmr~Bvgeo2!^j2j9Do~^@E@U53ZO987drWtW4A zx)_==b)YUw!=kTbUjb{?pvpgiy+C+y)brj;v-^6DLdlkRB7V5oU31#kpMh|eTev9KoyGh!i{16IoP+*Ik3)`Lus#nWx zz_)+#Ur|@_0P@GAu8`2mrFU@24@#Nt^%9Wm#HtRVg65Z|Itgc&!7&QAM#svcwOj}g zE#TXyu8mHky(K@C;cyig*u+M2A8pSrZy~VHxEb+jU6dK z2Y@Wfv5wKnj+4xmlsEbD0BnUVY%-cz<u@6mR^62>%OXO*qRPW=Bht47hp*xn^ zEr+d@E#yz3L#wN7Z4s5m`x!jkM*O=aqCojO=O0_*awXr%V~D`GP`J_Dl6?C# z28Sy#u<-TR``yX+OA22FM=agva-HKUJkq%k)){8OY@lUtp!U?xV(W1#NR_;VI*YCN z3Zlp?)$L3X!6hu!di|Y|yW_0O!w4IqXJMZFj?6MF`47s`-&Aa@CFRQmx%Z=3cB_X98Y-Vbce*$=Q?&`a=Ms;~)N%!=L?Pl1Ac zi|hw>iGQCZ7?NKv!P_J-cL{FOOK^+$%M$F{Vf<3YpWIzBlifYkvkXT?Js;8Qo(%Nf z>t3T;=CahYrN&kd?JS~^742NsHzl+)7sYC^EI-ugVr}eA78{RzrbtSm!RrzsjS`Hy z+0fJ(8rW(aPN4B`E$Z-k#1$i*tqwb>cQ}XN4+q{+7&ajl7B$!NA2&{jiNX82Z{cggoJFj>j-TW{VcH8N5EiwWkRv3mBm zdk-tv{=ZBymrbyXej`Ua6W?{n}F>qFFC$y^Vir zGakBvZN_@n_Z-)+m`dwKP)y z1_48MGO2TM$2Ug@IesjsD7teT*j+-?c%#iZnO3SyU8J+`E2uow;uel)SEN84)0k^j z);iT2J*hl#;DbQ!xu4MR7Be-i#!0%4e{kKokNr{Bc#D?L=t(PH4jm5UfE@cXD(WGC8FVfjnWJ1_VC zWVJgw&^l8c@ucA)=lp(iG{wM*Z5&<=uZw0@*Qhl5wA>S`n+52kE+OmA6-q}&O03(x zz+0W(*eTaBLG$Gr`+9fK4yB!2<(~Fn_jfL3dO_};4-?ni&B*?>9B9hOMgh}95aRf| zIDje(%?Lm1fg`cp&DBW28vSfw#E}X15YxZoBeome=aTFdoovXn?qoUCy61Ajf1c}4 zK`>KZ?uM5vFyg4$PBM3Md9!be@wlwR$&Nw(!sitWl+**3koe6Im^J20LGx{YIuWQ? zsZXX(!x#1@sWvwuoqs^iO1}e9ZTEWW>x{=Ud&Z;mjvhGgq|WPSSiX}pG*Y|RItW~L z;5tuhU0lYqq{ms{IHj#{4(J^^FgG-O-ATk?K4jAe>`NNt^e|utetuHG4*&dQ+tVPs z`+z;VK@JWB_K*gzkWMsG2H3E*mA$Ir=mt*jTzILOCMDWM4Lsgw7Bv)NH~|vZ zfdX3wK<%5YZVp%uL=u6#pU6OmT|w&jXKR6fHW%!wwo*>#sl;?ZP{`zzo(>j-qdM4# zA48)=4|WLDf>ZjAk8i{;M%`~IA+@XD(YE6F@g@EA?Z;ORbb|3#P>nwLPV)P6@m=J5 zlgt;mC9i@ha)au;?3MgpwLe!U z3PiZSCxY24#B)`Z9hiH<(ECpunpsPuS2!C~b=qPJE0ZGeoDHg?MwPFSi-O*7VDf=V zy*RDjp$;*vcm{0Y2a}A1n_)O62+WLs2uiX8g_I#{f4Su6T8%%u=GTc z`|p2YZlHddH6h{+n1?&7B-@o`q~H=xsOb!&g3ebT&1>?v&%G8`SKJyXawV5M{?EDB z()(DyanG3$#{ae3*yW7yK7{eh+@9TI0GT3-|C4vIbl__GvC{ZO3sxDCB?;Nta-{r4 z^8I`W&B^$g8&)Pb)no`%sJjV21F1sshd2j}mKqdB>^x9rWocKSG%=b3qSJ8slON>4 z=I)995Wc?XsF*tP9TYtct-NwqRqNYc%!zA^8)40_4tl zz&z;84;k>;`C;yI-lFhKNsjZ?3|VW-46zha){_L5WIadbpTc}7Z5!>57AW$ZlcH%b z*mz?bnHkULk+0xxml502iNZ9+^g3@(h2m_O|5Cu7%QJ+=QIdl^ zM&O2&+Y95VZTgbbcna~;>d?p6cA_=JAA651T08$98b{WX?0HHA4;+G9jM(kku}U1@ zxM=vz&;C;D2J0r*57J`2Pga8FtBR`7oK2|83YhO%YMUA~kH(G|#v>O)qd4FR_@Cm}eZDw;sD1uUe7?n{(`m7eb1CtIVlu4lbLmw0 znt3ew-au}u7V+YEbK$P!Cj*wg-@)2bco@J6w>uzJnTvLN9A@+AK=VkaJmg&Z{;?(8 zWJ(Y|AK>G}h}>cieG#WiuES1`bqtj2uv007yCS&`t7UU_!t1cY*uI4tEDtG+3|QW! z?{|@7YQEDTb<5=pD5pW9*0(FP-{$Bk^u%p(B-<9SS^8^_O1aw!B0StVk9$;e|o zbTPK2`#@t$IxoW?)aCSu-#)5xgPBau~JoktKftjp;97%Ss5%W|fU zEWkwiDOBVd-f(5#+kE}|qHlN)sR@@TH_GrQyNH$(b?(KCM! zb?n@As?!pMNJ7?kX*sq870z*p6Qe&5l|X$&AHqB7^ux~oCdZtUA6fglcuGf}h<2Y)%n+xH*Xf+4t`vprp1159% z*ULUQV}*<9e*e)p<30{7go%FChmz+3jb+$UM+1gL`(DhmhYiT?#P9z zVqX6ryrfH=#Wau#E+dOQqdk$r(fPr%i1?029-lQ*kGJIDaF~R|D?1%m+UsHqPx%Xtx!%R|n2Z7H(Ie zUs{1U&bGp~fO|KSpnx~N_WeV`W_z@rO<%~a`Hl17ENrVWnYv&FAY4~%dlD($6DnIW zWO=&bU45k(2+@pGRGn@nFSqf?FQ}t+-&*n9c0WBKa7 zx;@=&xmV{!ho#DrALp@zTfXEcd9nB1=7H2XmOB$uv%AGQ@?*?#x##Xqw@UU#-AoF{ zM)!)XpYH7$)HJ#%&xBGb#jt{_6o6+d0MB2Y7a5t8eA zl=&4fn*qygkl}`X#|KRMI z?3Zzc%^l_F4n3F}nRB1bnJuh0CF;Sc*{BjhV`dV@;*!u)jq=Wbxm$p%mu%s%7Q-$w zxstHUq+2{o{k0Fc^J>iPMtnLrB-YKjehN^Y(dYWfdF9XOGyUYeE1%Kl`AH+TnHX{l zu+JUveHxu!)f%`{Ni^sbxg*MjiN18cJ<(240qopdog)5~Gi~_(Vn+rT@x4xVn>lcgZu(f(1M)duJdQQau@%DuXYI7yR@YH)1(R z{M!6`v%#smdmubn`3f3L-6TaW2=x?qUbeXTmrDu-YxKgNe9rgBDC3NtGIVaz5UD~H z4IAp55>+jMJ9oKYF$u=^@Cc5rc)X5&sWcvKm7rHan|2!>0pDx#Mh=!gf9(>tCt4Qk z9vC?tC(|jZ2VVM_2E0;;FMYhisL+h|1t4@)4!n-P#xDnywl>n*D*iyFFQ%xZ)xN6 z8fd9YsV1g>S+!f}i~wXt^dvfXUw#ayPgZdXs#;cbmZW?mq!FEeS&>)VZxSxZzluwP zvz%F+x_Y%IBD`X&z9Hd?J_&=yI@ISHPwkV&Jm8Y==Py zD>My7&J;(7ioZ{do_D3cQl~3#6>}d)-GMuyozg1SLFM^bp+tBUd?n`I0{Om`uM(;6 zzJg4mw0Is=o}U#cfMBzc$qxsQn!(UMxQuuSjT!5PsFL|Zr$;9{y&D}My&I{PjCSQM z8%Hk&?%cm@tTGQR8%OU2?(AGPR+)#FjicKFcYd&JtTMZo4HRc;*#I$)bgrj%DkC>V zESUC8J8tIH{2Rq{|3LY6y5;-90Qsi!dx64D$GM;Kzmqy9`L)}~+c^D8Kggf&JeQbArIo2F@rx6zo+M%>sDKNSv z$NZHORmK|S$C4oeT(Xu~8vDmuVXjmw0Qh$pSF8((4zeA_r7M#;jLV2`QBSNO5`_#> zavY5KFX&I`Od-~C=uCg|BaR0C@W_5E`rP!bHIam9-)y}e|HS8gU zEICq3RzFo4d&qU6q7i zB3jf1Pj-JH^fgG=6#<)E3k|0F5$xm+Se~B+%shMx#c37&%pA{M=d~wgU0z%#3m?8! z^sqU;sBhGTXIX$o;4m}Si0OX4F~?%999nER0={ydWy z_N*f$Q@MRAxsm=}o^Q_F#)L?j%AIgzT1(*D3A|JO??R2|Vw62heZdgD)9A_%|M+B?)dLjU1-G z0;k$nG|GMZB$_-#Y5V@TVzbH{SavP4J%V+-rM`*2U<>SyRFlgPhw5h4@xUniu=MJ< z({UB)cX-*pJwyC-S4-!f+2bs_bt4b}#V4G~PHQGT=Wz<6*94;7Qa?bNwuO*02$7o0 zmBcGNW%=qQV0}AZ$|PhrA$xSWsuxI1Q9}sXN65R9h93Ot9n!oj_2ZV~!JicKxo!yY z&LZ9p;>oeHN?77}^eIfThB6p;Hfg>?Vg|-NycxN6=$y@vMsD|~3O;i@$zN#~;H5*P z?WLu^g8lD4p8puPCrAp}z2LiZ4yk0@%y~m;i5&X0@BVc5X+qsLb)Y6EyK7K;Q&CB2 zg=p3vXx_Ft&ZD zEHDV{$70!1Qm2geS;CJIQj9wGL=4MZ1HQ9r<;xiu1&iZG3risrp5{HYGg(`Jp ztupL-jG8$u@>bE;ZT-EY%M7YnFUs( z)WUeG1r8Ix`+2|hAo_r>Ddvr^RFiX5H&?HRKlPiuA(gJRJ)>|R<}8AfSM`$AZ|<}` zSBt0Fu!hpCxEc$YcrQsF?A6}pl)T-)<3qpuuFq!dHIOTBx0xPiP z%y$&S%lqRIygv@lXs&Hi6^~vXM7g*5-jbVEgw0m{{+NC=Y|km`ow9m=Z05tXVq@%g}p^jfxcQjN`b5YG_P9o|l$0e)dyn8R%*0a1zUuW>QM` zgn2Xxt)j+I?17;!qwWWA&{fabD?Q?mjXs-$vxZ`l)tE0vF80S3U*d`k^Be1K$mbIU z38|SyW@>C&@!||1)pJ@zJSL6NtxAhaCNx$FKKsQA8yE)XRtR9ah=akL_Chw-Fco`r zE$t$gyQ9<2r~QwW_+|NAzmgST-kF@6Wd^H zAU3JDMb5%cs6~J6Vz-B_X59|OV`c2? zP4YVv%ZFIlf~c;KpG3VFR=cGP)7~xJWW`;`h@qF-#g*J&inUD#NF8w{l&e8r)qADYSZVAbZgsvWBJ-9Q0c5BwB9T1iY3SCeSE;-j+m);FG39FNQm&>I%C%B8 zfVr|>T*(yRtgKbmPp~#ERMwBNHqKR6Rr+jYRj;L{VKtK)i6Id(H9cp1A$D&o8j9P@ z;0ALeD@di3%NkCSfH?|XzBJ85Nza*Fe6m;`FDN-Afk^i4@L0ACPvnIR#Sdu|MC!t$ zB3{WiWH!%VgwKAlTXu&S!s5eg_Z@Nv8@B4)YBpIL-0ci8WKAM17&#Xku9PCeX5E#= zm?3Me6K%PQW;Lw=NV8i#xuxh5)mLi^iZRoI`UQf{Yt=kc_E?iNd7<&hQ{Xd8wLjpq zxT?yk2g1t>hDb@gdn0DpezZUe2HnlYDKjLy-y!?Q@S|4iF(AlHd%0LDhqItyfsua{ z2Ae3<71wyH&GG#694U|o{YJ6pRajS7F_5c88Vyo|K8ENilO$ZAo3E=`dvJ_V960Pj zfX)gcB2q$PIbpFTDaIA~F3DD7W59>a_cX%qk;!5;DX1C+8->lcm-xzVGyvQ!KD>U}8Tm%ta!xt?#_+t{L1v7o(}SzkR6|cDHLK%bM6~&r1>K`L@Bc@bIh_GD z8VY=6o9f`VjQBP3E{u)08TU|3YkV|-2#tVDv#f2TW20rI>SjWZ?07fh*jlcJq8??f zimnhu0xeGotUsA?D^pXhNOW{6DwEc#Q^9&rAqgw8 zb_lM4_f9+3D@T`h2=Oq70U6Yc08sWF6u^^3zfs89dnh#s~xM9qewe; zD@U<*%vO#e+A&QzO0=V0IfmkZ`c^e4)d6iPU>FW_7w=fihM|N8tlurf=)BI0nl`k) z1CDgL6)LhO7R$|0-7o17B+NjmW zTy0F##zbvYV)SZ>_#rr0^s~^~CLvGU(YXjxky#+(;D=+ExgqvaGcc?K@mMlzLfF~z z#w9G z>~*m@+3S3DPGMa<`3giTU#me<qYyR6f2;DB7Q`PmV`V|i zjx;rbNvuX`&`M+7A*)GR5nEcPZk4h&NLd-dr6NuBq8YkfefeQG$at$Z--Og>rIC_Y)K&J?=3c+KNsj|wIRSoB4>8u{q}iPOpadDO zs!`3>=q>*5@!!ve59#FFv3A9p zWMOlWaif4jHjX*@chVoY9dq&%(!#hMb8@Cx9dmM~c^z|drkNdcvTAMtQ<}Xu{gV;) z2Pvc_Hj11-wK4Xo4OXOp`@AT(YZ~)s`YLQ5rITb3(@DEAR4Y2`1$$&^{mxDXX4xJD z;OfzM(`Mtcwl%H=L)WB@%L?XjgSucal`oiqO(j2QT#~3)nMxXI14Pi0X2H;>CB@a* z6y-T7cGs^IPBUGO3^$!7TaG)M>I=J9IeUpK<;aliRnB(eX~;I`q@bL)vgJsB^(p5c zvZ;QNlZyIC>qt3zmgLmu_H2sjIVmXTwQM;uZ8GIJt?D%2McKr|auQSA>rQdf)xE0K z4NI=B)&Xpp2B)|^#MC3dLILaaV)W8}I=yFt_U(I(*nAqXDa;f_ERY~LpUTxzXe{bY zouO?)yMAmu^jnd`Xug^{$w7cY^Kh(5rm!no3ZyK`)x-N}(Py3uS~wPcM`KStu0|DT`q#_O+z{x<#^;wN#e$KXai_Uw&mF7Dw`b4D^ff7D7M|o?=ft% zmF;nCioy=A!q%?*e&f*A!PR)RDX%Ak=6mJ=vr$HN`JT=R!tA+n6%HDggXEfG=;p{h z0c&xa9JIwn4+lOA7SMiDY3N<4FD|JmE~z6fsU0q<7Y$p>kwhJ4J?3)E6_}4=K8E=? z<|@qJV6Mh|GF7fi%bWLWyfR5ulGGV}3(`qh*Z!->U_ey0inCD*u9dmUL<+7~lU5VN zn(z#62pvb%nlC}tN-KiAgdFIH{4`&;q8sv~>*G7k=!WP|kWic4&ozS(odIFWE#@Ju zvg40k=p*G%oM{<2PK#b~vNON&VzlQ+*owPgY9dq&%(vr9xbMi{3`5bd{rWqY`a;7;QbFykyfk&FR zH^G^a3l72gvC|Ttgv~B^OQb#3bxNc#)+FuXivGYM0djiCZ01Og-q}j;w5bgA*zQbb zsZHH(y{OGEVj1G+b8IS^qBesxdU0H8--}k-nvLF)6Mc96tuJaiQ<@_~mpKI4KV*~q zA}2|rKDbRvkm05iaE$EGv$tncOwUO{A=`B!(wBV-8JSITSxyoPVaHM&*TY}x?KGQH z(6NUymG8!usXPU3)&B7Ua*2&D}rsh zi=yY{^keQ!>*-4rCvD|zkIXSkp`6H|Adhww7D_imJ37;@O?yR#5-M_T7Lvet=%2e~ zKB&|%v*v@;99q&+b2nPVp^#+VS+k#rRy5(v<^h3j$cvy1zL&VQB6xfcK*5%{dgCJSkv zNHqfdYvAsb3qn`)0?L#)cmc#H8p!934psu=^oh04i_#13UE(Tx%T}c!JcO7bj_E^Y zUJ#9h>WzXQW=!LuM=0!BfaLP?7@WTcIbJH2YXQ4K2KBr<YW@LGZ zRJ7t0uxwq4+@MvA^+YZRTc?Dqv0kJ^D-nTQcGR6E4G}h%pC$3rtpWrJJmwd{Ggpi< z6#F{&ynleZN(ji|)U>i5J_B`~iINU$Whq|L3opHmf(^h*$M?vI7rw$xs%~7X?d7#n z#&Z(v@CB%-yr-~yzQgl9RvyY4W66mj7@^ZIL_eUMrLh%ll1HVeJ+}TrHd8r{J ztXoQWj&FYKivr`3LUL2N$V-bWPsW9yzJEkepI_&iep`h;C)_05b(p(T;U2qskCLj; z=@0ppniX^RAL#&B>O#acIvy&Tl3tk_tn6f@E_V>i$gtoxL&zSqp1r`7%-p}2my56z zH7Y(sE5*I$=Ah~M8G=KQe6v0JY3j!+b7_c(5kI8K2-qM5P7zw;vVX*A81ggZRr!+h z4y1l?TxU4F=Wpc^frHjVHe*h~l7EZ;$G2{jW#@YS1^mz8e{?6#=2%RxE5hL$PthXArxV$IXJ-{eU%=G`jK=07O1#;ZFD$=t{-oE;Go^tXS`QgL%e2jD3B!q&`HM%^9;D6F9{0|m{Gg)^y|Csp$B12B8PZU*Z)!sb-k(a*z) z1HI5*RXe&Y9laeZJ-`5yIW$`OW{rY`CLw6`yt4xpi`Pd7R>eklxuS!7x2}zjtcoq_ z^0*?GNG|9Mtg0_63z{F{N>tSkojtaG(QL?tU|XQRl;xBO*#@r^dK%IjeYdWOE@c4Z z2kS(AM}?YFJdT!{#IIxmFm`wVMGexyRl2%PgPE;8`~v0?-)lzfY@*u_xJ?A7M%!yg zpOKEf?^8`-)D4iSq~<)K<_(<5C^+kjim7E={a~}5xo;j&2gJIthcN30`^bk!N|xtW^}FnU^;fp{S1j*##j_cNo}~} zMA*a;-AV9djzI-ns}cJyLH0Fye_%YMN4%t`GK9vs-OnnY1D5J%rD%ysA@KQlsl<8uOp}%FaF!x~xY;|`tUn-i!$9PCxdzP@gn%&8t1)utoPQ9{jCRWEG1zC26L^CwvA~R+>K9GM#EfXASsqOg0;Ns zE5($R!@d@-X^|IT4K^Pwm`FtHM`hS1Dcdw`Di3>N!210P5}9iYrby856F8P0{S9jX zUQ(R(dwJZ9<2L0emF`|!Fk3lqW{@sU1=~ z>-TfDqfR+iX-B*a$mODiiiCRaJOq3u? z^@mk-)T~)yg^zmBON!w&YaSIkH+&QYBPw!;$Y#xNEYDH2mRbQK23KQv&EVr0MP~3x zuETiT>;I!bS8YEmH3f`P(xLCjnh812!^}r6q)`|W(yGj_xypzuNbDgzLQD_TAX@04 zr~&ENQZ8Jk+eG$)8C|8cPDCG7DkpCE4Xl&QU277`CT>`b;Wcjn8q`b#pH#fKEJSvL zjC?h&c~qcQ5loogKj9I>>=$H>5eTc$GqIjG3`!smobOq=vM|v1BUctR4)B=sG~mJQ zBoaLo55h99#mc^f2{+n@Kbo|3u)L)wLQ-`J3Cg(ll-RFzwDg4J&^&i#U-xDV%U_2 zyKNb1_<7xG9Hws1yeHr#2t4=Gc-SzV|Is8X=%h2w5wuBywC9)bM6&=i$$WH={FQ7( zja%T&m&X=7i7;;2Y}`yo+>!(unM<>bW1hjYj3u7IZZ*uSV+r1fKF_~JR6#H}2J;?@ z`2fOK!#cJxKw&bp*|Y@^FClfpOOO3@BGz5VU4)E4BmTjck}h5>ky*<02!vThWKOIs z!B-Gm7N0}i$-rX=+Iz}K0pEJc$f*9NDRWpfCfG4&vPe}F{u^sO*|b6d@DoFgkrFa z7W>(HTaU&CQ7PtIi5e+ppc@NLs>*+~$s-F_Qa=xaUF=X~FnW0gk?NCtok|Bv5*S=n zl7LELw(Z#OW*t8KX_Z;mB>A%)XZWmKM23t(-k3~#Ltcn5eil+ynZ%y|iHBPL2;8Ke zi|{&x2|aD=GhU_^H10Zv-#xDpq+T%by67wBvh#R{k-UkVQS4fbyy$7XLz>S2XmVBv z?JQ7yPf@NQkGnUE&t_PeWeN&yc`Ssue8@`o0f=b#4(YDqd|U9g*@j31%+&R;m?6pq zYufXWbv;v~QB92@59izU_{#Z(o*)y3A2&l9GBbp?OiK_#PNtGZRHPsfRVz(mdTgYX z$TuX}#af_16+Q?c)Rop{+OJeYEIw34w6Yi6tckQPzM^n_ROEY`@?RU9(uq<)j|HolZuo*{k*K;JTft2zr`ERf-`YpHt6&0o14*ATQE@j z4a!&M+lq|aoCB^MRLmego1terh~>5(e3-) znIttPZL~w*lNdXU;hZQ>Ar_LyCC;C4cW$lxxTpy*++5&O_ToWTDs%EyYO_;Wb#3GjU-lEa zR)8-Ksa+!0=7ynapQkYC)NZrUQ>(ut3#rwQRIPUQ)JmW^u2yY*Yt>%+MLJqKIvpLV zdNb31x?ic2tMe-A*%I99X$ii_e_MV_unluF|1F-L4)00eZ}fEL^=g~OOxyh6uH3dc zA6KSrzN_rTgU(W>YMXV+`i*VVaqPrxGoLK}BW**I&^!?%xB66%*7B@Lr;2*FgiHi! zFx@6%3!6=H{UGD!O!y?NBC!c%7^}EqJi%zi@VK;`qND?sYcJvNq%4d}b~qzu#p}78Qdfr}>8n9-qgqnc1PrjjL@qW&mgpUqC8|k)G=_5@ zH-%+wQ^2}m6QK?dUGeXvD#q|P34NXa(SreXt2%|AD1JgZ))@XWoi>G=y<&B4W(?mZ zZaVvB7_V-oFy6g_Cnj$+Zo)&D@}|5)z`MHtZvF_9Ho6%#=2H%0qA#?aC%DDHkO&H1 zFeV@1{}unOWXupz5ClM}GAQ8pep|}xAnCh7lh?Z`Nh+*w`$;0*xKdi(82$nUWCo4O ziTlh~xuPV$DX&PTu*6M1MtpTcU=062vZb0ge?)t68BZvcx`={`8#M+-N%|COrbh1b z2ZK@vl2Acv4y3e~lt1|`qP>i#3jGud_oS*$2aU_RjEDaT_VZ;oB$wU#Je&VjoX8gj zM{VJ70`KPlJRS0Vy1+Z?B_sY*e2B7IM?tUc9Pv=}lPpvr-;Vi(m7@?6{Ua4oEsz0O zcNIf$z%1@Og8;xE^o!e_Iq@D`AdFXylKYHXSph}_SMaYTO~DM($owh=r1C^d{4mR} zt0jI&8=q_AGi@By#sO`7s*Nr&Ixm6j%$yIgt3YoL5`)Z6to|g={|o7%Vs!#xCS^6F z{ie{D#i!Q_$dj6hD&CBWDK8k4wiuTs7Yt%{FDNiBdt+X{zyA5L^-rEJTNHJo8Q;QL zHmAtyBo8FfjS-z(<2%^MXsnMdE z2#lo3&*SIZTB{KyBTHrwOP`duk81p_%Ck|ik#~(~*E7Ly+}tfT;q;t*ghD=1U>TDE zeR9G2nbK`jo^TMTI;_H9$Pk%86lOzn<8T* zAaP}s1SCktiAni`8)c{=9Nu{3BrivB$gV<34qnbqlk$uHo@lo2o#KM${~zk!1wQKH z-2dNPfMCJhC@3oEszHNFOb`?*)GQ<<(FCG~lGs2w3(+K+HLj(tX+1r1 zN`FVr@$^VbTSaQolL$ft)Zi8GcqhgS-T^86-k+J-%_cs+e2+~x=Btd$1HE^E-Y4S*nm$Z&tTCe!)9;lJ{;D$5#tL>x7H^*Z^^UX z)ct@%-qbe$?Cfk!LrZ?r}stCPC@{(2jyP=+AWI|O7oOLJvvKt9Fx!^AS&u+$I&fWa0 zyFGa9)+W`GBY!^9^?OZL>{NAgGaH%GIP^y*PowJR)0P_AK=JcHDb!{(E*WqQ4kTf~ z)gnk${TV*bSowx}V6L|aMkX8i6yWLu1X9O(lzVs``)uW>QyphMV!h zh|*o&qGyz@&SfW&_sCQgqeE=)@y)xM!=_1SuDaFhX5!wo3BUQF zGWdHk=uM?*l??bf7J_%kLG8MD_^rmuH}q_iq}Ys^Xrmbudm>ur^iWub zu1G|>^#i-Q0`2d`2L?aY{XN+y#I^Myz%D!P2n0sfoiX4&#sCIloBi(LF=3e@BI0aP z+cQMaSNev}QJytK9Ec1NEJb<`5w#2vOmu8!r#EjJy#me{25$B?w4=->@PHaf**tOY zkKoh28?GPC0mA{iTOWAloj`bw>ccrZOq;R172D3o54vxUW*cV+S({;67h6w>!3+5> zwz&*=o4(qw$KH=(U6)w*4y=2k>s2WF-`n43#bfNfUL z&4C1-_zG;Iig>!T4Zd(y53@fp%zl@$knE6|&h{`{M1DHzD47=B=#od{duyng$t}dzTFOB|cP_8#ddU2BtBa z>imYD74&Am7^r8j>3n95Pc$4bRa(JPrHhU#7rl_qyKsYdbZ7QcBB2*m#a|x0Lx%YF zw-N?3G4Mqe0Vc8CCr6lkG))hO)yIv#Lpd$l5_&PhV3zb^ur0FXY*(0R&r7xO&6J4E zFRWB4b2>ILQmd~64n zH&&AIWng*Cls&ZVlG%6D7XOgo^|Jj76H+G;2@ zjWv|LHm6eAoYHPn%;uD1Ez@WhDpTujEB+Um&pLS%aehqp9)6wtWL}@u^8LiDgcHz` zz>?-eHjJ|pzMsTz2tOacVf;q$8_Ca~A09>2)LGxq_)<#1oB9PoQpDMJVg8#l---FT zY;Wpg{Iq;YUUD}!@5JsubCh}!8#CZ`U&&Hxu0p0sENyxyJh-- zw33=5?KJ6g&ifNqCCo`nJ|Y68wAdtf7C9=}EA%n`C1^aPPidlyy%aA)-mNR*-P@e zl%5Zjo+X>yyQC@9XX*&v%+g>$)m18*xtk$knl8R6-y}(q8iZLHM-u$@@Z0ZAW!X@| zoRI}fDvJwa>?U$+X7Oo)lIMNi)Yl9Fmfgr^DIKuMa>sHadCC1QkI4laZOTnzy^SX& zm{Q(O3A0{Kt`n1PxC>#pxVnOk^kKhr2DGZ7G`;i zUjCG+@93vkhoiL=|XylHK@C-J@$k8p>Fuh)77`eDBAlW8kx zoSNN6AZfen;;$Qw!`!VDlU6>-v(_k9?^V3vUNqpxic|sfk0z4(qW9KcD+9CHrgs5& zn^wxEWexwkm%FTW;;P1)@igb#8ExoVT|SBO%xc|3pyv^&5whaySADU^Q+uj$>O`0V z*JEQ`e~4=h`|`8)aMZRpH}qpfg^+6CFuG+H=b44*5lAxPP}H1{Xo}s0RPvFM=1tq% zllecJ6^w6)kFqPc$7YYC!=eE{Cw!B=nvJt z?zR9+NG2ZJG~My!ZOb%h-Zo(#qtUOc6gnN>^>hAEbY*VC2k8Z&CkxQo655d$dKL{9 zEkoru7uDLKg59~{g0|eykQ8>_o<%IY6TJ_iM|*bNNUAEl?+1x&uRY65gAx9m-QLtg zB>iiWZgxx)Q(&VnKLwgOosm5(Rd(%9u@8VgNbRK2FJo2ADMl$P*aP?}Js8}~%E-`n~^UD(_?yfssQbcruICazw%Y8%ztBR64 zXHSqZ8`s_qo!2}%seSKYj-~V1z&kiIeBR2u_IDCzhEvYv+)}*MiS?fc zfB9DGqxI;|{*}adjhx3SE9bGs%6T9Hf}95;AVuvXtPIFVD+A)UG9W3)fK02u{@gfE zUGlWsvd)d492YzZ0pO-%D9S+n

w9p2{TaQifkt!nhOfbkc6>fhjF-RjVvG^yQ3r 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%0D(JtqBIvJNQoXohNySoynOayr|C;=&`Q?kJ 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

BJJ|q3$Acc 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 0] [Units: vx] + int width; + + /// The height of the field along the z-axis. [Limit: >= 0] [Units: vx] + int height; + + /// The width/height size of tile's on the xz-plane. [Limit: >= 0] [Units: vx] + int tileSize; + + /// The size of the non-navigable border around the heightfield. [Limit: >=0] [Units: vx] + int borderSize; + + /// The xz-plane cell size to use for fields. [Limit: > 0] [Units: wu] + float cs; + + /// The y-axis cell size to use for fields. [Limit: > 0] [Units: wu] + float ch; + + /// The minimum bounds of the field's AABB. [(x, y, z)] [Units: wu] + float bmin[3]; + + /// The maximum bounds of the field's AABB. [(x, y, z)] [Units: wu] + float bmax[3]; + + /// The maximum slope that is considered walkable. [Limits: 0 <= value < 90] [Units: Degrees] + float walkableSlopeAngle; + + /// Minimum floor to 'ceiling' height that will still allow the floor area to + /// be considered walkable. [Limit: >= 3] [Units: vx] + int walkableHeight; + + /// Maximum ledge height that is considered to still be traversable. [Limit: >=0] [Units: vx] + int walkableClimb; + + /// The distance to erode/shrink the walkable area of the heightfield away from + /// obstructions. [Limit: >=0] [Units: vx] + int walkableRadius; + + /// The maximum allowed length for contour edges along the border of the mesh. [Limit: >=0] [Units: vx] + int maxEdgeLen; + + /// The maximum distance a simplfied contour's border edges should deviate + /// the original raw contour. [Limit: >=0] [Units: vx] + float maxSimplificationError; + + /// The minimum number of cells allowed to form isolated island areas. [Limit: >=0] [Units: vx] + int minRegionArea; + + /// Any regions with a span count smaller than this value will, if possible, + /// be merged with larger regions. [Limit: >=0] [Units: vx] + int mergeRegionArea; + + /// The maximum number of vertices allowed for polygons generated during the + /// contour to polygon conversion process. [Limit: >= 3] + int maxVertsPerPoly; + + /// Sets the sampling distance to use when generating the detail mesh. + /// (For height detail only.) [Limits: 0 or >= 0.9] [Units: wu] + float detailSampleDist; + + /// The maximum distance the detail mesh surface should deviate from heightfield + /// data. (For height detail only.) [Limit: >=0] [Units: wu] + float detailSampleMaxError; +}; + +/// Defines the number of bits allocated to rcSpan::smin and rcSpan::smax. +static const int RC_SPAN_HEIGHT_BITS = 13; +/// Defines the maximum value for rcSpan::smin and rcSpan::smax. +static const int RC_SPAN_MAX_HEIGHT = (1< void rcIgnoreUnused(const T&) { } + +/// Swaps the values of the two parameters. +/// @param[in,out] a Value A +/// @param[in,out] b Value B +template inline void rcSwap(T& a, T& b) { T t = a; a = b; b = t; } + +/// Returns the minimum of two values. +/// @param[in] a Value A +/// @param[in] b Value B +/// @return The minimum of the two values. +template inline T rcMin(T a, T b) { return a < b ? a : b; } + +/// Returns the maximum of two values. +/// @param[in] a Value A +/// @param[in] b Value B +/// @return The maximum of the two values. +template inline T rcMax(T a, T b) { return a > b ? a : b; } + +/// Returns the absolute value. +/// @param[in] a The value. +/// @return The absolute value of the specified value. +template inline T rcAbs(T a) { return a < 0 ? -a : a; } + +/// Returns the square of the value. +/// @param[in] a The value. +/// @return The square of the value. +template inline T rcSqr(T a) { return a*a; } + +/// Clamps the value to the specified range. +/// @param[in] v The value to clamp. +/// @param[in] mn The minimum permitted return value. +/// @param[in] mx The maximum permitted return value. +/// @return The value, clamped to the specified range. +template inline T rcClamp(T v, T mn, T mx) { return v < mn ? mn : (v > mx ? mx : v); } + +/// Returns the square root of the value. +/// @param[in] x The value. +/// @return The square root of the vlaue. +float rcSqrt(float x); + +/// @} +/// @name Vector helper functions. +/// @{ + +/// Derives the cross product of two vectors. (@p v1 x @p v2) +/// @param[out] dest The cross product. [(x, y, z)] +/// @param[in] v1 A Vector [(x, y, z)] +/// @param[in] v2 A vector [(x, y, z)] +inline void rcVcross(float* dest, const float* v1, const float* v2) +{ + dest[0] = v1[1]*v2[2] - v1[2]*v2[1]; + dest[1] = v1[2]*v2[0] - v1[0]*v2[2]; + dest[2] = v1[0]*v2[1] - v1[1]*v2[0]; +} + +/// Derives the dot product of two vectors. (@p v1 . @p v2) +/// @param[in] v1 A Vector [(x, y, z)] +/// @param[in] v2 A vector [(x, y, z)] +/// @return The dot product. +inline float rcVdot(const float* v1, const float* v2) +{ + return v1[0]*v2[0] + v1[1]*v2[1] + v1[2]*v2[2]; +} + +/// Performs a scaled vector addition. (@p v1 + (@p v2 * @p s)) +/// @param[out] dest The result vector. [(x, y, z)] +/// @param[in] v1 The base vector. [(x, y, z)] +/// @param[in] v2 The vector to scale and add to @p v1. [(x, y, z)] +/// @param[in] s The amount to scale @p v2 by before adding to @p v1. +inline void rcVmad(float* dest, const float* v1, const float* v2, const float s) +{ + dest[0] = v1[0]+v2[0]*s; + dest[1] = v1[1]+v2[1]*s; + dest[2] = v1[2]+v2[2]*s; +} + +/// Performs a vector addition. (@p v1 + @p v2) +/// @param[out] dest The result vector. [(x, y, z)] +/// @param[in] v1 The base vector. [(x, y, z)] +/// @param[in] v2 The vector to add to @p v1. [(x, y, z)] +inline void rcVadd(float* dest, const float* v1, const float* v2) +{ + dest[0] = v1[0]+v2[0]; + dest[1] = v1[1]+v2[1]; + dest[2] = v1[2]+v2[2]; +} + +/// Performs a vector subtraction. (@p v1 - @p v2) +/// @param[out] dest The result vector. [(x, y, z)] +/// @param[in] v1 The base vector. [(x, y, z)] +/// @param[in] v2 The vector to subtract from @p v1. [(x, y, z)] +inline void rcVsub(float* dest, const float* v1, const float* v2) +{ + dest[0] = v1[0]-v2[0]; + dest[1] = v1[1]-v2[1]; + dest[2] = v1[2]-v2[2]; +} + +/// Selects the minimum value of each element from the specified vectors. +/// @param[in,out] mn A vector. (Will be updated with the result.) [(x, y, z)] +/// @param[in] v A vector. [(x, y, z)] +inline void rcVmin(float* mn, const float* v) +{ + mn[0] = rcMin(mn[0], v[0]); + mn[1] = rcMin(mn[1], v[1]); + mn[2] = rcMin(mn[2], v[2]); +} + +/// Selects the maximum value of each element from the specified vectors. +/// @param[in,out] mx A vector. (Will be updated with the result.) [(x, y, z)] +/// @param[in] v A vector. [(x, y, z)] +inline void rcVmax(float* mx, const float* v) +{ + mx[0] = rcMax(mx[0], v[0]); + mx[1] = rcMax(mx[1], v[1]); + mx[2] = rcMax(mx[2], v[2]); +} + +/// Performs a vector copy. +/// @param[out] dest The result. [(x, y, z)] +/// @param[in] v The vector to copy. [(x, y, z)] +inline void rcVcopy(float* dest, const float* v) +{ + dest[0] = v[0]; + dest[1] = v[1]; + dest[2] = v[2]; +} + +/// Returns the distance between two points. +/// @param[in] v1 A point. [(x, y, z)] +/// @param[in] v2 A point. [(x, y, z)] +/// @return The distance between the two points. +inline float rcVdist(const float* v1, const float* v2) +{ + float dx = v2[0] - v1[0]; + float dy = v2[1] - v1[1]; + float dz = v2[2] - v1[2]; + return rcSqrt(dx*dx + dy*dy + dz*dz); +} + +/// Returns the square of the distance between two points. +/// @param[in] v1 A point. [(x, y, z)] +/// @param[in] v2 A point. [(x, y, z)] +/// @return The square of the distance between the two points. +inline float rcVdistSqr(const float* v1, const float* v2) +{ + float dx = v2[0] - v1[0]; + float dy = v2[1] - v1[1]; + float dz = v2[2] - v1[2]; + return dx*dx + dy*dy + dz*dz; +} + +/// Normalizes the vector. +/// @param[in,out] v The vector to normalize. [(x, y, z)] +inline void rcVnormalize(float* v) +{ + float d = 1.0f / rcSqrt(rcSqr(v[0]) + rcSqr(v[1]) + rcSqr(v[2])); + v[0] *= d; + v[1] *= d; + v[2] *= d; +} + +/// @} +/// @name Heightfield Functions +/// @see rcHeightfield +/// @{ + +/// Calculates the bounding box of an array of vertices. +/// @ingroup recast +/// @param[in] verts An array of vertices. [(x, y, z) * @p nv] +/// @param[in] nv The number of vertices in the @p verts array. +/// @param[out] bmin The minimum bounds of the AABB. [(x, y, z)] [Units: wu] +/// @param[out] bmax The maximum bounds of the AABB. [(x, y, z)] [Units: wu] +void rcCalcBounds(const float* verts, int nv, float* bmin, float* bmax); + +/// Calculates the grid size based on the bounding box and grid cell size. +/// @ingroup recast +/// @param[in] bmin The minimum bounds of the AABB. [(x, y, z)] [Units: wu] +/// @param[in] bmax The maximum bounds of the AABB. [(x, y, z)] [Units: wu] +/// @param[in] cs The xz-plane cell size. [Limit: > 0] [Units: wu] +/// @param[out] w The width along the x-axis. [Limit: >= 0] [Units: vx] +/// @param[out] h The height along the z-axis. [Limit: >= 0] [Units: vx] +void rcCalcGridSize(const float* bmin, const float* bmax, float cs, int* w, int* h); + +/// Initializes a new heightfield. +/// @ingroup recast +/// @param[in,out] ctx The build context to use during the operation. +/// @param[in,out] hf The allocated heightfield to initialize. +/// @param[in] width The width of the field along the x-axis. [Limit: >= 0] [Units: vx] +/// @param[in] height The height of the field along the z-axis. [Limit: >= 0] [Units: vx] +/// @param[in] bmin The minimum bounds of the field's AABB. [(x, y, z)] [Units: wu] +/// @param[in] bmax The maximum bounds of the field's AABB. [(x, y, z)] [Units: wu] +/// @param[in] cs The xz-plane cell size to use for the field. [Limit: > 0] [Units: wu] +/// @param[in] ch The y-axis cell size to use for field. [Limit: > 0] [Units: wu] +bool rcCreateHeightfield(rcContext* ctx, rcHeightfield& hf, int width, int height, + const float* bmin, const float* bmax, + float cs, float ch); + +/// Sets the area id of all triangles with a slope below the specified value +/// to #RC_WALKABLE_AREA. +/// @ingroup recast +/// @param[in,out] ctx The build context to use during the operation. +/// @param[in] walkableSlopeAngle The maximum slope that is considered walkable. +/// [Limits: 0 <= value < 90] [Units: Degrees] +/// @param[in] verts The vertices. [(x, y, z) * @p nv] +/// @param[in] nv The number of vertices. +/// @param[in] tris The triangle vertex indices. [(vertA, vertB, vertC) * @p nt] +/// @param[in] nt The number of triangles. +/// @param[out] areas The triangle area ids. [Length: >= @p nt] +void rcMarkWalkableTriangles(rcContext* ctx, const float walkableSlopeAngle, const float* verts, int nv, + const int* tris, int nt, unsigned char* areas); + +/// Sets the area id of all triangles with a slope greater than or equal to the specified value to #RC_NULL_AREA. +/// @ingroup recast +/// @param[in,out] ctx The build context to use during the operation. +/// @param[in] walkableSlopeAngle The maximum slope that is considered walkable. +/// [Limits: 0 <= value < 90] [Units: Degrees] +/// @param[in] verts The vertices. [(x, y, z) * @p nv] +/// @param[in] nv The number of vertices. +/// @param[in] tris The triangle vertex indices. [(vertA, vertB, vertC) * @p nt] +/// @param[in] nt The number of triangles. +/// @param[out] areas The triangle area ids. [Length: >= @p nt] +void rcClearUnwalkableTriangles(rcContext* ctx, const float walkableSlopeAngle, const float* verts, int nv, + const int* tris, int nt, unsigned char* areas); + +/// Adds a span to the specified heightfield. +/// @ingroup recast +/// @param[in,out] ctx The build context to use during the operation. +/// @param[in,out] hf An initialized heightfield. +/// @param[in] x The width index where the span is to be added. +/// [Limits: 0 <= value < rcHeightfield::width] +/// @param[in] y The height index where the span is to be added. +/// [Limits: 0 <= value < rcHeightfield::height] +/// @param[in] smin The minimum height of the span. [Limit: < @p smax] [Units: vx] +/// @param[in] smax The maximum height of the span. [Limit: <= #RC_SPAN_MAX_HEIGHT] [Units: vx] +/// @param[in] area The area id of the span. [Limit: <= #RC_WALKABLE_AREA) +/// @param[in] flagMergeThr The merge theshold. [Limit: >= 0] [Units: vx] +void rcAddSpan(rcContext* ctx, rcHeightfield& hf, const int x, const int y, + const unsigned short smin, const unsigned short smax, + const unsigned char area, const int flagMergeThr); + +/// Rasterizes a triangle into the specified heightfield. +/// @ingroup recast +/// @param[in,out] ctx The build context to use during the operation. +/// @param[in] v0 Triangle vertex 0 [(x, y, z)] +/// @param[in] v1 Triangle vertex 1 [(x, y, z)] +/// @param[in] v2 Triangle vertex 2 [(x, y, z)] +/// @param[in] area The area id of the triangle. [Limit: <= #RC_WALKABLE_AREA] +/// @param[in,out] solid An initialized heightfield. +/// @param[in] flagMergeThr The distance where the walkable flag is favored over the non-walkable flag. +/// [Limit: >= 0] [Units: vx] +void rcRasterizeTriangle(rcContext* ctx, const float* v0, const float* v1, const float* v2, + const unsigned char area, rcHeightfield& solid, + const int flagMergeThr = 1); + +/// Rasterizes an indexed triangle mesh into the specified heightfield. +/// @ingroup recast +/// @param[in,out] ctx The build context to use during the operation. +/// @param[in] verts The vertices. [(x, y, z) * @p nv] +/// @param[in] nv The number of vertices. +/// @param[in] tris The triangle indices. [(vertA, vertB, vertC) * @p nt] +/// @param[in] areas The area id's of the triangles. [Limit: <= #RC_WALKABLE_AREA] [Size: @p nt] +/// @param[in] nt The number of triangles. +/// @param[in,out] solid An initialized heightfield. +/// @param[in] flagMergeThr The distance where the walkable flag is favored over the non-walkable flag. +/// [Limit: >= 0] [Units: vx] +void rcRasterizeTriangles(rcContext* ctx, const float* verts, const int nv, + const int* tris, const unsigned char* areas, const int nt, + rcHeightfield& solid, const int flagMergeThr = 1); + +/// Rasterizes an indexed triangle mesh into the specified heightfield. +/// @ingroup recast +/// @param[in,out] ctx The build context to use during the operation. +/// @param[in] verts The vertices. [(x, y, z) * @p nv] +/// @param[in] nv The number of vertices. +/// @param[in] tris The triangle indices. [(vertA, vertB, vertC) * @p nt] +/// @param[in] areas The area id's of the triangles. [Limit: <= #RC_WALKABLE_AREA] [Size: @p nt] +/// @param[in] nt The number of triangles. +/// @param[in,out] solid An initialized heightfield. +/// @param[in] flagMergeThr The distance where the walkable flag is favored over the non-walkable flag. +/// [Limit: >= 0] [Units: vx] +void rcRasterizeTriangles(rcContext* ctx, const float* verts, const int nv, + const unsigned short* tris, const unsigned char* areas, const int nt, + rcHeightfield& solid, const int flagMergeThr = 1); + +/// Rasterizes triangles into the specified heightfield. +/// @ingroup recast +/// @param[in,out] ctx The build context to use during the operation. +/// @param[in] verts The triangle vertices. [(ax, ay, az, bx, by, bz, cx, by, cx) * @p nt] +/// @param[in] areas The area id's of the triangles. [Limit: <= #RC_WALKABLE_AREA] [Size: @p nt] +/// @param[in] nt The number of triangles. +/// @param[in,out] solid An initialized heightfield. +/// @param[in] flagMergeThr The distance where the walkable flag is favored over the non-walkable flag. +/// [Limit: >= 0] [Units: vx] +void rcRasterizeTriangles(rcContext* ctx, const float* verts, const unsigned char* areas, const int nt, + rcHeightfield& solid, const int flagMergeThr = 1); + +/// Marks non-walkable spans as walkable if their maximum is within @p walkableClimp of a walkable neihbor. +/// @ingroup recast +/// @param[in,out] ctx The build context to use during the operation. +/// @param[in] walkableClimb Maximum ledge height that is considered to still be traversable. +/// [Limit: >=0] [Units: vx] +/// @param[in,out] solid A fully built heightfield. (All spans have been added.) +void rcFilterLowHangingWalkableObstacles(rcContext* ctx, const int walkableClimb, rcHeightfield& solid); + +/// Marks spans that are ledges as not-walkable. +/// @ingroup recast +/// @param[in,out] ctx The build context to use during the operation. +/// @param[in] walkableHeight Minimum floor to 'ceiling' height that will still allow the floor area to +/// be considered walkable. [Limit: >= 3] [Units: vx] +/// @param[in] walkableClimb Maximum ledge height that is considered to still be traversable. +/// [Limit: >=0] [Units: vx] +/// @param[in,out] solid A fully built heightfield. (All spans have been added.) +void rcFilterLedgeSpans(rcContext* ctx, const int walkableHeight, + const int walkableClimb, rcHeightfield& solid); + +/// Marks walkable spans as not walkable if the clearence above the span is less than the specified height. +/// @ingroup recast +/// @param[in,out] ctx The build context to use during the operation. +/// @param[in] walkableHeight Minimum floor to 'ceiling' height that will still allow the floor area to +/// be considered walkable. [Limit: >= 3] [Units: vx] +/// @param[in,out] solid A fully built heightfield. (All spans have been added.) +void rcFilterWalkableLowHeightSpans(rcContext* ctx, int walkableHeight, rcHeightfield& solid); + +/// Returns the number of spans contained in the specified heightfield. +/// @ingroup recast +/// @param[in,out] ctx The build context to use during the operation. +/// @param[in] hf An initialized heightfield. +/// @returns The number of spans in the heightfield. +int rcGetHeightFieldSpanCount(rcContext* ctx, rcHeightfield& hf); + +/// @} +/// @name Compact Heightfield Functions +/// @see rcCompactHeightfield +/// @{ + +/// Builds a compact heightfield representing open space, from a heightfield representing solid space. +/// @ingroup recast +/// @param[in,out] ctx The build context to use during the operation. +/// @param[in] walkableHeight Minimum floor to 'ceiling' height that will still allow the floor area +/// to be considered walkable. [Limit: >= 3] [Units: vx] +/// @param[in] walkableClimb Maximum ledge height that is considered to still be traversable. +/// [Limit: >=0] [Units: vx] +/// @param[in] hf The heightfield to be compacted. +/// @param[out] chf The resulting compact heightfield. (Must be pre-allocated.) +/// @returns True if the operation completed successfully. +bool rcBuildCompactHeightfield(rcContext* ctx, const int walkableHeight, const int walkableClimb, + rcHeightfield& hf, rcCompactHeightfield& chf); + +/// Erodes the walkable area within the heightfield by the specified radius. +/// @ingroup recast +/// @param[in,out] ctx The build context to use during the operation. +/// @param[in] radius The radius of erosion. [Limits: 0 < value < 255] [Units: vx] +/// @param[in,out] chf The populated compact heightfield to erode. +/// @returns True if the operation completed successfully. +bool rcErodeWalkableArea(rcContext* ctx, int radius, rcCompactHeightfield& chf); + +/// Applies a median filter to walkable area types (based on area id), removing noise. +/// @ingroup recast +/// @param[in,out] ctx The build context to use during the operation. +/// @param[in,out] chf A populated compact heightfield. +/// @returns True if the operation completed successfully. +bool rcMedianFilterWalkableArea(rcContext* ctx, rcCompactHeightfield& chf); + +/// Applies an area id to all spans within the specified bounding box. (AABB) +/// @ingroup recast +/// @param[in,out] ctx The build context to use during the operation. +/// @param[in] bmin The minimum of the bounding box. [(x, y, z)] +/// @param[in] bmax The maximum of the bounding box. [(x, y, z)] +/// @param[in] areaId The area id to apply. [Limit: <= #RC_WALKABLE_AREA] +/// @param[in,out] chf A populated compact heightfield. +void rcMarkBoxArea(rcContext* ctx, const float* bmin, const float* bmax, unsigned char areaId, + rcCompactHeightfield& chf); + +/// Applies the area id to the all spans within the specified convex polygon. +/// @ingroup recast +/// @param[in,out] ctx The build context to use during the operation. +/// @param[in] verts The vertices of the polygon [Fomr: (x, y, z) * @p nverts] +/// @param[in] nverts The number of vertices in the polygon. +/// @param[in] hmin The height of the base of the polygon. +/// @param[in] hmax The height of the top of the polygon. +/// @param[in] areaId The area id to apply. [Limit: <= #RC_WALKABLE_AREA] +/// @param[in,out] chf A populated compact heightfield. +void rcMarkConvexPolyArea(rcContext* ctx, const float* verts, const int nverts, + const float hmin, const float hmax, unsigned char areaId, + rcCompactHeightfield& chf); + +/// Helper function to offset voncex polygons for rcMarkConvexPolyArea. +/// @ingroup recast +/// @param[in] verts The vertices of the polygon [Form: (x, y, z) * @p nverts] +/// @param[in] nverts The number of vertices in the polygon. +/// @param[out] outVerts The offset vertices (should hold up to 2 * @p nverts) [Form: (x, y, z) * return value] +/// @param[in] maxOutVerts The max number of vertices that can be stored to @p outVerts. +/// @returns Number of vertices in the offset polygon or 0 if too few vertices in @p outVerts. +int rcOffsetPoly(const float* verts, const int nverts, const float offset, + float* outVerts, const int maxOutVerts); + +/// Applies the area id to all spans within the specified cylinder. +/// @ingroup recast +/// @param[in,out] ctx The build context to use during the operation. +/// @param[in] pos The center of the base of the cylinder. [Form: (x, y, z)] +/// @param[in] r The radius of the cylinder. +/// @param[in] h The height of the cylinder. +/// @param[in] areaId The area id to apply. [Limit: <= #RC_WALKABLE_AREA] +/// @param[in,out] chf A populated compact heightfield. +void rcMarkCylinderArea(rcContext* ctx, const float* pos, + const float r, const float h, unsigned char areaId, + rcCompactHeightfield& chf); + +/// Builds the distance field for the specified compact heightfield. +/// @ingroup recast +/// @param[in,out] ctx The build context to use during the operation. +/// @param[in,out] chf A populated compact heightfield. +/// @returns True if the operation completed successfully. +bool rcBuildDistanceField(rcContext* ctx, rcCompactHeightfield& chf); + +/// Builds region data for the heightfield using watershed partitioning. +/// @ingroup recast +/// @param[in,out] ctx The build context to use during the operation. +/// @param[in,out] chf A populated compact heightfield. +/// @param[in] borderSize The size of the non-navigable border around the heightfield. +/// [Limit: >=0] [Units: vx] +/// @param[in] minRegionArea The minimum number of cells allowed to form isolated island areas. +/// [Limit: >=0] [Units: vx]. +/// @param[in] mergeRegionArea Any regions with a span count smaller than this value will, if possible, +/// be merged with larger regions. [Limit: >=0] [Units: vx] +/// @returns True if the operation completed successfully. +bool rcBuildRegions(rcContext* ctx, rcCompactHeightfield& chf, + const int borderSize, const int minRegionArea, const int mergeRegionArea); + +/// Builds region data for the heightfield using simple monotone partitioning. +/// @ingroup recast +/// @param[in,out] ctx The build context to use during the operation. +/// @param[in,out] chf A populated compact heightfield. +/// @param[in] borderSize The size of the non-navigable border around the heightfield. +/// [Limit: >=0] [Units: vx] +/// @param[in] minRegionArea The minimum number of cells allowed to form isolated island areas. +/// [Limit: >=0] [Units: vx]. +/// @param[in] mergeRegionArea Any regions with a span count smaller than this value will, if possible, +/// be merged with larger regions. [Limit: >=0] [Units: vx] +/// @returns True if the operation completed successfully. +bool rcBuildRegionsMonotone(rcContext* ctx, rcCompactHeightfield& chf, + const int borderSize, const int minRegionArea, const int mergeRegionArea); + + +/// Sets the neighbor connection data for the specified direction. +/// @param[in] s The span to update. +/// @param[in] dir The direction to set. [Limits: 0 <= value < 4] +/// @param[in] i The index of the neighbor span. +inline void rcSetCon(rcCompactSpan& s, int dir, int i) +{ + const unsigned int shift = (unsigned int)dir*6; + unsigned int con = s.con; + s.con = (con & ~(0x3f << shift)) | (((unsigned int)i & 0x3f) << shift); +} + +/// Gets neighbor connection data for the specified direction. +/// @param[in] s The span to check. +/// @param[in] dir The direction to check. [Limits: 0 <= value < 4] +/// @return The neighbor connection data for the specified direction, +/// or #RC_NOT_CONNECTED if there is no connection. +inline int rcGetCon(const rcCompactSpan& s, int dir) +{ + const unsigned int shift = (unsigned int)dir*6; + return (s.con >> shift) & 0x3f; +} + +/// Gets the standard width (x-axis) offset for the specified direction. +/// @param[in] dir The direction. [Limits: 0 <= value < 4] +/// @return The width offset to apply to the current cell position to move +/// in the direction. +inline int rcGetDirOffsetX(int dir) +{ + const int offset[4] = { -1, 0, 1, 0, }; + return offset[dir&0x03]; +} + +/// Gets the standard height (z-axis) offset for the specified direction. +/// @param[in] dir The direction. [Limits: 0 <= value < 4] +/// @return The height offset to apply to the current cell position to move +/// in the direction. +inline int rcGetDirOffsetY(int dir) +{ + const int offset[4] = { 0, 1, 0, -1 }; + return offset[dir&0x03]; +} + +/// @} +/// @name Layer, Contour, Polymesh, and Detail Mesh Functions +/// @see rcHeightfieldLayer, rcContourSet, rcPolyMesh, rcPolyMeshDetail +/// @{ + +/// Builds a layer set from the specified compact heightfield. +/// @ingroup recast +/// @param[in,out] ctx The build context to use during the operation. +/// @param[in] chf A fully built compact heightfield. +/// @param[in] borderSize The size of the non-navigable border around the heightfield. [Limit: >=0] +/// [Units: vx] +/// @param[in] walkableHeight Minimum floor to 'ceiling' height that will still allow the floor area +/// to be considered walkable. [Limit: >= 3] [Units: vx] +/// @param[out] lset The resulting layer set. (Must be pre-allocated.) +/// @returns True if the operation completed successfully. +bool rcBuildHeightfieldLayers(rcContext* ctx, rcCompactHeightfield& chf, + const int borderSize, const int walkableHeight, + rcHeightfieldLayerSet& lset); + +/// Builds a contour set from the region outlines in the provided compact heightfield. +/// @ingroup recast +/// @param[in,out] ctx The build context to use during the operation. +/// @param[in] chf A fully built compact heightfield. +/// @param[in] maxError The maximum distance a simplfied contour's border edges should deviate +/// the original raw contour. [Limit: >=0] [Units: wu] +/// @param[in] maxEdgeLen The maximum allowed length for contour edges along the border of the mesh. +/// [Limit: >=0] [Units: vx] +/// @param[out] cset The resulting contour set. (Must be pre-allocated.) +/// @param[in] buildFlags The build flags. (See: #rcBuildContoursFlags) +/// @returns True if the operation completed successfully. +bool rcBuildContours(rcContext* ctx, rcCompactHeightfield& chf, + const float maxError, const int maxEdgeLen, + rcContourSet& cset, const int flags = RC_CONTOUR_TESS_WALL_EDGES); + +/// Builds a polygon mesh from the provided contours. +/// @ingroup recast +/// @param[in,out] ctx The build context to use during the operation. +/// @param[in] cset A fully built contour set. +/// @param[in] nvp The maximum number of vertices allowed for polygons generated during the +/// contour to polygon conversion process. [Limit: >= 3] +/// @param[out] mesh The resulting polygon mesh. (Must be re-allocated.) +/// @returns True if the operation completed successfully. +bool rcBuildPolyMesh(rcContext* ctx, rcContourSet& cset, const int nvp, rcPolyMesh& mesh); + +/// Merges multiple polygon meshes into a single mesh. +/// @ingroup recast +/// @param[in,out] ctx The build context to use during the operation. +/// @param[in] meshes An array of polygon meshes to merge. [Size: @p nmeshes] +/// @param[in] nmeshes The number of polygon meshes in the meshes array. +/// @param[in] mesh The resulting polygon mesh. (Must be pre-allocated.) +/// @returns True if the operation completed successfully. +bool rcMergePolyMeshes(rcContext* ctx, rcPolyMesh** meshes, const int nmeshes, rcPolyMesh& mesh); + +/// Builds a detail mesh from the provided polygon mesh. +/// @ingroup recast +/// @param[in,out] ctx The build context to use during the operation. +/// @param[in] mesh A fully built polygon mesh. +/// @param[in] chf The compact heightfield used to build the polygon mesh. +/// @param[in] sampleDist Sets the distance to use when samping the heightfield. [Limit: >=0] [Units: wu] +/// @param[in] sampleMaxError The maximum distance the detail mesh surface should deviate from +/// heightfield data. [Limit: >=0] [Units: wu] +/// @param[out] dmesh The resulting detail mesh. (Must be pre-allocated.) +/// @returns True if the operation completed successfully. +bool rcBuildPolyMeshDetail(rcContext* ctx, const rcPolyMesh& mesh, const rcCompactHeightfield& chf, + const float sampleDist, const float sampleMaxError, + rcPolyMeshDetail& dmesh); + +/// Copies the poly mesh data from src to dst. +/// @ingroup recast +/// @param[in,out] ctx The build context to use during the operation. +/// @param[in] src The source mesh to copy from. +/// @param[out] dst The resulting detail mesh. (Must be pre-allocated, must be empty mesh.) +/// @returns True if the operation completed successfully. +bool rcCopyPolyMesh(rcContext* ctx, const rcPolyMesh& src, rcPolyMesh& dst); + +/// Merges multiple detail meshes into a single detail mesh. +/// @ingroup recast +/// @param[in,out] ctx The build context to use during the operation. +/// @param[in] meshes An array of detail meshes to merge. [Size: @p nmeshes] +/// @param[in] nmeshes The number of detail meshes in the meshes array. +/// @param[out] mesh The resulting detail mesh. (Must be pre-allocated.) +/// @returns True if the operation completed successfully. +bool rcMergePolyMeshDetails(rcContext* ctx, rcPolyMeshDetail** meshes, const int nmeshes, rcPolyMeshDetail& mesh); + +/// @} + +#endif // RECAST_H + +/////////////////////////////////////////////////////////////////////////// + +// Due to the large amount of detail documentation for this file, +// the content normally located at the end of the header file has been separated +// out to a file in /Docs/Extern. diff --git a/KREngine/3rdparty/recast/include/RecastAlloc.h b/KREngine/3rdparty/recast/include/RecastAlloc.h new file mode 100755 index 0000000..438be9e --- /dev/null +++ b/KREngine/3rdparty/recast/include/RecastAlloc.h @@ -0,0 +1,124 @@ +// +// Copyright (c) 2009-2010 Mikko Mononen memon@inside.org +// +// This software is provided 'as-is', without any express or implied +// warranty. In no event will the authors be held liable for any damages +// arising from the use of this software. +// Permission is granted to anyone to use this software for any purpose, +// including commercial applications, and to alter it and redistribute it +// freely, subject to the following restrictions: +// 1. The origin of this software must not be misrepresented; you must not +// claim that you wrote the original software. If you use this software +// in a product, an acknowledgment in the product documentation would be +// appreciated but is not required. +// 2. Altered source versions must be plainly marked as such, and must not be +// misrepresented as being the original software. +// 3. This notice may not be removed or altered from any source distribution. +// + +#ifndef RECASTALLOC_H +#define RECASTALLOC_H + +/// Provides hint values to the memory allocator on how long the +/// memory is expected to be used. +enum rcAllocHint +{ + RC_ALLOC_PERM, ///< Memory will persist after a function call. + RC_ALLOC_TEMP ///< Memory used temporarily within a function. +}; + +/// A memory allocation function. +// @param[in] size The size, in bytes of memory, to allocate. +// @param[in] rcAllocHint A hint to the allocator on how long the memory is expected to be in use. +// @return A pointer to the beginning of the allocated memory block, or null if the allocation failed. +/// @see rcAllocSetCustom +typedef void* (rcAllocFunc)(int size, rcAllocHint hint); + +/// A memory deallocation function. +/// @param[in] ptr A pointer to a memory block previously allocated using #rcAllocFunc. +/// @see rcAllocSetCustom +typedef void (rcFreeFunc)(void* ptr); + +/// Sets the base custom allocation functions to be used by Recast. +/// @param[in] allocFunc The memory allocation function to be used by #rcAlloc +/// @param[in] freeFunc The memory de-allocation function to be used by #rcFree +void rcAllocSetCustom(rcAllocFunc *allocFunc, rcFreeFunc *freeFunc); + +/// Allocates a memory block. +/// @param[in] size The size, in bytes of memory, to allocate. +/// @param[in] hint A hint to the allocator on how long the memory is expected to be in use. +/// @return A pointer to the beginning of the allocated memory block, or null if the allocation failed. +/// @see rcFree +void* rcAlloc(int size, rcAllocHint hint); + +/// Deallocates a memory block. +/// @param[in] ptr A pointer to a memory block previously allocated using #rcAlloc. +/// @see rcAlloc +void rcFree(void* ptr); + + +/// A simple dynamic array of integers. +class rcIntArray +{ + int* m_data; + int m_size, m_cap; + inline rcIntArray(const rcIntArray&); + inline rcIntArray& operator=(const rcIntArray&); +public: + + /// Constructs an instance with an initial array size of zero. + inline rcIntArray() : m_data(0), m_size(0), m_cap(0) {} + + /// Constructs an instance initialized to the specified size. + /// @param[in] n The initial size of the integer array. + inline rcIntArray(int n) : m_data(0), m_size(0), m_cap(0) { resize(n); } + inline ~rcIntArray() { rcFree(m_data); } + + /// Specifies the new size of the integer array. + /// @param[in] n The new size of the integer array. + void resize(int n); + + /// Push the specified integer onto the end of the array and increases the size by one. + /// @param[in] item The new value. + inline void push(int item) { resize(m_size+1); m_data[m_size-1] = item; } + + /// Returns the value at the end of the array and reduces the size by one. + /// @return The value at the end of the array. + inline int pop() { if (m_size > 0) m_size--; return m_data[m_size]; } + + /// The value at the specified array index. + /// @warning Does not provide overflow protection. + /// @param[in] i The index of the value. + inline const int& operator[](int i) const { return m_data[i]; } + + /// The value at the specified array index. + /// @warning Does not provide overflow protection. + /// @param[in] i The index of the value. + inline int& operator[](int i) { return m_data[i]; } + + /// The current size of the integer array. + inline int size() const { return m_size; } +}; + +/// A simple helper class used to delete an array when it goes out of scope. +/// @note This class is rarely if ever used by the end user. +template class rcScopedDelete +{ + T* ptr; + inline T* operator=(T* p); +public: + + /// Constructs an instance with a null pointer. + inline rcScopedDelete() : ptr(0) {} + + /// Constructs an instance with the specified pointer. + /// @param[in] p An pointer to an allocated array. + inline rcScopedDelete(T* p) : ptr(p) {} + inline ~rcScopedDelete() { rcFree(ptr); } + + /// The root array pointer. + /// @return The root array pointer. + inline operator T*() { return ptr; } +}; + +#endif diff --git a/KREngine/3rdparty/recast/include/RecastAssert.h b/KREngine/3rdparty/recast/include/RecastAssert.h new file mode 100755 index 0000000..2aca0d9 --- /dev/null +++ b/KREngine/3rdparty/recast/include/RecastAssert.h @@ -0,0 +1,33 @@ +// +// Copyright (c) 2009-2010 Mikko Mononen memon@inside.org +// +// This software is provided 'as-is', without any express or implied +// warranty. In no event will the authors be held liable for any damages +// arising from the use of this software. +// Permission is granted to anyone to use this software for any purpose, +// including commercial applications, and to alter it and redistribute it +// freely, subject to the following restrictions: +// 1. The origin of this software must not be misrepresented; you must not +// claim that you wrote the original software. If you use this software +// in a product, an acknowledgment in the product documentation would be +// appreciated but is not required. +// 2. Altered source versions must be plainly marked as such, and must not be +// misrepresented as being the original software. +// 3. This notice may not be removed or altered from any source distribution. +// + +#ifndef RECASTASSERT_H +#define RECASTASSERT_H + +// Note: This header file's only purpose is to include define assert. +// Feel free to change the file and include your own implementation instead. + +#ifdef NDEBUG +// From http://cnicholson.net/2009/02/stupid-c-tricks-adventures-in-assert/ +# define rcAssert(x) do { (void)sizeof(x); } while((void)(__LINE__==-1),false) +#else +# include +# define rcAssert assert +#endif + +#endif // RECASTASSERT_H diff --git a/KREngine/3rdparty/recast/source/Recast.cpp b/KREngine/3rdparty/recast/source/Recast.cpp new file mode 100755 index 0000000..b9d8603 --- /dev/null +++ b/KREngine/3rdparty/recast/source/Recast.cpp @@ -0,0 +1,489 @@ +// +// Copyright (c) 2009-2010 Mikko Mononen memon@inside.org +// +// This software is provided 'as-is', without any express or implied +// warranty. In no event will the authors be held liable for any damages +// arising from the use of this software. +// Permission is granted to anyone to use this software for any purpose, +// including commercial applications, and to alter it and redistribute it +// freely, subject to the following restrictions: +// 1. The origin of this software must not be misrepresented; you must not +// claim that you wrote the original software. If you use this software +// in a product, an acknowledgment in the product documentation would be +// appreciated but is not required. +// 2. Altered source versions must be plainly marked as such, and must not be +// misrepresented as being the original software. +// 3. This notice may not be removed or altered from any source distribution. +// + +#include +#define _USE_MATH_DEFINES +#include +#include +#include +#include +#include +#include "Recast.h" +#include "RecastAlloc.h" +#include "RecastAssert.h" + +float rcSqrt(float x) +{ + return sqrtf(x); +} + +/// @class rcContext +/// @par +/// +/// This class does not provide logging or timer functionality on its +/// own. Both must be provided by a concrete implementation +/// by overriding the protected member functions. Also, this class does not +/// provide an interface for extracting log messages. (Only adding them.) +/// So concrete implementations must provide one. +/// +/// If no logging or timers are required, just pass an instance of this +/// class through the Recast build process. +/// + +/// @par +/// +/// Example: +/// @code +/// // Where ctx is an instance of rcContext and filepath is a char array. +/// ctx->log(RC_LOG_ERROR, "buildTiledNavigation: Could not load '%s'", filepath); +/// @endcode +void rcContext::log(const rcLogCategory category, const char* format, ...) +{ + if (!m_logEnabled) + return; + static const int MSG_SIZE = 512; + char msg[MSG_SIZE]; + va_list ap; + va_start(ap, format); + int len = vsnprintf(msg, MSG_SIZE, format, ap); + if (len >= MSG_SIZE) + { + len = MSG_SIZE-1; + msg[MSG_SIZE-1] = '\0'; + } + va_end(ap); + doLog(category, msg, len); +} + +rcHeightfield* rcAllocHeightfield() +{ + rcHeightfield* hf = (rcHeightfield*)rcAlloc(sizeof(rcHeightfield), RC_ALLOC_PERM); + memset(hf, 0, sizeof(rcHeightfield)); + return hf; +} + +void rcFreeHeightField(rcHeightfield* hf) +{ + if (!hf) return; + // Delete span array. + rcFree(hf->spans); + // Delete span pools. + while (hf->pools) + { + rcSpanPool* next = hf->pools->next; + rcFree(hf->pools); + hf->pools = next; + } + rcFree(hf); +} + +rcCompactHeightfield* rcAllocCompactHeightfield() +{ + rcCompactHeightfield* chf = (rcCompactHeightfield*)rcAlloc(sizeof(rcCompactHeightfield), RC_ALLOC_PERM); + memset(chf, 0, sizeof(rcCompactHeightfield)); + return chf; +} + +void rcFreeCompactHeightfield(rcCompactHeightfield* chf) +{ + if (!chf) return; + rcFree(chf->cells); + rcFree(chf->spans); + rcFree(chf->dist); + rcFree(chf->areas); + rcFree(chf); +} + + +rcHeightfieldLayerSet* rcAllocHeightfieldLayerSet() +{ + rcHeightfieldLayerSet* lset = (rcHeightfieldLayerSet*)rcAlloc(sizeof(rcHeightfieldLayerSet), RC_ALLOC_PERM); + memset(lset, 0, sizeof(rcHeightfieldLayerSet)); + return lset; +} + +void rcFreeHeightfieldLayerSet(rcHeightfieldLayerSet* lset) +{ + if (!lset) return; + for (int i = 0; i < lset->nlayers; ++i) + { + rcFree(lset->layers[i].heights); + rcFree(lset->layers[i].areas); + rcFree(lset->layers[i].cons); + } + rcFree(lset->layers); + rcFree(lset); +} + + +rcContourSet* rcAllocContourSet() +{ + rcContourSet* cset = (rcContourSet*)rcAlloc(sizeof(rcContourSet), RC_ALLOC_PERM); + memset(cset, 0, sizeof(rcContourSet)); + return cset; +} + +void rcFreeContourSet(rcContourSet* cset) +{ + if (!cset) return; + for (int i = 0; i < cset->nconts; ++i) + { + rcFree(cset->conts[i].verts); + rcFree(cset->conts[i].rverts); + } + rcFree(cset->conts); + rcFree(cset); +} + +rcPolyMesh* rcAllocPolyMesh() +{ + rcPolyMesh* pmesh = (rcPolyMesh*)rcAlloc(sizeof(rcPolyMesh), RC_ALLOC_PERM); + memset(pmesh, 0, sizeof(rcPolyMesh)); + return pmesh; +} + +void rcFreePolyMesh(rcPolyMesh* pmesh) +{ + if (!pmesh) return; + rcFree(pmesh->verts); + rcFree(pmesh->polys); + rcFree(pmesh->regs); + rcFree(pmesh->flags); + rcFree(pmesh->areas); + rcFree(pmesh); +} + +rcPolyMeshDetail* rcAllocPolyMeshDetail() +{ + rcPolyMeshDetail* dmesh = (rcPolyMeshDetail*)rcAlloc(sizeof(rcPolyMeshDetail), RC_ALLOC_PERM); + memset(dmesh, 0, sizeof(rcPolyMeshDetail)); + return dmesh; +} + +void rcFreePolyMeshDetail(rcPolyMeshDetail* dmesh) +{ + if (!dmesh) return; + rcFree(dmesh->meshes); + rcFree(dmesh->verts); + rcFree(dmesh->tris); + rcFree(dmesh); +} + +void rcCalcBounds(const float* verts, int nv, float* bmin, float* bmax) +{ + // Calculate bounding box. + rcVcopy(bmin, verts); + rcVcopy(bmax, verts); + for (int i = 1; i < nv; ++i) + { + const float* v = &verts[i*3]; + rcVmin(bmin, v); + rcVmax(bmax, v); + } +} + +void rcCalcGridSize(const float* bmin, const float* bmax, float cs, int* w, int* h) +{ + *w = (int)((bmax[0] - bmin[0])/cs+0.5f); + *h = (int)((bmax[2] - bmin[2])/cs+0.5f); +} + +/// @par +/// +/// See the #rcConfig documentation for more information on the configuration parameters. +/// +/// @see rcAllocHeightfield, rcHeightfield +bool rcCreateHeightfield(rcContext* ctx, rcHeightfield& hf, int width, int height, + const float* bmin, const float* bmax, + float cs, float ch) +{ + rcIgnoreUnused(ctx); + + hf.width = width; + hf.height = height; + rcVcopy(hf.bmin, bmin); + rcVcopy(hf.bmax, bmax); + hf.cs = cs; + hf.ch = ch; + hf.spans = (rcSpan**)rcAlloc(sizeof(rcSpan*)*hf.width*hf.height, RC_ALLOC_PERM); + if (!hf.spans) + return false; + memset(hf.spans, 0, sizeof(rcSpan*)*hf.width*hf.height); + return true; +} + +static void calcTriNormal(const float* v0, const float* v1, const float* v2, float* norm) +{ + float e0[3], e1[3]; + rcVsub(e0, v1, v0); + rcVsub(e1, v2, v0); + rcVcross(norm, e0, e1); + rcVnormalize(norm); +} + +/// @par +/// +/// Only sets the aread id's for the walkable triangles. Does not alter the +/// area id's for unwalkable triangles. +/// +/// See the #rcConfig documentation for more information on the configuration parameters. +/// +/// @see rcHeightfield, rcClearUnwalkableTriangles, rcRasterizeTriangles +void rcMarkWalkableTriangles(rcContext* ctx, const float walkableSlopeAngle, + const float* verts, int /*nv*/, + const int* tris, int nt, + unsigned char* areas) +{ + rcIgnoreUnused(ctx); + + const float walkableThr = cosf(walkableSlopeAngle/180.0f*RC_PI); + + float norm[3]; + + for (int i = 0; i < nt; ++i) + { + const int* tri = &tris[i*3]; + calcTriNormal(&verts[tri[0]*3], &verts[tri[1]*3], &verts[tri[2]*3], norm); + // Check if the face is walkable. + if (norm[1] > walkableThr) + areas[i] = RC_WALKABLE_AREA; + } +} + +/// @par +/// +/// Only sets the aread id's for the unwalkable triangles. Does not alter the +/// area id's for walkable triangles. +/// +/// See the #rcConfig documentation for more information on the configuration parameters. +/// +/// @see rcHeightfield, rcClearUnwalkableTriangles, rcRasterizeTriangles +void rcClearUnwalkableTriangles(rcContext* ctx, const float walkableSlopeAngle, + const float* verts, int /*nv*/, + const int* tris, int nt, + unsigned char* areas) +{ + rcIgnoreUnused(ctx); + + const float walkableThr = cosf(walkableSlopeAngle/180.0f*RC_PI); + + float norm[3]; + + for (int i = 0; i < nt; ++i) + { + const int* tri = &tris[i*3]; + calcTriNormal(&verts[tri[0]*3], &verts[tri[1]*3], &verts[tri[2]*3], norm); + // Check if the face is walkable. + if (norm[1] <= walkableThr) + areas[i] = RC_NULL_AREA; + } +} + +int rcGetHeightFieldSpanCount(rcContext* ctx, rcHeightfield& hf) +{ + rcIgnoreUnused(ctx); + + const int w = hf.width; + const int h = hf.height; + int spanCount = 0; + for (int y = 0; y < h; ++y) + { + for (int x = 0; x < w; ++x) + { + for (rcSpan* s = hf.spans[x + y*w]; s; s = s->next) + { + if (s->area != RC_NULL_AREA) + spanCount++; + } + } + } + return spanCount; +} + +/// @par +/// +/// This is just the beginning of the process of fully building a compact heightfield. +/// Various filters may be applied applied, then the distance field and regions built. +/// E.g: #rcBuildDistanceField and #rcBuildRegions +/// +/// See the #rcConfig documentation for more information on the configuration parameters. +/// +/// @see rcAllocCompactHeightfield, rcHeightfield, rcCompactHeightfield, rcConfig +bool rcBuildCompactHeightfield(rcContext* ctx, const int walkableHeight, const int walkableClimb, + rcHeightfield& hf, rcCompactHeightfield& chf) +{ + rcAssert(ctx); + + ctx->startTimer(RC_TIMER_BUILD_COMPACTHEIGHTFIELD); + + const int w = hf.width; + const int h = hf.height; + const int spanCount = rcGetHeightFieldSpanCount(ctx, hf); + + // Fill in header. + chf.width = w; + chf.height = h; + chf.spanCount = spanCount; + chf.walkableHeight = walkableHeight; + chf.walkableClimb = walkableClimb; + chf.maxRegions = 0; + rcVcopy(chf.bmin, hf.bmin); + rcVcopy(chf.bmax, hf.bmax); + chf.bmax[1] += walkableHeight*hf.ch; + chf.cs = hf.cs; + chf.ch = hf.ch; + chf.cells = (rcCompactCell*)rcAlloc(sizeof(rcCompactCell)*w*h, RC_ALLOC_PERM); + if (!chf.cells) + { + ctx->log(RC_LOG_ERROR, "rcBuildCompactHeightfield: Out of memory 'chf.cells' (%d)", w*h); + return false; + } + memset(chf.cells, 0, sizeof(rcCompactCell)*w*h); + chf.spans = (rcCompactSpan*)rcAlloc(sizeof(rcCompactSpan)*spanCount, RC_ALLOC_PERM); + if (!chf.spans) + { + ctx->log(RC_LOG_ERROR, "rcBuildCompactHeightfield: Out of memory 'chf.spans' (%d)", spanCount); + return false; + } + memset(chf.spans, 0, sizeof(rcCompactSpan)*spanCount); + chf.areas = (unsigned char*)rcAlloc(sizeof(unsigned char)*spanCount, RC_ALLOC_PERM); + if (!chf.areas) + { + ctx->log(RC_LOG_ERROR, "rcBuildCompactHeightfield: Out of memory 'chf.areas' (%d)", spanCount); + return false; + } + memset(chf.areas, RC_NULL_AREA, sizeof(unsigned char)*spanCount); + + const int MAX_HEIGHT = 0xffff; + + // Fill in cells and spans. + int idx = 0; + for (int y = 0; y < h; ++y) + { + for (int x = 0; x < w; ++x) + { + const rcSpan* s = hf.spans[x + y*w]; + // If there are no spans at this cell, just leave the data to index=0, count=0. + if (!s) continue; + rcCompactCell& c = chf.cells[x+y*w]; + c.index = idx; + c.count = 0; + while (s) + { + if (s->area != RC_NULL_AREA) + { + const int bot = (int)s->smax; + const int top = s->next ? (int)s->next->smin : MAX_HEIGHT; + chf.spans[idx].y = (unsigned short)rcClamp(bot, 0, 0xffff); + chf.spans[idx].h = (unsigned char)rcClamp(top - bot, 0, 0xff); + chf.areas[idx] = s->area; + idx++; + c.count++; + } + s = s->next; + } + } + } + + // Find neighbour connections. + const int MAX_LAYERS = RC_NOT_CONNECTED-1; + int tooHighNeighbour = 0; + for (int y = 0; y < h; ++y) + { + for (int x = 0; x < w; ++x) + { + const rcCompactCell& c = chf.cells[x+y*w]; + for (int i = (int)c.index, ni = (int)(c.index+c.count); i < ni; ++i) + { + rcCompactSpan& s = chf.spans[i]; + + for (int dir = 0; dir < 4; ++dir) + { + rcSetCon(s, dir, RC_NOT_CONNECTED); + const int nx = x + rcGetDirOffsetX(dir); + const int ny = y + rcGetDirOffsetY(dir); + // First check that the neighbour cell is in bounds. + if (nx < 0 || ny < 0 || nx >= w || ny >= h) + continue; + + // Iterate over all neighbour spans and check if any of the is + // accessible from current cell. + const rcCompactCell& nc = chf.cells[nx+ny*w]; + for (int k = (int)nc.index, nk = (int)(nc.index+nc.count); k < nk; ++k) + { + const rcCompactSpan& ns = chf.spans[k]; + const int bot = rcMax(s.y, ns.y); + const int top = rcMin(s.y+s.h, ns.y+ns.h); + + // Check that the gap between the spans is walkable, + // and that the climb height between the gaps is not too high. + if ((top - bot) >= walkableHeight && rcAbs((int)ns.y - (int)s.y) <= walkableClimb) + { + // Mark direction as walkable. + const int lidx = k - (int)nc.index; + if (lidx < 0 || lidx > MAX_LAYERS) + { + tooHighNeighbour = rcMax(tooHighNeighbour, lidx); + continue; + } + rcSetCon(s, dir, lidx); + break; + } + } + + } + } + } + } + + if (tooHighNeighbour > MAX_LAYERS) + { + ctx->log(RC_LOG_ERROR, "rcBuildCompactHeightfield: Heightfield has too many layers %d (max: %d)", + tooHighNeighbour, MAX_LAYERS); + } + + ctx->stopTimer(RC_TIMER_BUILD_COMPACTHEIGHTFIELD); + + return true; +} + +/* +static int getHeightfieldMemoryUsage(const rcHeightfield& hf) +{ + int size = 0; + size += sizeof(hf); + size += hf.width * hf.height * sizeof(rcSpan*); + + rcSpanPool* pool = hf.pools; + while (pool) + { + size += (sizeof(rcSpanPool) - sizeof(rcSpan)) + sizeof(rcSpan)*RC_SPANS_PER_POOL; + pool = pool->next; + } + return size; +} + +static int getCompactHeightFieldMemoryusage(const rcCompactHeightfield& chf) +{ + int size = 0; + size += sizeof(rcCompactHeightfield); + size += sizeof(rcCompactSpan) * chf.spanCount; + size += sizeof(rcCompactCell) * chf.width * chf.height; + return size; +} +*/ \ No newline at end of file diff --git a/KREngine/3rdparty/recast/source/RecastAlloc.cpp b/KREngine/3rdparty/recast/source/RecastAlloc.cpp new file mode 100755 index 0000000..b5ec151 --- /dev/null +++ b/KREngine/3rdparty/recast/source/RecastAlloc.cpp @@ -0,0 +1,88 @@ +// +// Copyright (c) 2009-2010 Mikko Mononen memon@inside.org +// +// This software is provided 'as-is', without any express or implied +// warranty. In no event will the authors be held liable for any damages +// arising from the use of this software. +// Permission is granted to anyone to use this software for any purpose, +// including commercial applications, and to alter it and redistribute it +// freely, subject to the following restrictions: +// 1. The origin of this software must not be misrepresented; you must not +// claim that you wrote the original software. If you use this software +// in a product, an acknowledgment in the product documentation would be +// appreciated but is not required. +// 2. Altered source versions must be plainly marked as such, and must not be +// misrepresented as being the original software. +// 3. This notice may not be removed or altered from any source distribution. +// + +#include +#include +#include "RecastAlloc.h" + +static void *rcAllocDefault(int size, rcAllocHint) +{ + return malloc(size); +} + +static void rcFreeDefault(void *ptr) +{ + free(ptr); +} + +static rcAllocFunc* sRecastAllocFunc = rcAllocDefault; +static rcFreeFunc* sRecastFreeFunc = rcFreeDefault; + +/// @see rcAlloc, rcFree +void rcAllocSetCustom(rcAllocFunc *allocFunc, rcFreeFunc *freeFunc) +{ + sRecastAllocFunc = allocFunc ? allocFunc : rcAllocDefault; + sRecastFreeFunc = freeFunc ? freeFunc : rcFreeDefault; +} + +/// @see rcAllocSetCustom +void* rcAlloc(int size, rcAllocHint hint) +{ + return sRecastAllocFunc(size, hint); +} + +/// @par +/// +/// @warning This function leaves the value of @p ptr unchanged. So it still +/// points to the same (now invalid) location, and not to null. +/// +/// @see rcAllocSetCustom +void rcFree(void* ptr) +{ + if (ptr) + sRecastFreeFunc(ptr); +} + +/// @class rcIntArray +/// +/// While it is possible to pre-allocate a specific array size during +/// construction or by using the #resize method, certain methods will +/// automatically resize the array as needed. +/// +/// @warning The array memory is not initialized to zero when the size is +/// manually set during construction or when using #resize. + +/// @par +/// +/// Using this method ensures the array is at least large enough to hold +/// the specified number of elements. This can improve performance by +/// avoiding auto-resizing during use. +void rcIntArray::resize(int n) +{ + if (n > m_cap) + { + if (!m_cap) m_cap = n; + while (m_cap < n) m_cap *= 2; + int* newData = (int*)rcAlloc(m_cap*sizeof(int), RC_ALLOC_TEMP); + if (m_size && newData) memcpy(newData, m_data, m_size*sizeof(int)); + rcFree(m_data); + m_data = newData; + } + m_size = n; +} + diff --git a/KREngine/3rdparty/recast/source/RecastArea.cpp b/KREngine/3rdparty/recast/source/RecastArea.cpp new file mode 100755 index 0000000..1a338cd --- /dev/null +++ b/KREngine/3rdparty/recast/source/RecastArea.cpp @@ -0,0 +1,602 @@ +// +// Copyright (c) 2009-2010 Mikko Mononen memon@inside.org +// +// This software is provided 'as-is', without any express or implied +// warranty. In no event will the authors be held liable for any damages +// arising from the use of this software. +// Permission is granted to anyone to use this software for any purpose, +// including commercial applications, and to alter it and redistribute it +// freely, subject to the following restrictions: +// 1. The origin of this software must not be misrepresented; you must not +// claim that you wrote the original software. If you use this software +// in a product, an acknowledgment in the product documentation would be +// appreciated but is not required. +// 2. Altered source versions must be plainly marked as such, and must not be +// misrepresented as being the original software. +// 3. This notice may not be removed or altered from any source distribution. +// + +#include +#define _USE_MATH_DEFINES +#include +#include +#include +#include +#include "Recast.h" +#include "RecastAlloc.h" +#include "RecastAssert.h" + +/// @par +/// +/// Basically, any spans that are closer to a boundary or obstruction than the specified radius +/// are marked as unwalkable. +/// +/// This method is usually called immediately after the heightfield has been built. +/// +/// @see rcCompactHeightfield, rcBuildCompactHeightfield, rcConfig::walkableRadius +bool rcErodeWalkableArea(rcContext* ctx, int radius, rcCompactHeightfield& chf) +{ + rcAssert(ctx); + + const int w = chf.width; + const int h = chf.height; + + ctx->startTimer(RC_TIMER_ERODE_AREA); + + unsigned char* dist = (unsigned char*)rcAlloc(sizeof(unsigned char)*chf.spanCount, RC_ALLOC_TEMP); + if (!dist) + { + ctx->log(RC_LOG_ERROR, "erodeWalkableArea: Out of memory 'dist' (%d).", chf.spanCount); + return false; + } + + // Init distance. + memset(dist, 0xff, sizeof(unsigned char)*chf.spanCount); + + // Mark boundary cells. + for (int y = 0; y < h; ++y) + { + for (int x = 0; x < w; ++x) + { + const rcCompactCell& c = chf.cells[x+y*w]; + for (int i = (int)c.index, ni = (int)(c.index+c.count); i < ni; ++i) + { + if (chf.areas[i] == RC_NULL_AREA) + { + dist[i] = 0; + } + else + { + const rcCompactSpan& s = chf.spans[i]; + int nc = 0; + for (int dir = 0; dir < 4; ++dir) + { + if (rcGetCon(s, dir) != RC_NOT_CONNECTED) + { + const int nx = x + rcGetDirOffsetX(dir); + const int ny = y + rcGetDirOffsetY(dir); + const int nidx = (int)chf.cells[nx+ny*w].index + rcGetCon(s, dir); + if (chf.areas[nidx] != RC_NULL_AREA) + { + nc++; + } + } + } + // At least one missing neighbour. + if (nc != 4) + dist[i] = 0; + } + } + } + } + + unsigned char nd; + + // Pass 1 + for (int y = 0; y < h; ++y) + { + for (int x = 0; x < w; ++x) + { + const rcCompactCell& c = chf.cells[x+y*w]; + for (int i = (int)c.index, ni = (int)(c.index+c.count); i < ni; ++i) + { + const rcCompactSpan& s = chf.spans[i]; + + if (rcGetCon(s, 0) != RC_NOT_CONNECTED) + { + // (-1,0) + const int ax = x + rcGetDirOffsetX(0); + const int ay = y + rcGetDirOffsetY(0); + const int ai = (int)chf.cells[ax+ay*w].index + rcGetCon(s, 0); + const rcCompactSpan& as = chf.spans[ai]; + nd = (unsigned char)rcMin((int)dist[ai]+2, 255); + if (nd < dist[i]) + dist[i] = nd; + + // (-1,-1) + if (rcGetCon(as, 3) != RC_NOT_CONNECTED) + { + const int aax = ax + rcGetDirOffsetX(3); + const int aay = ay + rcGetDirOffsetY(3); + const int aai = (int)chf.cells[aax+aay*w].index + rcGetCon(as, 3); + nd = (unsigned char)rcMin((int)dist[aai]+3, 255); + if (nd < dist[i]) + dist[i] = nd; + } + } + if (rcGetCon(s, 3) != RC_NOT_CONNECTED) + { + // (0,-1) + const int ax = x + rcGetDirOffsetX(3); + const int ay = y + rcGetDirOffsetY(3); + const int ai = (int)chf.cells[ax+ay*w].index + rcGetCon(s, 3); + const rcCompactSpan& as = chf.spans[ai]; + nd = (unsigned char)rcMin((int)dist[ai]+2, 255); + if (nd < dist[i]) + dist[i] = nd; + + // (1,-1) + if (rcGetCon(as, 2) != RC_NOT_CONNECTED) + { + const int aax = ax + rcGetDirOffsetX(2); + const int aay = ay + rcGetDirOffsetY(2); + const int aai = (int)chf.cells[aax+aay*w].index + rcGetCon(as, 2); + nd = (unsigned char)rcMin((int)dist[aai]+3, 255); + if (nd < dist[i]) + dist[i] = nd; + } + } + } + } + } + + // Pass 2 + for (int y = h-1; y >= 0; --y) + { + for (int x = w-1; x >= 0; --x) + { + const rcCompactCell& c = chf.cells[x+y*w]; + for (int i = (int)c.index, ni = (int)(c.index+c.count); i < ni; ++i) + { + const rcCompactSpan& s = chf.spans[i]; + + if (rcGetCon(s, 2) != RC_NOT_CONNECTED) + { + // (1,0) + const int ax = x + rcGetDirOffsetX(2); + const int ay = y + rcGetDirOffsetY(2); + const int ai = (int)chf.cells[ax+ay*w].index + rcGetCon(s, 2); + const rcCompactSpan& as = chf.spans[ai]; + nd = (unsigned char)rcMin((int)dist[ai]+2, 255); + if (nd < dist[i]) + dist[i] = nd; + + // (1,1) + if (rcGetCon(as, 1) != RC_NOT_CONNECTED) + { + const int aax = ax + rcGetDirOffsetX(1); + const int aay = ay + rcGetDirOffsetY(1); + const int aai = (int)chf.cells[aax+aay*w].index + rcGetCon(as, 1); + nd = (unsigned char)rcMin((int)dist[aai]+3, 255); + if (nd < dist[i]) + dist[i] = nd; + } + } + if (rcGetCon(s, 1) != RC_NOT_CONNECTED) + { + // (0,1) + const int ax = x + rcGetDirOffsetX(1); + const int ay = y + rcGetDirOffsetY(1); + const int ai = (int)chf.cells[ax+ay*w].index + rcGetCon(s, 1); + const rcCompactSpan& as = chf.spans[ai]; + nd = (unsigned char)rcMin((int)dist[ai]+2, 255); + if (nd < dist[i]) + dist[i] = nd; + + // (-1,1) + if (rcGetCon(as, 0) != RC_NOT_CONNECTED) + { + const int aax = ax + rcGetDirOffsetX(0); + const int aay = ay + rcGetDirOffsetY(0); + const int aai = (int)chf.cells[aax+aay*w].index + rcGetCon(as, 0); + nd = (unsigned char)rcMin((int)dist[aai]+3, 255); + if (nd < dist[i]) + dist[i] = nd; + } + } + } + } + } + + const unsigned char thr = (unsigned char)(radius*2); + for (int i = 0; i < chf.spanCount; ++i) + if (dist[i] < thr) + chf.areas[i] = RC_NULL_AREA; + + rcFree(dist); + + ctx->stopTimer(RC_TIMER_ERODE_AREA); + + return true; +} + +static void insertSort(unsigned char* a, const int n) +{ + int i, j; + for (i = 1; i < n; i++) + { + const unsigned char value = a[i]; + for (j = i - 1; j >= 0 && a[j] > value; j--) + a[j+1] = a[j]; + a[j+1] = value; + } +} + +/// @par +/// +/// This filter is usually applied after applying area id's using functions +/// such as #rcMarkBoxArea, #rcMarkConvexPolyArea, and #rcMarkCylinderArea. +/// +/// @see rcCompactHeightfield +bool rcMedianFilterWalkableArea(rcContext* ctx, rcCompactHeightfield& chf) +{ + rcAssert(ctx); + + const int w = chf.width; + const int h = chf.height; + + ctx->startTimer(RC_TIMER_MEDIAN_AREA); + + unsigned char* areas = (unsigned char*)rcAlloc(sizeof(unsigned char)*chf.spanCount, RC_ALLOC_TEMP); + if (!areas) + { + ctx->log(RC_LOG_ERROR, "medianFilterWalkableArea: Out of memory 'areas' (%d).", chf.spanCount); + return false; + } + + // Init distance. + memset(areas, 0xff, sizeof(unsigned char)*chf.spanCount); + + for (int y = 0; y < h; ++y) + { + for (int x = 0; x < w; ++x) + { + const rcCompactCell& c = chf.cells[x+y*w]; + for (int i = (int)c.index, ni = (int)(c.index+c.count); i < ni; ++i) + { + const rcCompactSpan& s = chf.spans[i]; + if (chf.areas[i] == RC_NULL_AREA) + { + areas[i] = chf.areas[i]; + continue; + } + + unsigned char nei[9]; + for (int j = 0; j < 9; ++j) + nei[j] = chf.areas[i]; + + for (int dir = 0; dir < 4; ++dir) + { + if (rcGetCon(s, dir) != RC_NOT_CONNECTED) + { + const int ax = x + rcGetDirOffsetX(dir); + const int ay = y + rcGetDirOffsetY(dir); + const int ai = (int)chf.cells[ax+ay*w].index + rcGetCon(s, dir); + if (chf.areas[ai] != RC_NULL_AREA) + nei[dir*2+0] = chf.areas[ai]; + + const rcCompactSpan& as = chf.spans[ai]; + const int dir2 = (dir+1) & 0x3; + if (rcGetCon(as, dir2) != RC_NOT_CONNECTED) + { + const int ax2 = ax + rcGetDirOffsetX(dir2); + const int ay2 = ay + rcGetDirOffsetY(dir2); + const int ai2 = (int)chf.cells[ax2+ay2*w].index + rcGetCon(as, dir2); + if (chf.areas[ai2] != RC_NULL_AREA) + nei[dir*2+1] = chf.areas[ai2]; + } + } + } + insertSort(nei, 9); + areas[i] = nei[4]; + } + } + } + + memcpy(chf.areas, areas, sizeof(unsigned char)*chf.spanCount); + + rcFree(areas); + + ctx->stopTimer(RC_TIMER_MEDIAN_AREA); + + return true; +} + +/// @par +/// +/// The value of spacial parameters are in world units. +/// +/// @see rcCompactHeightfield, rcMedianFilterWalkableArea +void rcMarkBoxArea(rcContext* ctx, const float* bmin, const float* bmax, unsigned char areaId, + rcCompactHeightfield& chf) +{ + rcAssert(ctx); + + ctx->startTimer(RC_TIMER_MARK_BOX_AREA); + + int minx = (int)((bmin[0]-chf.bmin[0])/chf.cs); + int miny = (int)((bmin[1]-chf.bmin[1])/chf.ch); + int minz = (int)((bmin[2]-chf.bmin[2])/chf.cs); + int maxx = (int)((bmax[0]-chf.bmin[0])/chf.cs); + int maxy = (int)((bmax[1]-chf.bmin[1])/chf.ch); + int maxz = (int)((bmax[2]-chf.bmin[2])/chf.cs); + + if (maxx < 0) return; + if (minx >= chf.width) return; + if (maxz < 0) return; + if (minz >= chf.height) return; + + if (minx < 0) minx = 0; + if (maxx >= chf.width) maxx = chf.width-1; + if (minz < 0) minz = 0; + if (maxz >= chf.height) maxz = chf.height-1; + + for (int z = minz; z <= maxz; ++z) + { + for (int x = minx; x <= maxx; ++x) + { + const rcCompactCell& c = chf.cells[x+z*chf.width]; + for (int i = (int)c.index, ni = (int)(c.index+c.count); i < ni; ++i) + { + rcCompactSpan& s = chf.spans[i]; + if ((int)s.y >= miny && (int)s.y <= maxy) + { + if (chf.areas[i] != RC_NULL_AREA) + chf.areas[i] = areaId; + } + } + } + } + + ctx->stopTimer(RC_TIMER_MARK_BOX_AREA); + +} + + +static int pointInPoly(int nvert, const float* verts, const float* p) +{ + int i, j, c = 0; + for (i = 0, j = nvert-1; i < nvert; j = i++) + { + const float* vi = &verts[i*3]; + const float* vj = &verts[j*3]; + if (((vi[2] > p[2]) != (vj[2] > p[2])) && + (p[0] < (vj[0]-vi[0]) * (p[2]-vi[2]) / (vj[2]-vi[2]) + vi[0]) ) + c = !c; + } + return c; +} + +/// @par +/// +/// The value of spacial parameters are in world units. +/// +/// The y-values of the polygon vertices are ignored. So the polygon is effectively +/// projected onto the xz-plane at @p hmin, then extruded to @p hmax. +/// +/// @see rcCompactHeightfield, rcMedianFilterWalkableArea +void rcMarkConvexPolyArea(rcContext* ctx, const float* verts, const int nverts, + const float hmin, const float hmax, unsigned char areaId, + rcCompactHeightfield& chf) +{ + rcAssert(ctx); + + ctx->startTimer(RC_TIMER_MARK_CONVEXPOLY_AREA); + + float bmin[3], bmax[3]; + rcVcopy(bmin, verts); + rcVcopy(bmax, verts); + for (int i = 1; i < nverts; ++i) + { + rcVmin(bmin, &verts[i*3]); + rcVmax(bmax, &verts[i*3]); + } + bmin[1] = hmin; + bmax[1] = hmax; + + int minx = (int)((bmin[0]-chf.bmin[0])/chf.cs); + int miny = (int)((bmin[1]-chf.bmin[1])/chf.ch); + int minz = (int)((bmin[2]-chf.bmin[2])/chf.cs); + int maxx = (int)((bmax[0]-chf.bmin[0])/chf.cs); + int maxy = (int)((bmax[1]-chf.bmin[1])/chf.ch); + int maxz = (int)((bmax[2]-chf.bmin[2])/chf.cs); + + if (maxx < 0) return; + if (minx >= chf.width) return; + if (maxz < 0) return; + if (minz >= chf.height) return; + + if (minx < 0) minx = 0; + if (maxx >= chf.width) maxx = chf.width-1; + if (minz < 0) minz = 0; + if (maxz >= chf.height) maxz = chf.height-1; + + + // TODO: Optimize. + for (int z = minz; z <= maxz; ++z) + { + for (int x = minx; x <= maxx; ++x) + { + const rcCompactCell& c = chf.cells[x+z*chf.width]; + for (int i = (int)c.index, ni = (int)(c.index+c.count); i < ni; ++i) + { + rcCompactSpan& s = chf.spans[i]; + if (chf.areas[i] == RC_NULL_AREA) + continue; + if ((int)s.y >= miny && (int)s.y <= maxy) + { + float p[3]; + p[0] = chf.bmin[0] + (x+0.5f)*chf.cs; + p[1] = 0; + p[2] = chf.bmin[2] + (z+0.5f)*chf.cs; + + if (pointInPoly(nverts, verts, p)) + { + chf.areas[i] = areaId; + } + } + } + } + } + + ctx->stopTimer(RC_TIMER_MARK_CONVEXPOLY_AREA); +} + +int rcOffsetPoly(const float* verts, const int nverts, const float offset, + float* outVerts, const int maxOutVerts) +{ + const float MITER_LIMIT = 1.20f; + + int n = 0; + + for (int i = 0; i < nverts; i++) + { + const int a = (i+nverts-1) % nverts; + const int b = i; + const int c = (i+1) % nverts; + const float* va = &verts[a*3]; + const float* vb = &verts[b*3]; + const float* vc = &verts[c*3]; + float dx0 = vb[0] - va[0]; + float dy0 = vb[2] - va[2]; + float d0 = dx0*dx0 + dy0*dy0; + if (d0 > 1e-6f) + { + d0 = 1.0f/rcSqrt(d0); + dx0 *= d0; + dy0 *= d0; + } + float dx1 = vc[0] - vb[0]; + float dy1 = vc[2] - vb[2]; + float d1 = dx1*dx1 + dy1*dy1; + if (d1 > 1e-6f) + { + d1 = 1.0f/rcSqrt(d1); + dx1 *= d1; + dy1 *= d1; + } + const float dlx0 = -dy0; + const float dly0 = dx0; + const float dlx1 = -dy1; + const float dly1 = dx1; + float cross = dx1*dy0 - dx0*dy1; + float dmx = (dlx0 + dlx1) * 0.5f; + float dmy = (dly0 + dly1) * 0.5f; + float dmr2 = dmx*dmx + dmy*dmy; + bool bevel = dmr2 * MITER_LIMIT*MITER_LIMIT < 1.0f; + if (dmr2 > 1e-6f) + { + const float scale = 1.0f / dmr2; + dmx *= scale; + dmy *= scale; + } + + if (bevel && cross < 0.0f) + { + if (n+2 >= maxOutVerts) + return 0; + float d = (1.0f - (dx0*dx1 + dy0*dy1))*0.5f; + outVerts[n*3+0] = vb[0] + (-dlx0+dx0*d)*offset; + outVerts[n*3+1] = vb[1]; + outVerts[n*3+2] = vb[2] + (-dly0+dy0*d)*offset; + n++; + outVerts[n*3+0] = vb[0] + (-dlx1-dx1*d)*offset; + outVerts[n*3+1] = vb[1]; + outVerts[n*3+2] = vb[2] + (-dly1-dy1*d)*offset; + n++; + } + else + { + if (n+1 >= maxOutVerts) + return 0; + outVerts[n*3+0] = vb[0] - dmx*offset; + outVerts[n*3+1] = vb[1]; + outVerts[n*3+2] = vb[2] - dmy*offset; + n++; + } + } + + return n; +} + + +/// @par +/// +/// The value of spacial parameters are in world units. +/// +/// @see rcCompactHeightfield, rcMedianFilterWalkableArea +void rcMarkCylinderArea(rcContext* ctx, const float* pos, + const float r, const float h, unsigned char areaId, + rcCompactHeightfield& chf) +{ + rcAssert(ctx); + + ctx->startTimer(RC_TIMER_MARK_CYLINDER_AREA); + + float bmin[3], bmax[3]; + bmin[0] = pos[0] - r; + bmin[1] = pos[1]; + bmin[2] = pos[2] - r; + bmax[0] = pos[0] + r; + bmax[1] = pos[1] + h; + bmax[2] = pos[2] + r; + const float r2 = r*r; + + int minx = (int)((bmin[0]-chf.bmin[0])/chf.cs); + int miny = (int)((bmin[1]-chf.bmin[1])/chf.ch); + int minz = (int)((bmin[2]-chf.bmin[2])/chf.cs); + int maxx = (int)((bmax[0]-chf.bmin[0])/chf.cs); + int maxy = (int)((bmax[1]-chf.bmin[1])/chf.ch); + int maxz = (int)((bmax[2]-chf.bmin[2])/chf.cs); + + if (maxx < 0) return; + if (minx >= chf.width) return; + if (maxz < 0) return; + if (minz >= chf.height) return; + + if (minx < 0) minx = 0; + if (maxx >= chf.width) maxx = chf.width-1; + if (minz < 0) minz = 0; + if (maxz >= chf.height) maxz = chf.height-1; + + + for (int z = minz; z <= maxz; ++z) + { + for (int x = minx; x <= maxx; ++x) + { + const rcCompactCell& c = chf.cells[x+z*chf.width]; + for (int i = (int)c.index, ni = (int)(c.index+c.count); i < ni; ++i) + { + rcCompactSpan& s = chf.spans[i]; + + if (chf.areas[i] == RC_NULL_AREA) + continue; + + if ((int)s.y >= miny && (int)s.y <= maxy) + { + const float sx = chf.bmin[0] + (x+0.5f)*chf.cs; + const float sz = chf.bmin[2] + (z+0.5f)*chf.cs; + const float dx = sx - pos[0]; + const float dz = sz - pos[2]; + + if (dx*dx + dz*dz < r2) + { + chf.areas[i] = areaId; + } + } + } + } + } + + ctx->stopTimer(RC_TIMER_MARK_CYLINDER_AREA); +} diff --git a/KREngine/3rdparty/recast/source/RecastContour.cpp b/KREngine/3rdparty/recast/source/RecastContour.cpp new file mode 100755 index 0000000..5c324bc --- /dev/null +++ b/KREngine/3rdparty/recast/source/RecastContour.cpp @@ -0,0 +1,851 @@ +// +// Copyright (c) 2009-2010 Mikko Mononen memon@inside.org +// +// This software is provided 'as-is', without any express or implied +// warranty. In no event will the authors be held liable for any damages +// arising from the use of this software. +// Permission is granted to anyone to use this software for any purpose, +// including commercial applications, and to alter it and redistribute it +// freely, subject to the following restrictions: +// 1. The origin of this software must not be misrepresented; you must not +// claim that you wrote the original software. If you use this software +// in a product, an acknowledgment in the product documentation would be +// appreciated but is not required. +// 2. Altered source versions must be plainly marked as such, and must not be +// misrepresented as being the original software. +// 3. This notice may not be removed or altered from any source distribution. +// + +#define _USE_MATH_DEFINES +#include +#include +#include +#include "Recast.h" +#include "RecastAlloc.h" +#include "RecastAssert.h" + + +static int getCornerHeight(int x, int y, int i, int dir, + const rcCompactHeightfield& chf, + bool& isBorderVertex) +{ + const rcCompactSpan& s = chf.spans[i]; + int ch = (int)s.y; + int dirp = (dir+1) & 0x3; + + unsigned int regs[4] = {0,0,0,0}; + + // Combine region and area codes in order to prevent + // border vertices which are in between two areas to be removed. + regs[0] = chf.spans[i].reg | (chf.areas[i] << 16); + + if (rcGetCon(s, dir) != RC_NOT_CONNECTED) + { + const int ax = x + rcGetDirOffsetX(dir); + const int ay = y + rcGetDirOffsetY(dir); + const int ai = (int)chf.cells[ax+ay*chf.width].index + rcGetCon(s, dir); + const rcCompactSpan& as = chf.spans[ai]; + ch = rcMax(ch, (int)as.y); + regs[1] = chf.spans[ai].reg | (chf.areas[ai] << 16); + if (rcGetCon(as, dirp) != RC_NOT_CONNECTED) + { + const int ax2 = ax + rcGetDirOffsetX(dirp); + const int ay2 = ay + rcGetDirOffsetY(dirp); + const int ai2 = (int)chf.cells[ax2+ay2*chf.width].index + rcGetCon(as, dirp); + const rcCompactSpan& as2 = chf.spans[ai2]; + ch = rcMax(ch, (int)as2.y); + regs[2] = chf.spans[ai2].reg | (chf.areas[ai2] << 16); + } + } + if (rcGetCon(s, dirp) != RC_NOT_CONNECTED) + { + const int ax = x + rcGetDirOffsetX(dirp); + const int ay = y + rcGetDirOffsetY(dirp); + const int ai = (int)chf.cells[ax+ay*chf.width].index + rcGetCon(s, dirp); + const rcCompactSpan& as = chf.spans[ai]; + ch = rcMax(ch, (int)as.y); + regs[3] = chf.spans[ai].reg | (chf.areas[ai] << 16); + if (rcGetCon(as, dir) != RC_NOT_CONNECTED) + { + const int ax2 = ax + rcGetDirOffsetX(dir); + const int ay2 = ay + rcGetDirOffsetY(dir); + const int ai2 = (int)chf.cells[ax2+ay2*chf.width].index + rcGetCon(as, dir); + const rcCompactSpan& as2 = chf.spans[ai2]; + ch = rcMax(ch, (int)as2.y); + regs[2] = chf.spans[ai2].reg | (chf.areas[ai2] << 16); + } + } + + // Check if the vertex is special edge vertex, these vertices will be removed later. + for (int j = 0; j < 4; ++j) + { + const int a = j; + const int b = (j+1) & 0x3; + const int c = (j+2) & 0x3; + const int d = (j+3) & 0x3; + + // The vertex is a border vertex there are two same exterior cells in a row, + // followed by two interior cells and none of the regions are out of bounds. + const bool twoSameExts = (regs[a] & regs[b] & RC_BORDER_REG) != 0 && regs[a] == regs[b]; + const bool twoInts = ((regs[c] | regs[d]) & RC_BORDER_REG) == 0; + const bool intsSameArea = (regs[c]>>16) == (regs[d]>>16); + const bool noZeros = regs[a] != 0 && regs[b] != 0 && regs[c] != 0 && regs[d] != 0; + if (twoSameExts && twoInts && intsSameArea && noZeros) + { + isBorderVertex = true; + break; + } + } + + return ch; +} + +static void walkContour(int x, int y, int i, + rcCompactHeightfield& chf, + unsigned char* flags, rcIntArray& points) +{ + // Choose the first non-connected edge + unsigned char dir = 0; + while ((flags[i] & (1 << dir)) == 0) + dir++; + + unsigned char startDir = dir; + int starti = i; + + const unsigned char area = chf.areas[i]; + + int iter = 0; + while (++iter < 40000) + { + if (flags[i] & (1 << dir)) + { + // Choose the edge corner + bool isBorderVertex = false; + bool isAreaBorder = false; + int px = x; + int py = getCornerHeight(x, y, i, dir, chf, isBorderVertex); + int pz = y; + switch(dir) + { + case 0: pz++; break; + case 1: px++; pz++; break; + case 2: px++; break; + } + int r = 0; + const rcCompactSpan& s = chf.spans[i]; + if (rcGetCon(s, dir) != RC_NOT_CONNECTED) + { + const int ax = x + rcGetDirOffsetX(dir); + const int ay = y + rcGetDirOffsetY(dir); + const int ai = (int)chf.cells[ax+ay*chf.width].index + rcGetCon(s, dir); + r = (int)chf.spans[ai].reg; + if (area != chf.areas[ai]) + isAreaBorder = true; + } + if (isBorderVertex) + r |= RC_BORDER_VERTEX; + if (isAreaBorder) + r |= RC_AREA_BORDER; + points.push(px); + points.push(py); + points.push(pz); + points.push(r); + + flags[i] &= ~(1 << dir); // Remove visited edges + dir = (dir+1) & 0x3; // Rotate CW + } + else + { + int ni = -1; + const int nx = x + rcGetDirOffsetX(dir); + const int ny = y + rcGetDirOffsetY(dir); + const rcCompactSpan& s = chf.spans[i]; + if (rcGetCon(s, dir) != RC_NOT_CONNECTED) + { + const rcCompactCell& nc = chf.cells[nx+ny*chf.width]; + ni = (int)nc.index + rcGetCon(s, dir); + } + if (ni == -1) + { + // Should not happen. + return; + } + x = nx; + y = ny; + i = ni; + dir = (dir+3) & 0x3; // Rotate CCW + } + + if (starti == i && startDir == dir) + { + break; + } + } +} + +static float distancePtSeg(const int x, const int z, + const int px, const int pz, + const int qx, const int qz) +{ +/* float pqx = (float)(qx - px); + float pqy = (float)(qy - py); + float pqz = (float)(qz - pz); + float dx = (float)(x - px); + float dy = (float)(y - py); + float dz = (float)(z - pz); + float d = pqx*pqx + pqy*pqy + pqz*pqz; + float t = pqx*dx + pqy*dy + pqz*dz; + if (d > 0) + t /= d; + if (t < 0) + t = 0; + else if (t > 1) + t = 1; + + dx = px + t*pqx - x; + dy = py + t*pqy - y; + dz = pz + t*pqz - z; + + return dx*dx + dy*dy + dz*dz;*/ + + float pqx = (float)(qx - px); + float pqz = (float)(qz - pz); + float dx = (float)(x - px); + float dz = (float)(z - pz); + float d = pqx*pqx + pqz*pqz; + float t = pqx*dx + pqz*dz; + if (d > 0) + t /= d; + if (t < 0) + t = 0; + else if (t > 1) + t = 1; + + dx = px + t*pqx - x; + dz = pz + t*pqz - z; + + return dx*dx + dz*dz; +} + +static void simplifyContour(rcIntArray& points, rcIntArray& simplified, + const float maxError, const int maxEdgeLen, const int buildFlags) +{ + // Add initial points. + bool hasConnections = false; + for (int i = 0; i < points.size(); i += 4) + { + if ((points[i+3] & RC_CONTOUR_REG_MASK) != 0) + { + hasConnections = true; + break; + } + } + + if (hasConnections) + { + // The contour has some portals to other regions. + // Add a new point to every location where the region changes. + for (int i = 0, ni = points.size()/4; i < ni; ++i) + { + int ii = (i+1) % ni; + const bool differentRegs = (points[i*4+3] & RC_CONTOUR_REG_MASK) != (points[ii*4+3] & RC_CONTOUR_REG_MASK); + const bool areaBorders = (points[i*4+3] & RC_AREA_BORDER) != (points[ii*4+3] & RC_AREA_BORDER); + if (differentRegs || areaBorders) + { + simplified.push(points[i*4+0]); + simplified.push(points[i*4+1]); + simplified.push(points[i*4+2]); + simplified.push(i); + } + } + } + + if (simplified.size() == 0) + { + // If there is no connections at all, + // create some initial points for the simplification process. + // Find lower-left and upper-right vertices of the contour. + int llx = points[0]; + int lly = points[1]; + int llz = points[2]; + int lli = 0; + int urx = points[0]; + int ury = points[1]; + int urz = points[2]; + int uri = 0; + for (int i = 0; i < points.size(); i += 4) + { + int x = points[i+0]; + int y = points[i+1]; + int z = points[i+2]; + if (x < llx || (x == llx && z < llz)) + { + llx = x; + lly = y; + llz = z; + lli = i/4; + } + if (x > urx || (x == urx && z > urz)) + { + urx = x; + ury = y; + urz = z; + uri = i/4; + } + } + simplified.push(llx); + simplified.push(lly); + simplified.push(llz); + simplified.push(lli); + + simplified.push(urx); + simplified.push(ury); + simplified.push(urz); + simplified.push(uri); + } + + // Add points until all raw points are within + // error tolerance to the simplified shape. + const int pn = points.size()/4; + for (int i = 0; i < simplified.size()/4; ) + { + int ii = (i+1) % (simplified.size()/4); + + const int ax = simplified[i*4+0]; + const int az = simplified[i*4+2]; + const int ai = simplified[i*4+3]; + + const int bx = simplified[ii*4+0]; + const int bz = simplified[ii*4+2]; + const int bi = simplified[ii*4+3]; + + // Find maximum deviation from the segment. + float maxd = 0; + int maxi = -1; + int ci, cinc, endi; + + // Traverse the segment in lexilogical order so that the + // max deviation is calculated similarly when traversing + // opposite segments. + if (bx > ax || (bx == ax && bz > az)) + { + cinc = 1; + ci = (ai+cinc) % pn; + endi = bi; + } + else + { + cinc = pn-1; + ci = (bi+cinc) % pn; + endi = ai; + } + + // Tessellate only outer edges or edges between areas. + if ((points[ci*4+3] & RC_CONTOUR_REG_MASK) == 0 || + (points[ci*4+3] & RC_AREA_BORDER)) + { + while (ci != endi) + { + float d = distancePtSeg(points[ci*4+0], points[ci*4+2], ax, az, bx, bz); + if (d > maxd) + { + maxd = d; + maxi = ci; + } + ci = (ci+cinc) % pn; + } + } + + + // If the max deviation is larger than accepted error, + // add new point, else continue to next segment. + if (maxi != -1 && maxd > (maxError*maxError)) + { + // Add space for the new point. + simplified.resize(simplified.size()+4); + const int n = simplified.size()/4; + for (int j = n-1; j > i; --j) + { + simplified[j*4+0] = simplified[(j-1)*4+0]; + simplified[j*4+1] = simplified[(j-1)*4+1]; + simplified[j*4+2] = simplified[(j-1)*4+2]; + simplified[j*4+3] = simplified[(j-1)*4+3]; + } + // Add the point. + simplified[(i+1)*4+0] = points[maxi*4+0]; + simplified[(i+1)*4+1] = points[maxi*4+1]; + simplified[(i+1)*4+2] = points[maxi*4+2]; + simplified[(i+1)*4+3] = maxi; + } + else + { + ++i; + } + } + + // Split too long edges. + if (maxEdgeLen > 0 && (buildFlags & (RC_CONTOUR_TESS_WALL_EDGES|RC_CONTOUR_TESS_AREA_EDGES)) != 0) + { + for (int i = 0; i < simplified.size()/4; ) + { + const int ii = (i+1) % (simplified.size()/4); + + const int ax = simplified[i*4+0]; + const int az = simplified[i*4+2]; + const int ai = simplified[i*4+3]; + + const int bx = simplified[ii*4+0]; + const int bz = simplified[ii*4+2]; + const int bi = simplified[ii*4+3]; + + // Find maximum deviation from the segment. + int maxi = -1; + int ci = (ai+1) % pn; + + // Tessellate only outer edges or edges between areas. + bool tess = false; + // Wall edges. + if ((buildFlags & RC_CONTOUR_TESS_WALL_EDGES) && (points[ci*4+3] & RC_CONTOUR_REG_MASK) == 0) + tess = true; + // Edges between areas. + if ((buildFlags & RC_CONTOUR_TESS_AREA_EDGES) && (points[ci*4+3] & RC_AREA_BORDER)) + tess = true; + + if (tess) + { + int dx = bx - ax; + int dz = bz - az; + if (dx*dx + dz*dz > maxEdgeLen*maxEdgeLen) + { + // Round based on the segments in lexilogical order so that the + // max tesselation is consistent regardles in which direction + // segments are traversed. + const int n = bi < ai ? (bi+pn - ai) : (bi - ai); + if (n > 1) + { + if (bx > ax || (bx == ax && bz > az)) + maxi = (ai + n/2) % pn; + else + maxi = (ai + (n+1)/2) % pn; + } + } + } + + // If the max deviation is larger than accepted error, + // add new point, else continue to next segment. + if (maxi != -1) + { + // Add space for the new point. + simplified.resize(simplified.size()+4); + const int n = simplified.size()/4; + for (int j = n-1; j > i; --j) + { + simplified[j*4+0] = simplified[(j-1)*4+0]; + simplified[j*4+1] = simplified[(j-1)*4+1]; + simplified[j*4+2] = simplified[(j-1)*4+2]; + simplified[j*4+3] = simplified[(j-1)*4+3]; + } + // Add the point. + simplified[(i+1)*4+0] = points[maxi*4+0]; + simplified[(i+1)*4+1] = points[maxi*4+1]; + simplified[(i+1)*4+2] = points[maxi*4+2]; + simplified[(i+1)*4+3] = maxi; + } + else + { + ++i; + } + } + } + + for (int i = 0; i < simplified.size()/4; ++i) + { + // The edge vertex flag is take from the current raw point, + // and the neighbour region is take from the next raw point. + const int ai = (simplified[i*4+3]+1) % pn; + const int bi = simplified[i*4+3]; + simplified[i*4+3] = (points[ai*4+3] & (RC_CONTOUR_REG_MASK|RC_AREA_BORDER)) | (points[bi*4+3] & RC_BORDER_VERTEX); + } + +} + +static void removeDegenerateSegments(rcIntArray& simplified) +{ + // Remove adjacent vertices which are equal on xz-plane, + // or else the triangulator will get confused. + for (int i = 0; i < simplified.size()/4; ++i) + { + int ni = i+1; + if (ni >= (simplified.size()/4)) + ni = 0; + + if (simplified[i*4+0] == simplified[ni*4+0] && + simplified[i*4+2] == simplified[ni*4+2]) + { + // Degenerate segment, remove. + for (int j = i; j < simplified.size()/4-1; ++j) + { + simplified[j*4+0] = simplified[(j+1)*4+0]; + simplified[j*4+1] = simplified[(j+1)*4+1]; + simplified[j*4+2] = simplified[(j+1)*4+2]; + simplified[j*4+3] = simplified[(j+1)*4+3]; + } + simplified.resize(simplified.size()-4); + } + } +} + +static int calcAreaOfPolygon2D(const int* verts, const int nverts) +{ + int area = 0; + for (int i = 0, j = nverts-1; i < nverts; j=i++) + { + const int* vi = &verts[i*4]; + const int* vj = &verts[j*4]; + area += vi[0] * vj[2] - vj[0] * vi[2]; + } + return (area+1) / 2; +} + +inline bool ileft(const int* a, const int* b, const int* c) +{ + return (b[0] - a[0]) * (c[2] - a[2]) - (c[0] - a[0]) * (b[2] - a[2]) <= 0; +} + +static void getClosestIndices(const int* vertsa, const int nvertsa, + const int* vertsb, const int nvertsb, + int& ia, int& ib) +{ + int closestDist = 0xfffffff; + ia = -1, ib = -1; + for (int i = 0; i < nvertsa; ++i) + { + const int in = (i+1) % nvertsa; + const int ip = (i+nvertsa-1) % nvertsa; + const int* va = &vertsa[i*4]; + const int* van = &vertsa[in*4]; + const int* vap = &vertsa[ip*4]; + + for (int j = 0; j < nvertsb; ++j) + { + const int* vb = &vertsb[j*4]; + // vb must be "infront" of va. + if (ileft(vap,va,vb) && ileft(va,van,vb)) + { + const int dx = vb[0] - va[0]; + const int dz = vb[2] - va[2]; + const int d = dx*dx + dz*dz; + if (d < closestDist) + { + ia = i; + ib = j; + closestDist = d; + } + } + } + } +} + +static bool mergeContours(rcContour& ca, rcContour& cb, int ia, int ib) +{ + const int maxVerts = ca.nverts + cb.nverts + 2; + int* verts = (int*)rcAlloc(sizeof(int)*maxVerts*4, RC_ALLOC_PERM); + if (!verts) + return false; + + int nv = 0; + + // Copy contour A. + for (int i = 0; i <= ca.nverts; ++i) + { + int* dst = &verts[nv*4]; + const int* src = &ca.verts[((ia+i)%ca.nverts)*4]; + dst[0] = src[0]; + dst[1] = src[1]; + dst[2] = src[2]; + dst[3] = src[3]; + nv++; + } + + // Copy contour B + for (int i = 0; i <= cb.nverts; ++i) + { + int* dst = &verts[nv*4]; + const int* src = &cb.verts[((ib+i)%cb.nverts)*4]; + dst[0] = src[0]; + dst[1] = src[1]; + dst[2] = src[2]; + dst[3] = src[3]; + nv++; + } + + rcFree(ca.verts); + ca.verts = verts; + ca.nverts = nv; + + rcFree(cb.verts); + cb.verts = 0; + cb.nverts = 0; + + return true; +} + +/// @par +/// +/// The raw contours will match the region outlines exactly. The @p maxError and @p maxEdgeLen +/// parameters control how closely the simplified contours will match the raw contours. +/// +/// Simplified contours are generated such that the vertices for portals between areas match up. +/// (They are considered mandatory vertices.) +/// +/// Setting @p maxEdgeLength to zero will disabled the edge length feature. +/// +/// See the #rcConfig documentation for more information on the configuration parameters. +/// +/// @see rcAllocContourSet, rcCompactHeightfield, rcContourSet, rcConfig +bool rcBuildContours(rcContext* ctx, rcCompactHeightfield& chf, + const float maxError, const int maxEdgeLen, + rcContourSet& cset, const int buildFlags) +{ + rcAssert(ctx); + + const int w = chf.width; + const int h = chf.height; + const int borderSize = chf.borderSize; + + ctx->startTimer(RC_TIMER_BUILD_CONTOURS); + + rcVcopy(cset.bmin, chf.bmin); + rcVcopy(cset.bmax, chf.bmax); + if (borderSize > 0) + { + // If the heightfield was build with bordersize, remove the offset. + const float pad = borderSize*chf.cs; + cset.bmin[0] += pad; + cset.bmin[2] += pad; + cset.bmax[0] -= pad; + cset.bmax[2] -= pad; + } + cset.cs = chf.cs; + cset.ch = chf.ch; + cset.width = chf.width - chf.borderSize*2; + cset.height = chf.height - chf.borderSize*2; + cset.borderSize = chf.borderSize; + + int maxContours = rcMax((int)chf.maxRegions, 8); + cset.conts = (rcContour*)rcAlloc(sizeof(rcContour)*maxContours, RC_ALLOC_PERM); + if (!cset.conts) + return false; + cset.nconts = 0; + + rcScopedDelete flags = (unsigned char*)rcAlloc(sizeof(unsigned char)*chf.spanCount, RC_ALLOC_TEMP); + if (!flags) + { + ctx->log(RC_LOG_ERROR, "rcBuildContours: Out of memory 'flags' (%d).", chf.spanCount); + return false; + } + + ctx->startTimer(RC_TIMER_BUILD_CONTOURS_TRACE); + + // Mark boundaries. + for (int y = 0; y < h; ++y) + { + for (int x = 0; x < w; ++x) + { + const rcCompactCell& c = chf.cells[x+y*w]; + for (int i = (int)c.index, ni = (int)(c.index+c.count); i < ni; ++i) + { + unsigned char res = 0; + const rcCompactSpan& s = chf.spans[i]; + if (!chf.spans[i].reg || (chf.spans[i].reg & RC_BORDER_REG)) + { + flags[i] = 0; + continue; + } + for (int dir = 0; dir < 4; ++dir) + { + unsigned short r = 0; + if (rcGetCon(s, dir) != RC_NOT_CONNECTED) + { + const int ax = x + rcGetDirOffsetX(dir); + const int ay = y + rcGetDirOffsetY(dir); + const int ai = (int)chf.cells[ax+ay*w].index + rcGetCon(s, dir); + r = chf.spans[ai].reg; + } + if (r == chf.spans[i].reg) + res |= (1 << dir); + } + flags[i] = res ^ 0xf; // Inverse, mark non connected edges. + } + } + } + + ctx->stopTimer(RC_TIMER_BUILD_CONTOURS_TRACE); + + rcIntArray verts(256); + rcIntArray simplified(64); + + for (int y = 0; y < h; ++y) + { + for (int x = 0; x < w; ++x) + { + const rcCompactCell& c = chf.cells[x+y*w]; + for (int i = (int)c.index, ni = (int)(c.index+c.count); i < ni; ++i) + { + if (flags[i] == 0 || flags[i] == 0xf) + { + flags[i] = 0; + continue; + } + const unsigned short reg = chf.spans[i].reg; + if (!reg || (reg & RC_BORDER_REG)) + continue; + const unsigned char area = chf.areas[i]; + + verts.resize(0); + simplified.resize(0); + + ctx->startTimer(RC_TIMER_BUILD_CONTOURS_TRACE); + walkContour(x, y, i, chf, flags, verts); + ctx->stopTimer(RC_TIMER_BUILD_CONTOURS_TRACE); + + ctx->startTimer(RC_TIMER_BUILD_CONTOURS_SIMPLIFY); + simplifyContour(verts, simplified, maxError, maxEdgeLen, buildFlags); + removeDegenerateSegments(simplified); + ctx->stopTimer(RC_TIMER_BUILD_CONTOURS_SIMPLIFY); + + + // Store region->contour remap info. + // Create contour. + if (simplified.size()/4 >= 3) + { + if (cset.nconts >= maxContours) + { + // Allocate more contours. + // This can happen when there are tiny holes in the heightfield. + const int oldMax = maxContours; + maxContours *= 2; + rcContour* newConts = (rcContour*)rcAlloc(sizeof(rcContour)*maxContours, RC_ALLOC_PERM); + for (int j = 0; j < cset.nconts; ++j) + { + newConts[j] = cset.conts[j]; + // Reset source pointers to prevent data deletion. + cset.conts[j].verts = 0; + cset.conts[j].rverts = 0; + } + rcFree(cset.conts); + cset.conts = newConts; + + ctx->log(RC_LOG_WARNING, "rcBuildContours: Expanding max contours from %d to %d.", oldMax, maxContours); + } + + rcContour* cont = &cset.conts[cset.nconts++]; + + cont->nverts = simplified.size()/4; + cont->verts = (int*)rcAlloc(sizeof(int)*cont->nverts*4, RC_ALLOC_PERM); + if (!cont->verts) + { + ctx->log(RC_LOG_ERROR, "rcBuildContours: Out of memory 'verts' (%d).", cont->nverts); + return false; + } + memcpy(cont->verts, &simplified[0], sizeof(int)*cont->nverts*4); + if (borderSize > 0) + { + // If the heightfield was build with bordersize, remove the offset. + for (int j = 0; j < cont->nverts; ++j) + { + int* v = &cont->verts[j*4]; + v[0] -= borderSize; + v[2] -= borderSize; + } + } + + cont->nrverts = verts.size()/4; + cont->rverts = (int*)rcAlloc(sizeof(int)*cont->nrverts*4, RC_ALLOC_PERM); + if (!cont->rverts) + { + ctx->log(RC_LOG_ERROR, "rcBuildContours: Out of memory 'rverts' (%d).", cont->nrverts); + return false; + } + memcpy(cont->rverts, &verts[0], sizeof(int)*cont->nrverts*4); + if (borderSize > 0) + { + // If the heightfield was build with bordersize, remove the offset. + for (int j = 0; j < cont->nrverts; ++j) + { + int* v = &cont->rverts[j*4]; + v[0] -= borderSize; + v[2] -= borderSize; + } + } + +/* cont->cx = cont->cy = cont->cz = 0; + for (int i = 0; i < cont->nverts; ++i) + { + cont->cx += cont->verts[i*4+0]; + cont->cy += cont->verts[i*4+1]; + cont->cz += cont->verts[i*4+2]; + } + cont->cx /= cont->nverts; + cont->cy /= cont->nverts; + cont->cz /= cont->nverts;*/ + + cont->reg = reg; + cont->area = area; + } + } + } + } + + // Check and merge droppings. + // Sometimes the previous algorithms can fail and create several contours + // per area. This pass will try to merge the holes into the main region. + for (int i = 0; i < cset.nconts; ++i) + { + rcContour& cont = cset.conts[i]; + // Check if the contour is would backwards. + if (calcAreaOfPolygon2D(cont.verts, cont.nverts) < 0) + { + // Find another contour which has the same region ID. + int mergeIdx = -1; + for (int j = 0; j < cset.nconts; ++j) + { + if (i == j) continue; + if (cset.conts[j].nverts && cset.conts[j].reg == cont.reg) + { + // Make sure the polygon is correctly oriented. + if (calcAreaOfPolygon2D(cset.conts[j].verts, cset.conts[j].nverts)) + { + mergeIdx = j; + break; + } + } + } + if (mergeIdx == -1) + { + ctx->log(RC_LOG_WARNING, "rcBuildContours: Could not find merge target for bad contour %d.", i); + } + else + { + rcContour& mcont = cset.conts[mergeIdx]; + // Merge by closest points. + int ia = 0, ib = 0; + getClosestIndices(mcont.verts, mcont.nverts, cont.verts, cont.nverts, ia, ib); + if (ia == -1 || ib == -1) + { + ctx->log(RC_LOG_WARNING, "rcBuildContours: Failed to find merge points for %d and %d.", i, mergeIdx); + continue; + } + if (!mergeContours(mcont, cont, ia, ib)) + { + ctx->log(RC_LOG_WARNING, "rcBuildContours: Failed to merge contours %d and %d.", i, mergeIdx); + continue; + } + } + } + } + + ctx->stopTimer(RC_TIMER_BUILD_CONTOURS); + + return true; +} diff --git a/KREngine/3rdparty/recast/source/RecastFilter.cpp b/KREngine/3rdparty/recast/source/RecastFilter.cpp new file mode 100755 index 0000000..bf985c3 --- /dev/null +++ b/KREngine/3rdparty/recast/source/RecastFilter.cpp @@ -0,0 +1,207 @@ +// +// Copyright (c) 2009-2010 Mikko Mononen memon@inside.org +// +// This software is provided 'as-is', without any express or implied +// warranty. In no event will the authors be held liable for any damages +// arising from the use of this software. +// Permission is granted to anyone to use this software for any purpose, +// including commercial applications, and to alter it and redistribute it +// freely, subject to the following restrictions: +// 1. The origin of this software must not be misrepresented; you must not +// claim that you wrote the original software. If you use this software +// in a product, an acknowledgment in the product documentation would be +// appreciated but is not required. +// 2. Altered source versions must be plainly marked as such, and must not be +// misrepresented as being the original software. +// 3. This notice may not be removed or altered from any source distribution. +// + +#define _USE_MATH_DEFINES +#include +#include +#include "Recast.h" +#include "RecastAssert.h" + +/// @par +/// +/// Allows the formation of walkable regions that will flow over low lying +/// objects such as curbs, and up structures such as stairways. +/// +/// Two neighboring spans are walkable if: rcAbs(currentSpan.smax - neighborSpan.smax) < waklableClimb +/// +/// @warning Will override the effect of #rcFilterLedgeSpans. So if both filters are used, call +/// #rcFilterLedgeSpans after calling this filter. +/// +/// @see rcHeightfield, rcConfig +void rcFilterLowHangingWalkableObstacles(rcContext* ctx, const int walkableClimb, rcHeightfield& solid) +{ + rcAssert(ctx); + + ctx->startTimer(RC_TIMER_FILTER_LOW_OBSTACLES); + + const int w = solid.width; + const int h = solid.height; + + for (int y = 0; y < h; ++y) + { + for (int x = 0; x < w; ++x) + { + rcSpan* ps = 0; + bool previousWalkable = false; + unsigned char previousArea = RC_NULL_AREA; + + for (rcSpan* s = solid.spans[x + y*w]; s; ps = s, s = s->next) + { + const bool walkable = s->area != RC_NULL_AREA; + // If current span is not walkable, but there is walkable + // span just below it, mark the span above it walkable too. + if (!walkable && previousWalkable) + { + if (rcAbs((int)s->smax - (int)ps->smax) <= walkableClimb) + s->area = previousArea; + } + // Copy walkable flag so that it cannot propagate + // past multiple non-walkable objects. + previousWalkable = walkable; + previousArea = s->area; + } + } + } + + ctx->stopTimer(RC_TIMER_FILTER_LOW_OBSTACLES); +} + +/// @par +/// +/// A ledge is a span with one or more neighbors whose maximum is further away than @p walkableClimb +/// from the current span's maximum. +/// This method removes the impact of the overestimation of conservative voxelization +/// so the resulting mesh will not have regions hanging in the air over ledges. +/// +/// A span is a ledge if: rcAbs(currentSpan.smax - neighborSpan.smax) > walkableClimb +/// +/// @see rcHeightfield, rcConfig +void rcFilterLedgeSpans(rcContext* ctx, const int walkableHeight, const int walkableClimb, + rcHeightfield& solid) +{ + rcAssert(ctx); + + ctx->startTimer(RC_TIMER_FILTER_BORDER); + + const int w = solid.width; + const int h = solid.height; + const int MAX_HEIGHT = 0xffff; + + // Mark border spans. + for (int y = 0; y < h; ++y) + { + for (int x = 0; x < w; ++x) + { + for (rcSpan* s = solid.spans[x + y*w]; s; s = s->next) + { + // Skip non walkable spans. + if (s->area == RC_NULL_AREA) + continue; + + const int bot = (int)(s->smax); + const int top = s->next ? (int)(s->next->smin) : MAX_HEIGHT; + + // Find neighbours minimum height. + int minh = MAX_HEIGHT; + + // Min and max height of accessible neighbours. + int asmin = s->smax; + int asmax = s->smax; + + for (int dir = 0; dir < 4; ++dir) + { + int dx = x + rcGetDirOffsetX(dir); + int dy = y + rcGetDirOffsetY(dir); + // Skip neighbours which are out of bounds. + if (dx < 0 || dy < 0 || dx >= w || dy >= h) + { + minh = rcMin(minh, -walkableClimb - bot); + continue; + } + + // From minus infinity to the first span. + rcSpan* ns = solid.spans[dx + dy*w]; + int nbot = -walkableClimb; + int ntop = ns ? (int)ns->smin : MAX_HEIGHT; + // Skip neightbour if the gap between the spans is too small. + if (rcMin(top,ntop) - rcMax(bot,nbot) > walkableHeight) + minh = rcMin(minh, nbot - bot); + + // Rest of the spans. + for (ns = solid.spans[dx + dy*w]; ns; ns = ns->next) + { + nbot = (int)ns->smax; + ntop = ns->next ? (int)ns->next->smin : MAX_HEIGHT; + // Skip neightbour if the gap between the spans is too small. + if (rcMin(top,ntop) - rcMax(bot,nbot) > walkableHeight) + { + minh = rcMin(minh, nbot - bot); + + // Find min/max accessible neighbour height. + if (rcAbs(nbot - bot) <= walkableClimb) + { + if (nbot < asmin) asmin = nbot; + if (nbot > asmax) asmax = nbot; + } + + } + } + } + + // The current span is close to a ledge if the drop to any + // neighbour span is less than the walkableClimb. + if (minh < -walkableClimb) + s->area = RC_NULL_AREA; + + // If the difference between all neighbours is too large, + // we are at steep slope, mark the span as ledge. + if ((asmax - asmin) > walkableClimb) + { + s->area = RC_NULL_AREA; + } + } + } + } + + ctx->stopTimer(RC_TIMER_FILTER_BORDER); +} + +/// @par +/// +/// For this filter, the clearance above the span is the distance from the span's +/// maximum to the next higher span's minimum. (Same grid column.) +/// +/// @see rcHeightfield, rcConfig +void rcFilterWalkableLowHeightSpans(rcContext* ctx, int walkableHeight, rcHeightfield& solid) +{ + rcAssert(ctx); + + ctx->startTimer(RC_TIMER_FILTER_WALKABLE); + + const int w = solid.width; + const int h = solid.height; + const int MAX_HEIGHT = 0xffff; + + // Remove walkable flag from spans which do not have enough + // space above them for the agent to stand there. + for (int y = 0; y < h; ++y) + { + for (int x = 0; x < w; ++x) + { + for (rcSpan* s = solid.spans[x + y*w]; s; s = s->next) + { + const int bot = (int)(s->smax); + const int top = s->next ? (int)(s->next->smin) : MAX_HEIGHT; + if ((top - bot) <= walkableHeight) + s->area = RC_NULL_AREA; + } + } + } + + ctx->stopTimer(RC_TIMER_FILTER_WALKABLE); +} diff --git a/KREngine/3rdparty/recast/source/RecastLayers.cpp b/KREngine/3rdparty/recast/source/RecastLayers.cpp new file mode 100755 index 0000000..204f72e --- /dev/null +++ b/KREngine/3rdparty/recast/source/RecastLayers.cpp @@ -0,0 +1,620 @@ +// +// Copyright (c) 2009-2010 Mikko Mononen memon@inside.org +// +// This software is provided 'as-is', without any express or implied +// warranty. In no event will the authors be held liable for any damages +// arising from the use of this software. +// Permission is granted to anyone to use this software for any purpose, +// including commercial applications, and to alter it and redistribute it +// freely, subject to the following restrictions: +// 1. The origin of this software must not be misrepresented; you must not +// claim that you wrote the original software. If you use this software +// in a product, an acknowledgment in the product documentation would be +// appreciated but is not required. +// 2. Altered source versions must be plainly marked as such, and must not be +// misrepresented as being the original software. +// 3. This notice may not be removed or altered from any source distribution. +// + +#include +#define _USE_MATH_DEFINES +#include +#include +#include +#include +#include "Recast.h" +#include "RecastAlloc.h" +#include "RecastAssert.h" + + +static const int RC_MAX_LAYERS = RC_NOT_CONNECTED; +static const int RC_MAX_NEIS = 16; + +struct rcLayerRegion +{ + unsigned char layers[RC_MAX_LAYERS]; + unsigned char neis[RC_MAX_NEIS]; + unsigned short ymin, ymax; + unsigned char layerId; // Layer ID + unsigned char nlayers; // Layer count + unsigned char nneis; // Neighbour count + unsigned char base; // Flag indicating if the region is hte base of merged regions. +}; + + +static void addUnique(unsigned char* a, unsigned char& an, unsigned char v) +{ + const int n = (int)an; + for (int i = 0; i < n; ++i) + if (a[i] == v) + return; + a[an] = v; + an++; +} + +static bool contains(const unsigned char* a, const unsigned char an, const unsigned char v) +{ + const int n = (int)an; + for (int i = 0; i < n; ++i) + if (a[i] == v) + return true; + return false; +} + +inline bool overlapRange(const unsigned short amin, const unsigned short amax, + const unsigned short bmin, const unsigned short bmax) +{ + return (amin > bmax || amax < bmin) ? false : true; +} + + + +struct rcLayerSweepSpan +{ + unsigned short ns; // number samples + unsigned char id; // region id + unsigned char nei; // neighbour id +}; + +/// @par +/// +/// See the #rcConfig documentation for more information on the configuration parameters. +/// +/// @see rcAllocHeightfieldLayerSet, rcCompactHeightfield, rcHeightfieldLayerSet, rcConfig +bool rcBuildHeightfieldLayers(rcContext* ctx, rcCompactHeightfield& chf, + const int borderSize, const int walkableHeight, + rcHeightfieldLayerSet& lset) +{ + rcAssert(ctx); + + ctx->startTimer(RC_TIMER_BUILD_LAYERS); + + const int w = chf.width; + const int h = chf.height; + + rcScopedDelete srcReg = (unsigned char*)rcAlloc(sizeof(unsigned char)*chf.spanCount, RC_ALLOC_TEMP); + if (!srcReg) + { + ctx->log(RC_LOG_ERROR, "rcBuildHeightfieldLayers: Out of memory 'srcReg' (%d).", chf.spanCount); + return false; + } + memset(srcReg,0xff,sizeof(unsigned char)*chf.spanCount); + + const int nsweeps = chf.width; + rcScopedDelete sweeps = (rcLayerSweepSpan*)rcAlloc(sizeof(rcLayerSweepSpan)*nsweeps, RC_ALLOC_TEMP); + if (!sweeps) + { + ctx->log(RC_LOG_ERROR, "rcBuildHeightfieldLayers: Out of memory 'sweeps' (%d).", nsweeps); + return false; + } + + + // Partition walkable area into monotone regions. + int prevCount[256]; + unsigned char regId = 0; + + for (int y = borderSize; y < h-borderSize; ++y) + { + memset(prevCount,0,sizeof(int)*regId); + unsigned char sweepId = 0; + + for (int x = borderSize; x < w-borderSize; ++x) + { + const rcCompactCell& c = chf.cells[x+y*w]; + + for (int i = (int)c.index, ni = (int)(c.index+c.count); i < ni; ++i) + { + const rcCompactSpan& s = chf.spans[i]; + if (chf.areas[i] == RC_NULL_AREA) continue; + + unsigned char sid = 0xff; + + // -x + if (rcGetCon(s, 0) != RC_NOT_CONNECTED) + { + const int ax = x + rcGetDirOffsetX(0); + const int ay = y + rcGetDirOffsetY(0); + const int ai = (int)chf.cells[ax+ay*w].index + rcGetCon(s, 0); + if (chf.areas[ai] != RC_NULL_AREA && srcReg[ai] != 0xff) + sid = srcReg[ai]; + } + + if (sid == 0xff) + { + sid = sweepId++; + sweeps[sid].nei = 0xff; + sweeps[sid].ns = 0; + } + + // -y + if (rcGetCon(s,3) != RC_NOT_CONNECTED) + { + const int ax = x + rcGetDirOffsetX(3); + const int ay = y + rcGetDirOffsetY(3); + const int ai = (int)chf.cells[ax+ay*w].index + rcGetCon(s, 3); + const unsigned char nr = srcReg[ai]; + if (nr != 0xff) + { + // Set neighbour when first valid neighbour is encoutered. + if (sweeps[sid].ns == 0) + sweeps[sid].nei = nr; + + if (sweeps[sid].nei == nr) + { + // Update existing neighbour + sweeps[sid].ns++; + prevCount[nr]++; + } + else + { + // This is hit if there is nore than one neighbour. + // Invalidate the neighbour. + sweeps[sid].nei = 0xff; + } + } + } + + srcReg[i] = sid; + } + } + + // Create unique ID. + for (int i = 0; i < sweepId; ++i) + { + // If the neighbour is set and there is only one continuous connection to it, + // the sweep will be merged with the previous one, else new region is created. + if (sweeps[i].nei != 0xff && prevCount[sweeps[i].nei] == (int)sweeps[i].ns) + { + sweeps[i].id = sweeps[i].nei; + } + else + { + if (regId == 255) + { + ctx->log(RC_LOG_ERROR, "rcBuildHeightfieldLayers: Region ID overflow."); + return false; + } + sweeps[i].id = regId++; + } + } + + // Remap local sweep ids to region ids. + for (int x = borderSize; x < w-borderSize; ++x) + { + const rcCompactCell& c = chf.cells[x+y*w]; + for (int i = (int)c.index, ni = (int)(c.index+c.count); i < ni; ++i) + { + if (srcReg[i] != 0xff) + srcReg[i] = sweeps[srcReg[i]].id; + } + } + } + + // Allocate and init layer regions. + const int nregs = (int)regId; + rcScopedDelete regs = (rcLayerRegion*)rcAlloc(sizeof(rcLayerRegion)*nregs, RC_ALLOC_TEMP); + if (!regs) + { + ctx->log(RC_LOG_ERROR, "rcBuildHeightfieldLayers: Out of memory 'regs' (%d).", nregs); + return false; + } + memset(regs, 0, sizeof(rcLayerRegion)*nregs); + for (int i = 0; i < nregs; ++i) + { + regs[i].layerId = 0xff; + regs[i].ymin = 0xffff; + regs[i].ymax = 0; + } + + // Find region neighbours and overlapping regions. + for (int y = 0; y < h; ++y) + { + for (int x = 0; x < w; ++x) + { + const rcCompactCell& c = chf.cells[x+y*w]; + + unsigned char lregs[RC_MAX_LAYERS]; + int nlregs = 0; + + for (int i = (int)c.index, ni = (int)(c.index+c.count); i < ni; ++i) + { + const rcCompactSpan& s = chf.spans[i]; + const unsigned char ri = srcReg[i]; + if (ri == 0xff) continue; + + regs[ri].ymin = rcMin(regs[ri].ymin, s.y); + regs[ri].ymax = rcMax(regs[ri].ymax, s.y); + + // Collect all region layers. + if (nlregs < RC_MAX_LAYERS) + lregs[nlregs++] = ri; + + // Update neighbours + for (int dir = 0; dir < 4; ++dir) + { + if (rcGetCon(s, dir) != RC_NOT_CONNECTED) + { + const int ax = x + rcGetDirOffsetX(dir); + const int ay = y + rcGetDirOffsetY(dir); + const int ai = (int)chf.cells[ax+ay*w].index + rcGetCon(s, dir); + const unsigned char rai = srcReg[ai]; + if (rai != 0xff && rai != ri) + addUnique(regs[ri].neis, regs[ri].nneis, rai); + } + } + + } + + // Update overlapping regions. + for (int i = 0; i < nlregs-1; ++i) + { + for (int j = i+1; j < nlregs; ++j) + { + if (lregs[i] != lregs[j]) + { + rcLayerRegion& ri = regs[lregs[i]]; + rcLayerRegion& rj = regs[lregs[j]]; + addUnique(ri.layers, ri.nlayers, lregs[j]); + addUnique(rj.layers, rj.nlayers, lregs[i]); + } + } + } + + } + } + + // Create 2D layers from regions. + unsigned char layerId = 0; + + static const int MAX_STACK = 64; + unsigned char stack[MAX_STACK]; + int nstack = 0; + + for (int i = 0; i < nregs; ++i) + { + rcLayerRegion& root = regs[i]; + // Skip alreadu visited. + if (root.layerId != 0xff) + continue; + + // Start search. + root.layerId = layerId; + root.base = 1; + + nstack = 0; + stack[nstack++] = (unsigned char)i; + + while (nstack) + { + // Pop front + rcLayerRegion& reg = regs[stack[0]]; + nstack--; + for (int j = 0; j < nstack; ++j) + stack[j] = stack[j+1]; + + const int nneis = (int)reg.nneis; + for (int j = 0; j < nneis; ++j) + { + const unsigned char nei = reg.neis[j]; + rcLayerRegion& regn = regs[nei]; + // Skip already visited. + if (regn.layerId != 0xff) + continue; + // Skip if the neighbour is overlapping root region. + if (contains(root.layers, root.nlayers, nei)) + continue; + // Skip if the height range would become too large. + const int ymin = rcMin(root.ymin, regn.ymin); + const int ymax = rcMax(root.ymax, regn.ymax); + if ((ymax - ymin) >= 255) + continue; + + if (nstack < MAX_STACK) + { + // Deepen + stack[nstack++] = (unsigned char)nei; + + // Mark layer id + regn.layerId = layerId; + // Merge current layers to root. + for (int k = 0; k < regn.nlayers; ++k) + addUnique(root.layers, root.nlayers, regn.layers[k]); + root.ymin = rcMin(root.ymin, regn.ymin); + root.ymax = rcMax(root.ymax, regn.ymax); + } + } + } + + layerId++; + } + + // Merge non-overlapping regions that are close in height. + const unsigned short mergeHeight = (unsigned short)walkableHeight * 4; + + for (int i = 0; i < nregs; ++i) + { + rcLayerRegion& ri = regs[i]; + if (!ri.base) continue; + + unsigned char newId = ri.layerId; + + for (;;) + { + unsigned char oldId = 0xff; + + for (int j = 0; j < nregs; ++j) + { + if (i == j) continue; + rcLayerRegion& rj = regs[j]; + if (!rj.base) continue; + + // Skip if teh regions are not close to each other. + if (!overlapRange(ri.ymin,ri.ymax+mergeHeight, rj.ymin,rj.ymax+mergeHeight)) + continue; + // Skip if the height range would become too large. + const int ymin = rcMin(ri.ymin, rj.ymin); + const int ymax = rcMax(ri.ymax, rj.ymax); + if ((ymax - ymin) >= 255) + continue; + + // Make sure that there is no overlap when mergin 'ri' and 'rj'. + bool overlap = false; + // Iterate over all regions which have the same layerId as 'rj' + for (int k = 0; k < nregs; ++k) + { + if (regs[k].layerId != rj.layerId) + continue; + // Check if region 'k' is overlapping region 'ri' + // Index to 'regs' is the same as region id. + if (contains(ri.layers,ri.nlayers, (unsigned char)k)) + { + overlap = true; + break; + } + } + // Cannot merge of regions overlap. + if (overlap) + continue; + + // Can merge i and j. + oldId = rj.layerId; + break; + } + + // Could not find anything to merge with, stop. + if (oldId == 0xff) + break; + + // Merge + for (int j = 0; j < nregs; ++j) + { + rcLayerRegion& rj = regs[j]; + if (rj.layerId == oldId) + { + rj.base = 0; + // Remap layerIds. + rj.layerId = newId; + // Add overlaid layers from 'rj' to 'ri'. + for (int k = 0; k < rj.nlayers; ++k) + addUnique(ri.layers, ri.nlayers, rj.layers[k]); + // Update heigh bounds. + ri.ymin = rcMin(ri.ymin, rj.ymin); + ri.ymax = rcMax(ri.ymax, rj.ymax); + } + } + } + } + + // Compact layerIds + unsigned char remap[256]; + memset(remap, 0, 256); + + // Find number of unique layers. + layerId = 0; + for (int i = 0; i < nregs; ++i) + remap[regs[i].layerId] = 1; + for (int i = 0; i < 256; ++i) + { + if (remap[i]) + remap[i] = layerId++; + else + remap[i] = 0xff; + } + // Remap ids. + for (int i = 0; i < nregs; ++i) + regs[i].layerId = remap[regs[i].layerId]; + + // No layers, return empty. + if (layerId == 0) + { + ctx->stopTimer(RC_TIMER_BUILD_LAYERS); + return true; + } + + // Create layers. + rcAssert(lset.layers == 0); + + const int lw = w - borderSize*2; + const int lh = h - borderSize*2; + + // Build contracted bbox for layers. + float bmin[3], bmax[3]; + rcVcopy(bmin, chf.bmin); + rcVcopy(bmax, chf.bmax); + bmin[0] += borderSize*chf.cs; + bmin[2] += borderSize*chf.cs; + bmax[0] -= borderSize*chf.cs; + bmax[2] -= borderSize*chf.cs; + + lset.nlayers = (int)layerId; + + lset.layers = (rcHeightfieldLayer*)rcAlloc(sizeof(rcHeightfieldLayer)*lset.nlayers, RC_ALLOC_PERM); + if (!lset.layers) + { + ctx->log(RC_LOG_ERROR, "rcBuildHeightfieldLayers: Out of memory 'layers' (%d).", lset.nlayers); + return false; + } + memset(lset.layers, 0, sizeof(rcHeightfieldLayer)*lset.nlayers); + + + // Store layers. + for (int i = 0; i < lset.nlayers; ++i) + { + unsigned char curId = (unsigned char)i; + + // Allocate memory for the current layer. + rcHeightfieldLayer* layer = &lset.layers[i]; + memset(layer, 0, sizeof(rcHeightfieldLayer)); + + const int gridSize = sizeof(unsigned char)*lw*lh; + + layer->heights = (unsigned char*)rcAlloc(gridSize, RC_ALLOC_PERM); + if (!layer->heights) + { + ctx->log(RC_LOG_ERROR, "rcBuildHeightfieldLayers: Out of memory 'heights' (%d).", gridSize); + return false; + } + memset(layer->heights, 0xff, gridSize); + + layer->areas = (unsigned char*)rcAlloc(gridSize, RC_ALLOC_PERM); + if (!layer->areas) + { + ctx->log(RC_LOG_ERROR, "rcBuildHeightfieldLayers: Out of memory 'areas' (%d).", gridSize); + return false; + } + memset(layer->areas, 0, gridSize); + + layer->cons = (unsigned char*)rcAlloc(gridSize, RC_ALLOC_PERM); + if (!layer->cons) + { + ctx->log(RC_LOG_ERROR, "rcBuildHeightfieldLayers: Out of memory 'cons' (%d).", gridSize); + return false; + } + memset(layer->cons, 0, gridSize); + + // Find layer height bounds. + int hmin = 0, hmax = 0; + for (int j = 0; j < nregs; ++j) + { + if (regs[j].base && regs[j].layerId == curId) + { + hmin = (int)regs[j].ymin; + hmax = (int)regs[j].ymax; + } + } + + layer->width = lw; + layer->height = lh; + layer->cs = chf.cs; + layer->ch = chf.ch; + + // Adjust the bbox to fit the heighfield. + rcVcopy(layer->bmin, bmin); + rcVcopy(layer->bmax, bmax); + layer->bmin[1] = bmin[1] + hmin*chf.ch; + layer->bmax[1] = bmin[1] + hmax*chf.ch; + layer->hmin = hmin; + layer->hmax = hmax; + + // Update usable data region. + layer->minx = layer->width; + layer->maxx = 0; + layer->miny = layer->height; + layer->maxy = 0; + + // Copy height and area from compact heighfield. + for (int y = 0; y < lh; ++y) + { + for (int x = 0; x < lw; ++x) + { + const int cx = borderSize+x; + const int cy = borderSize+y; + const rcCompactCell& c = chf.cells[cx+cy*w]; + for (int j = (int)c.index, nj = (int)(c.index+c.count); j < nj; ++j) + { + const rcCompactSpan& s = chf.spans[j]; + // Skip unassigned regions. + if (srcReg[j] == 0xff) + continue; + // Skip of does nto belong to current layer. + unsigned char lid = regs[srcReg[j]].layerId; + if (lid != curId) + continue; + + // Update data bounds. + layer->minx = rcMin(layer->minx, x); + layer->maxx = rcMax(layer->maxx, x); + layer->miny = rcMin(layer->miny, y); + layer->maxy = rcMax(layer->maxy, y); + + // Store height and area type. + const int idx = x+y*lw; + layer->heights[idx] = (unsigned char)(s.y - hmin); + layer->areas[idx] = chf.areas[j]; + + // Check connection. + unsigned char portal = 0; + unsigned char con = 0; + for (int dir = 0; dir < 4; ++dir) + { + if (rcGetCon(s, dir) != RC_NOT_CONNECTED) + { + const int ax = cx + rcGetDirOffsetX(dir); + const int ay = cy + rcGetDirOffsetY(dir); + const int ai = (int)chf.cells[ax+ay*w].index + rcGetCon(s, dir); + unsigned char alid = srcReg[ai] != 0xff ? regs[srcReg[ai]].layerId : 0xff; + // Portal mask + if (chf.areas[ai] != RC_NULL_AREA && lid != alid) + { + portal |= (unsigned char)(1< hmin) + layer->heights[idx] = rcMax(layer->heights[idx], (unsigned char)(as.y - hmin)); + } + // Valid connection mask + if (chf.areas[ai] != RC_NULL_AREA && lid == alid) + { + const int nx = ax - borderSize; + const int ny = ay - borderSize; + if (nx >= 0 && ny >= 0 && nx < lw && ny < lh) + con |= (unsigned char)(1<cons[idx] = (portal << 4) | con; + } + } + } + + if (layer->minx > layer->maxx) + layer->minx = layer->maxx = 0; + if (layer->miny > layer->maxy) + layer->miny = layer->maxy = 0; + } + + ctx->stopTimer(RC_TIMER_BUILD_LAYERS); + + return true; +} diff --git a/KREngine/3rdparty/recast/source/RecastMesh.cpp b/KREngine/3rdparty/recast/source/RecastMesh.cpp new file mode 100755 index 0000000..534a72e --- /dev/null +++ b/KREngine/3rdparty/recast/source/RecastMesh.cpp @@ -0,0 +1,1433 @@ +// +// Copyright (c) 2009-2010 Mikko Mononen memon@inside.org +// +// This software is provided 'as-is', without any express or implied +// warranty. In no event will the authors be held liable for any damages +// arising from the use of this software. +// Permission is granted to anyone to use this software for any purpose, +// including commercial applications, and to alter it and redistribute it +// freely, subject to the following restrictions: +// 1. The origin of this software must not be misrepresented; you must not +// claim that you wrote the original software. If you use this software +// in a product, an acknowledgment in the product documentation would be +// appreciated but is not required. +// 2. Altered source versions must be plainly marked as such, and must not be +// misrepresented as being the original software. +// 3. This notice may not be removed or altered from any source distribution. +// + +#define _USE_MATH_DEFINES +#include +#include +#include +#include "Recast.h" +#include "RecastAlloc.h" +#include "RecastAssert.h" + +struct rcEdge +{ + unsigned short vert[2]; + unsigned short polyEdge[2]; + unsigned short poly[2]; +}; + +static bool buildMeshAdjacency(unsigned short* polys, const int npolys, + const int nverts, const int vertsPerPoly) +{ + // Based on code by Eric Lengyel from: + // http://www.terathon.com/code/edges.php + + int maxEdgeCount = npolys*vertsPerPoly; + unsigned short* firstEdge = (unsigned short*)rcAlloc(sizeof(unsigned short)*(nverts + maxEdgeCount), RC_ALLOC_TEMP); + if (!firstEdge) + return false; + unsigned short* nextEdge = firstEdge + nverts; + int edgeCount = 0; + + rcEdge* edges = (rcEdge*)rcAlloc(sizeof(rcEdge)*maxEdgeCount, RC_ALLOC_TEMP); + if (!edges) + { + rcFree(firstEdge); + return false; + } + + for (int i = 0; i < nverts; i++) + firstEdge[i] = RC_MESH_NULL_IDX; + + for (int i = 0; i < npolys; ++i) + { + unsigned short* t = &polys[i*vertsPerPoly*2]; + for (int j = 0; j < vertsPerPoly; ++j) + { + if (t[j] == RC_MESH_NULL_IDX) break; + unsigned short v0 = t[j]; + unsigned short v1 = (j+1 >= vertsPerPoly || t[j+1] == RC_MESH_NULL_IDX) ? t[0] : t[j+1]; + if (v0 < v1) + { + rcEdge& edge = edges[edgeCount]; + edge.vert[0] = v0; + edge.vert[1] = v1; + edge.poly[0] = (unsigned short)i; + edge.polyEdge[0] = (unsigned short)j; + edge.poly[1] = (unsigned short)i; + edge.polyEdge[1] = 0; + // Insert edge + nextEdge[edgeCount] = firstEdge[v0]; + firstEdge[v0] = (unsigned short)edgeCount; + edgeCount++; + } + } + } + + for (int i = 0; i < npolys; ++i) + { + unsigned short* t = &polys[i*vertsPerPoly*2]; + for (int j = 0; j < vertsPerPoly; ++j) + { + if (t[j] == RC_MESH_NULL_IDX) break; + unsigned short v0 = t[j]; + unsigned short v1 = (j+1 >= vertsPerPoly || t[j+1] == RC_MESH_NULL_IDX) ? t[0] : t[j+1]; + if (v0 > v1) + { + for (unsigned short e = firstEdge[v1]; e != RC_MESH_NULL_IDX; e = nextEdge[e]) + { + rcEdge& edge = edges[e]; + if (edge.vert[1] == v0 && edge.poly[0] == edge.poly[1]) + { + edge.poly[1] = (unsigned short)i; + edge.polyEdge[1] = (unsigned short)j; + break; + } + } + } + } + } + + // Store adjacency + for (int i = 0; i < edgeCount; ++i) + { + const rcEdge& e = edges[i]; + if (e.poly[0] != e.poly[1]) + { + unsigned short* p0 = &polys[e.poly[0]*vertsPerPoly*2]; + unsigned short* p1 = &polys[e.poly[1]*vertsPerPoly*2]; + p0[vertsPerPoly + e.polyEdge[0]] = e.poly[1]; + p1[vertsPerPoly + e.polyEdge[1]] = e.poly[0]; + } + } + + rcFree(firstEdge); + rcFree(edges); + + return true; +} + + +static const int VERTEX_BUCKET_COUNT = (1<<12); + +inline int computeVertexHash(int x, int y, int z) +{ + const unsigned int h1 = 0x8da6b343; // Large multiplicative constants; + const unsigned int h2 = 0xd8163841; // here arbitrarily chosen primes + const unsigned int h3 = 0xcb1ab31f; + unsigned int n = h1 * x + h2 * y + h3 * z; + return (int)(n & (VERTEX_BUCKET_COUNT-1)); +} + +static unsigned short addVertex(unsigned short x, unsigned short y, unsigned short z, + unsigned short* verts, int* firstVert, int* nextVert, int& nv) +{ + int bucket = computeVertexHash(x, 0, z); + int i = firstVert[bucket]; + + while (i != -1) + { + const unsigned short* v = &verts[i*3]; + if (v[0] == x && (rcAbs(v[1] - y) <= 2) && v[2] == z) + return (unsigned short)i; + i = nextVert[i]; // next + } + + // Could not find, create new. + i = nv; nv++; + unsigned short* v = &verts[i*3]; + v[0] = x; + v[1] = y; + v[2] = z; + nextVert[i] = firstVert[bucket]; + firstVert[bucket] = i; + + return (unsigned short)i; +} + +inline int prev(int i, int n) { return i-1 >= 0 ? i-1 : n-1; } +inline int next(int i, int n) { return i+1 < n ? i+1 : 0; } + +inline int area2(const int* a, const int* b, const int* c) +{ + return (b[0] - a[0]) * (c[2] - a[2]) - (c[0] - a[0]) * (b[2] - a[2]); +} + +// Exclusive or: true iff exactly one argument is true. +// The arguments are negated to ensure that they are 0/1 +// values. Then the bitwise Xor operator may apply. +// (This idea is due to Michael Baldwin.) +inline bool xorb(bool x, bool y) +{ + return !x ^ !y; +} + +// Returns true iff c is strictly to the left of the directed +// line through a to b. +inline bool left(const int* a, const int* b, const int* c) +{ + return area2(a, b, c) < 0; +} + +inline bool leftOn(const int* a, const int* b, const int* c) +{ + return area2(a, b, c) <= 0; +} + +inline bool collinear(const int* a, const int* b, const int* c) +{ + return area2(a, b, c) == 0; +} + +// Returns true iff ab properly intersects cd: they share +// a point interior to both segments. The properness of the +// intersection is ensured by using strict leftness. +static bool intersectProp(const int* a, const int* b, const int* c, const int* d) +{ + // Eliminate improper cases. + if (collinear(a,b,c) || collinear(a,b,d) || + collinear(c,d,a) || collinear(c,d,b)) + return false; + + return xorb(left(a,b,c), left(a,b,d)) && xorb(left(c,d,a), left(c,d,b)); +} + +// Returns T iff (a,b,c) are collinear and point c lies +// on the closed segement ab. +static bool between(const int* a, const int* b, const int* c) +{ + if (!collinear(a, b, c)) + return false; + // If ab not vertical, check betweenness on x; else on y. + if (a[0] != b[0]) + return ((a[0] <= c[0]) && (c[0] <= b[0])) || ((a[0] >= c[0]) && (c[0] >= b[0])); + else + return ((a[2] <= c[2]) && (c[2] <= b[2])) || ((a[2] >= c[2]) && (c[2] >= b[2])); +} + +// Returns true iff segments ab and cd intersect, properly or improperly. +static bool intersect(const int* a, const int* b, const int* c, const int* d) +{ + if (intersectProp(a, b, c, d)) + return true; + else if (between(a, b, c) || between(a, b, d) || + between(c, d, a) || between(c, d, b)) + return true; + else + return false; +} + +static bool vequal(const int* a, const int* b) +{ + return a[0] == b[0] && a[2] == b[2]; +} + +// Returns T iff (v_i, v_j) is a proper internal *or* external +// diagonal of P, *ignoring edges incident to v_i and v_j*. +static bool diagonalie(int i, int j, int n, const int* verts, int* indices) +{ + const int* d0 = &verts[(indices[i] & 0x0fffffff) * 4]; + const int* d1 = &verts[(indices[j] & 0x0fffffff) * 4]; + + // For each edge (k,k+1) of P + for (int k = 0; k < n; k++) + { + int k1 = next(k, n); + // Skip edges incident to i or j + if (!((k == i) || (k1 == i) || (k == j) || (k1 == j))) + { + const int* p0 = &verts[(indices[k] & 0x0fffffff) * 4]; + const int* p1 = &verts[(indices[k1] & 0x0fffffff) * 4]; + + if (vequal(d0, p0) || vequal(d1, p0) || vequal(d0, p1) || vequal(d1, p1)) + continue; + + if (intersect(d0, d1, p0, p1)) + return false; + } + } + return true; +} + +// Returns true iff the diagonal (i,j) is strictly internal to the +// polygon P in the neighborhood of the i endpoint. +static bool inCone(int i, int j, int n, const int* verts, int* indices) +{ + const int* pi = &verts[(indices[i] & 0x0fffffff) * 4]; + const int* pj = &verts[(indices[j] & 0x0fffffff) * 4]; + const int* pi1 = &verts[(indices[next(i, n)] & 0x0fffffff) * 4]; + const int* pin1 = &verts[(indices[prev(i, n)] & 0x0fffffff) * 4]; + + // If P[i] is a convex vertex [ i+1 left or on (i-1,i) ]. + if (leftOn(pin1, pi, pi1)) + return left(pi, pj, pin1) && left(pj, pi, pi1); + // Assume (i-1,i,i+1) not collinear. + // else P[i] is reflex. + return !(leftOn(pi, pj, pi1) && leftOn(pj, pi, pin1)); +} + +// Returns T iff (v_i, v_j) is a proper internal +// diagonal of P. +static bool diagonal(int i, int j, int n, const int* verts, int* indices) +{ + return inCone(i, j, n, verts, indices) && diagonalie(i, j, n, verts, indices); +} + +static int triangulate(int n, const int* verts, int* indices, int* tris) +{ + int ntris = 0; + int* dst = tris; + + // The last bit of the index is used to indicate if the vertex can be removed. + for (int i = 0; i < n; i++) + { + int i1 = next(i, n); + int i2 = next(i1, n); + if (diagonal(i, i2, n, verts, indices)) + indices[i1] |= 0x80000000; + } + + while (n > 3) + { + int minLen = -1; + int mini = -1; + for (int i = 0; i < n; i++) + { + int i1 = next(i, n); + if (indices[i1] & 0x80000000) + { + const int* p0 = &verts[(indices[i] & 0x0fffffff) * 4]; + const int* p2 = &verts[(indices[next(i1, n)] & 0x0fffffff) * 4]; + + int dx = p2[0] - p0[0]; + int dy = p2[2] - p0[2]; + int len = dx*dx + dy*dy; + + if (minLen < 0 || len < minLen) + { + minLen = len; + mini = i; + } + } + } + + if (mini == -1) + { + // Should not happen. +/* printf("mini == -1 ntris=%d n=%d\n", ntris, n); + for (int i = 0; i < n; i++) + { + printf("%d ", indices[i] & 0x0fffffff); + } + printf("\n");*/ + return -ntris; + } + + int i = mini; + int i1 = next(i, n); + int i2 = next(i1, n); + + *dst++ = indices[i] & 0x0fffffff; + *dst++ = indices[i1] & 0x0fffffff; + *dst++ = indices[i2] & 0x0fffffff; + ntris++; + + // Removes P[i1] by copying P[i+1]...P[n-1] left one index. + n--; + for (int k = i1; k < n; k++) + indices[k] = indices[k+1]; + + if (i1 >= n) i1 = 0; + i = prev(i1,n); + // Update diagonal flags. + if (diagonal(prev(i, n), i1, n, verts, indices)) + indices[i] |= 0x80000000; + else + indices[i] &= 0x0fffffff; + + if (diagonal(i, next(i1, n), n, verts, indices)) + indices[i1] |= 0x80000000; + else + indices[i1] &= 0x0fffffff; + } + + // Append the remaining triangle. + *dst++ = indices[0] & 0x0fffffff; + *dst++ = indices[1] & 0x0fffffff; + *dst++ = indices[2] & 0x0fffffff; + ntris++; + + return ntris; +} + +static int countPolyVerts(const unsigned short* p, const int nvp) +{ + for (int i = 0; i < nvp; ++i) + if (p[i] == RC_MESH_NULL_IDX) + return i; + return nvp; +} + +inline bool uleft(const unsigned short* a, const unsigned short* b, const unsigned short* c) +{ + return ((int)b[0] - (int)a[0]) * ((int)c[2] - (int)a[2]) - + ((int)c[0] - (int)a[0]) * ((int)b[2] - (int)a[2]) < 0; +} + +static int getPolyMergeValue(unsigned short* pa, unsigned short* pb, + const unsigned short* verts, int& ea, int& eb, + const int nvp) +{ + const int na = countPolyVerts(pa, nvp); + const int nb = countPolyVerts(pb, nvp); + + // If the merged polygon would be too big, do not merge. + if (na+nb-2 > nvp) + return -1; + + // Check if the polygons share an edge. + ea = -1; + eb = -1; + + for (int i = 0; i < na; ++i) + { + unsigned short va0 = pa[i]; + unsigned short va1 = pa[(i+1) % na]; + if (va0 > va1) + rcSwap(va0, va1); + for (int j = 0; j < nb; ++j) + { + unsigned short vb0 = pb[j]; + unsigned short vb1 = pb[(j+1) % nb]; + if (vb0 > vb1) + rcSwap(vb0, vb1); + if (va0 == vb0 && va1 == vb1) + { + ea = i; + eb = j; + break; + } + } + } + + // No common edge, cannot merge. + if (ea == -1 || eb == -1) + return -1; + + // Check to see if the merged polygon would be convex. + unsigned short va, vb, vc; + + va = pa[(ea+na-1) % na]; + vb = pa[ea]; + vc = pb[(eb+2) % nb]; + if (!uleft(&verts[va*3], &verts[vb*3], &verts[vc*3])) + return -1; + + va = pb[(eb+nb-1) % nb]; + vb = pb[eb]; + vc = pa[(ea+2) % na]; + if (!uleft(&verts[va*3], &verts[vb*3], &verts[vc*3])) + return -1; + + va = pa[ea]; + vb = pa[(ea+1)%na]; + + int dx = (int)verts[va*3+0] - (int)verts[vb*3+0]; + int dy = (int)verts[va*3+2] - (int)verts[vb*3+2]; + + return dx*dx + dy*dy; +} + +static void mergePolys(unsigned short* pa, unsigned short* pb, int ea, int eb, + unsigned short* tmp, const int nvp) +{ + const int na = countPolyVerts(pa, nvp); + const int nb = countPolyVerts(pb, nvp); + + // Merge polygons. + memset(tmp, 0xff, sizeof(unsigned short)*nvp); + int n = 0; + // Add pa + for (int i = 0; i < na-1; ++i) + tmp[n++] = pa[(ea+1+i) % na]; + // Add pb + for (int i = 0; i < nb-1; ++i) + tmp[n++] = pb[(eb+1+i) % nb]; + + memcpy(pa, tmp, sizeof(unsigned short)*nvp); +} + + +static void pushFront(int v, int* arr, int& an) +{ + an++; + for (int i = an-1; i > 0; --i) arr[i] = arr[i-1]; + arr[0] = v; +} + +static void pushBack(int v, int* arr, int& an) +{ + arr[an] = v; + an++; +} + +static bool canRemoveVertex(rcContext* ctx, rcPolyMesh& mesh, const unsigned short rem) +{ + const int nvp = mesh.nvp; + + // Count number of polygons to remove. + int numRemovedVerts = 0; + int numTouchedVerts = 0; + int numRemainingEdges = 0; + for (int i = 0; i < mesh.npolys; ++i) + { + unsigned short* p = &mesh.polys[i*nvp*2]; + const int nv = countPolyVerts(p, nvp); + int numRemoved = 0; + int numVerts = 0; + for (int j = 0; j < nv; ++j) + { + if (p[j] == rem) + { + numTouchedVerts++; + numRemoved++; + } + numVerts++; + } + if (numRemoved) + { + numRemovedVerts += numRemoved; + numRemainingEdges += numVerts-(numRemoved+1); + } + } + + // There would be too few edges remaining to create a polygon. + // This can happen for example when a tip of a triangle is marked + // as deletion, but there are no other polys that share the vertex. + // In this case, the vertex should not be removed. + if (numRemainingEdges <= 2) + return false; + + // Find edges which share the removed vertex. + const int maxEdges = numTouchedVerts*2; + int nedges = 0; + rcScopedDelete edges = (int*)rcAlloc(sizeof(int)*maxEdges*3, RC_ALLOC_TEMP); + if (!edges) + { + ctx->log(RC_LOG_WARNING, "canRemoveVertex: Out of memory 'edges' (%d).", maxEdges*3); + return false; + } + + for (int i = 0; i < mesh.npolys; ++i) + { + unsigned short* p = &mesh.polys[i*nvp*2]; + const int nv = countPolyVerts(p, nvp); + + // Collect edges which touches the removed vertex. + for (int j = 0, k = nv-1; j < nv; k = j++) + { + if (p[j] == rem || p[k] == rem) + { + // Arrange edge so that a=rem. + int a = p[j], b = p[k]; + if (b == rem) + rcSwap(a,b); + + // Check if the edge exists + bool exists = false; + for (int m = 0; m < nedges; ++m) + { + int* e = &edges[m*3]; + if (e[1] == b) + { + // Exists, increment vertex share count. + e[2]++; + exists = true; + } + } + // Add new edge. + if (!exists) + { + int* e = &edges[nedges*3]; + e[0] = a; + e[1] = b; + e[2] = 1; + nedges++; + } + } + } + } + + // There should be no more than 2 open edges. + // This catches the case that two non-adjacent polygons + // share the removed vertex. In that case, do not remove the vertex. + int numOpenEdges = 0; + for (int i = 0; i < nedges; ++i) + { + if (edges[i*3+2] < 2) + numOpenEdges++; + } + if (numOpenEdges > 2) + return false; + + return true; +} + +static bool removeVertex(rcContext* ctx, rcPolyMesh& mesh, const unsigned short rem, const int maxTris) +{ + const int nvp = mesh.nvp; + + // Count number of polygons to remove. + int numRemovedVerts = 0; + for (int i = 0; i < mesh.npolys; ++i) + { + unsigned short* p = &mesh.polys[i*nvp*2]; + const int nv = countPolyVerts(p, nvp); + for (int j = 0; j < nv; ++j) + { + if (p[j] == rem) + numRemovedVerts++; + } + } + + int nedges = 0; + rcScopedDelete edges = (int*)rcAlloc(sizeof(int)*numRemovedVerts*nvp*4, RC_ALLOC_TEMP); + if (!edges) + { + ctx->log(RC_LOG_WARNING, "removeVertex: Out of memory 'edges' (%d).", numRemovedVerts*nvp*4); + return false; + } + + int nhole = 0; + rcScopedDelete hole = (int*)rcAlloc(sizeof(int)*numRemovedVerts*nvp, RC_ALLOC_TEMP); + if (!hole) + { + ctx->log(RC_LOG_WARNING, "removeVertex: Out of memory 'hole' (%d).", numRemovedVerts*nvp); + return false; + } + + int nhreg = 0; + rcScopedDelete hreg = (int*)rcAlloc(sizeof(int)*numRemovedVerts*nvp, RC_ALLOC_TEMP); + if (!hreg) + { + ctx->log(RC_LOG_WARNING, "removeVertex: Out of memory 'hreg' (%d).", numRemovedVerts*nvp); + return false; + } + + int nharea = 0; + rcScopedDelete harea = (int*)rcAlloc(sizeof(int)*numRemovedVerts*nvp, RC_ALLOC_TEMP); + if (!harea) + { + ctx->log(RC_LOG_WARNING, "removeVertex: Out of memory 'harea' (%d).", numRemovedVerts*nvp); + return false; + } + + for (int i = 0; i < mesh.npolys; ++i) + { + unsigned short* p = &mesh.polys[i*nvp*2]; + const int nv = countPolyVerts(p, nvp); + bool hasRem = false; + for (int j = 0; j < nv; ++j) + if (p[j] == rem) hasRem = true; + if (hasRem) + { + // Collect edges which does not touch the removed vertex. + for (int j = 0, k = nv-1; j < nv; k = j++) + { + if (p[j] != rem && p[k] != rem) + { + int* e = &edges[nedges*4]; + e[0] = p[k]; + e[1] = p[j]; + e[2] = mesh.regs[i]; + e[3] = mesh.areas[i]; + nedges++; + } + } + // Remove the polygon. + unsigned short* p2 = &mesh.polys[(mesh.npolys-1)*nvp*2]; + if (p != p2) + memcpy(p,p2,sizeof(unsigned short)*nvp); + memset(p+nvp,0xff,sizeof(unsigned short)*nvp); + mesh.regs[i] = mesh.regs[mesh.npolys-1]; + mesh.areas[i] = mesh.areas[mesh.npolys-1]; + mesh.npolys--; + --i; + } + } + + // Remove vertex. + for (int i = (int)rem; i < mesh.nverts; ++i) + { + mesh.verts[i*3+0] = mesh.verts[(i+1)*3+0]; + mesh.verts[i*3+1] = mesh.verts[(i+1)*3+1]; + mesh.verts[i*3+2] = mesh.verts[(i+1)*3+2]; + } + mesh.nverts--; + + // Adjust indices to match the removed vertex layout. + for (int i = 0; i < mesh.npolys; ++i) + { + unsigned short* p = &mesh.polys[i*nvp*2]; + const int nv = countPolyVerts(p, nvp); + for (int j = 0; j < nv; ++j) + if (p[j] > rem) p[j]--; + } + for (int i = 0; i < nedges; ++i) + { + if (edges[i*4+0] > rem) edges[i*4+0]--; + if (edges[i*4+1] > rem) edges[i*4+1]--; + } + + if (nedges == 0) + return true; + + // Start with one vertex, keep appending connected + // segments to the start and end of the hole. + pushBack(edges[0], hole, nhole); + pushBack(edges[2], hreg, nhreg); + pushBack(edges[3], harea, nharea); + + while (nedges) + { + bool match = false; + + for (int i = 0; i < nedges; ++i) + { + const int ea = edges[i*4+0]; + const int eb = edges[i*4+1]; + const int r = edges[i*4+2]; + const int a = edges[i*4+3]; + bool add = false; + if (hole[0] == eb) + { + // The segment matches the beginning of the hole boundary. + pushFront(ea, hole, nhole); + pushFront(r, hreg, nhreg); + pushFront(a, harea, nharea); + add = true; + } + else if (hole[nhole-1] == ea) + { + // The segment matches the end of the hole boundary. + pushBack(eb, hole, nhole); + pushBack(r, hreg, nhreg); + pushBack(a, harea, nharea); + add = true; + } + if (add) + { + // The edge segment was added, remove it. + edges[i*4+0] = edges[(nedges-1)*4+0]; + edges[i*4+1] = edges[(nedges-1)*4+1]; + edges[i*4+2] = edges[(nedges-1)*4+2]; + edges[i*4+3] = edges[(nedges-1)*4+3]; + --nedges; + match = true; + --i; + } + } + + if (!match) + break; + } + + rcScopedDelete tris = (int*)rcAlloc(sizeof(int)*nhole*3, RC_ALLOC_TEMP); + if (!tris) + { + ctx->log(RC_LOG_WARNING, "removeVertex: Out of memory 'tris' (%d).", nhole*3); + return false; + } + + rcScopedDelete tverts = (int*)rcAlloc(sizeof(int)*nhole*4, RC_ALLOC_TEMP); + if (!tverts) + { + ctx->log(RC_LOG_WARNING, "removeVertex: Out of memory 'tverts' (%d).", nhole*4); + return false; + } + + rcScopedDelete thole = (int*)rcAlloc(sizeof(int)*nhole, RC_ALLOC_TEMP); + if (!tverts) + { + ctx->log(RC_LOG_WARNING, "removeVertex: Out of memory 'thole' (%d).", nhole); + return false; + } + + // Generate temp vertex array for triangulation. + for (int i = 0; i < nhole; ++i) + { + const int pi = hole[i]; + tverts[i*4+0] = mesh.verts[pi*3+0]; + tverts[i*4+1] = mesh.verts[pi*3+1]; + tverts[i*4+2] = mesh.verts[pi*3+2]; + tverts[i*4+3] = 0; + thole[i] = i; + } + + // Triangulate the hole. + int ntris = triangulate(nhole, &tverts[0], &thole[0], tris); + if (ntris < 0) + { + ntris = -ntris; + ctx->log(RC_LOG_WARNING, "removeVertex: triangulate() returned bad results."); + } + + // Merge the hole triangles back to polygons. + rcScopedDelete polys = (unsigned short*)rcAlloc(sizeof(unsigned short)*(ntris+1)*nvp, RC_ALLOC_TEMP); + if (!polys) + { + ctx->log(RC_LOG_ERROR, "removeVertex: Out of memory 'polys' (%d).", (ntris+1)*nvp); + return false; + } + rcScopedDelete pregs = (unsigned short*)rcAlloc(sizeof(unsigned short)*ntris, RC_ALLOC_TEMP); + if (!pregs) + { + ctx->log(RC_LOG_ERROR, "removeVertex: Out of memory 'pregs' (%d).", ntris); + return false; + } + rcScopedDelete pareas = (unsigned char*)rcAlloc(sizeof(unsigned char)*ntris, RC_ALLOC_TEMP); + if (!pregs) + { + ctx->log(RC_LOG_ERROR, "removeVertex: Out of memory 'pareas' (%d).", ntris); + return false; + } + + unsigned short* tmpPoly = &polys[ntris*nvp]; + + // Build initial polygons. + int npolys = 0; + memset(polys, 0xff, ntris*nvp*sizeof(unsigned short)); + for (int j = 0; j < ntris; ++j) + { + int* t = &tris[j*3]; + if (t[0] != t[1] && t[0] != t[2] && t[1] != t[2]) + { + polys[npolys*nvp+0] = (unsigned short)hole[t[0]]; + polys[npolys*nvp+1] = (unsigned short)hole[t[1]]; + polys[npolys*nvp+2] = (unsigned short)hole[t[2]]; + pregs[npolys] = (unsigned short)hreg[t[0]]; + pareas[npolys] = (unsigned char)harea[t[0]]; + npolys++; + } + } + if (!npolys) + return true; + + // Merge polygons. + if (nvp > 3) + { + for (;;) + { + // Find best polygons to merge. + int bestMergeVal = 0; + int bestPa = 0, bestPb = 0, bestEa = 0, bestEb = 0; + + for (int j = 0; j < npolys-1; ++j) + { + unsigned short* pj = &polys[j*nvp]; + for (int k = j+1; k < npolys; ++k) + { + unsigned short* pk = &polys[k*nvp]; + int ea, eb; + int v = getPolyMergeValue(pj, pk, mesh.verts, ea, eb, nvp); + if (v > bestMergeVal) + { + bestMergeVal = v; + bestPa = j; + bestPb = k; + bestEa = ea; + bestEb = eb; + } + } + } + + if (bestMergeVal > 0) + { + // Found best, merge. + unsigned short* pa = &polys[bestPa*nvp]; + unsigned short* pb = &polys[bestPb*nvp]; + mergePolys(pa, pb, bestEa, bestEb, tmpPoly, nvp); + unsigned short* last = &polys[(npolys-1)*nvp]; + if (pb != last) + memcpy(pb, last, sizeof(unsigned short)*nvp); + pregs[bestPb] = pregs[npolys-1]; + pareas[bestPb] = pareas[npolys-1]; + npolys--; + } + else + { + // Could not merge any polygons, stop. + break; + } + } + } + + // Store polygons. + for (int i = 0; i < npolys; ++i) + { + if (mesh.npolys >= maxTris) break; + unsigned short* p = &mesh.polys[mesh.npolys*nvp*2]; + memset(p,0xff,sizeof(unsigned short)*nvp*2); + for (int j = 0; j < nvp; ++j) + p[j] = polys[i*nvp+j]; + mesh.regs[mesh.npolys] = pregs[i]; + mesh.areas[mesh.npolys] = pareas[i]; + mesh.npolys++; + if (mesh.npolys > maxTris) + { + ctx->log(RC_LOG_ERROR, "removeVertex: Too many polygons %d (max:%d).", mesh.npolys, maxTris); + return false; + } + } + + return true; +} + +/// @par +/// +/// @note If the mesh data is to be used to construct a Detour navigation mesh, then the upper +/// limit must be retricted to <= #DT_VERTS_PER_POLYGON. +/// +/// @see rcAllocPolyMesh, rcContourSet, rcPolyMesh, rcConfig +bool rcBuildPolyMesh(rcContext* ctx, rcContourSet& cset, const int nvp, rcPolyMesh& mesh) +{ + rcAssert(ctx); + + ctx->startTimer(RC_TIMER_BUILD_POLYMESH); + + rcVcopy(mesh.bmin, cset.bmin); + rcVcopy(mesh.bmax, cset.bmax); + mesh.cs = cset.cs; + mesh.ch = cset.ch; + mesh.borderSize = cset.borderSize; + + int maxVertices = 0; + int maxTris = 0; + int maxVertsPerCont = 0; + for (int i = 0; i < cset.nconts; ++i) + { + // Skip null contours. + if (cset.conts[i].nverts < 3) continue; + maxVertices += cset.conts[i].nverts; + maxTris += cset.conts[i].nverts - 2; + maxVertsPerCont = rcMax(maxVertsPerCont, cset.conts[i].nverts); + } + + if (maxVertices >= 0xfffe) + { + ctx->log(RC_LOG_ERROR, "rcBuildPolyMesh: Too many vertices %d.", maxVertices); + return false; + } + + rcScopedDelete vflags = (unsigned char*)rcAlloc(sizeof(unsigned char)*maxVertices, RC_ALLOC_TEMP); + if (!vflags) + { + ctx->log(RC_LOG_ERROR, "rcBuildPolyMesh: Out of memory 'vflags' (%d).", maxVertices); + return false; + } + memset(vflags, 0, maxVertices); + + mesh.verts = (unsigned short*)rcAlloc(sizeof(unsigned short)*maxVertices*3, RC_ALLOC_PERM); + if (!mesh.verts) + { + ctx->log(RC_LOG_ERROR, "rcBuildPolyMesh: Out of memory 'mesh.verts' (%d).", maxVertices); + return false; + } + mesh.polys = (unsigned short*)rcAlloc(sizeof(unsigned short)*maxTris*nvp*2, RC_ALLOC_PERM); + if (!mesh.polys) + { + ctx->log(RC_LOG_ERROR, "rcBuildPolyMesh: Out of memory 'mesh.polys' (%d).", maxTris*nvp*2); + return false; + } + mesh.regs = (unsigned short*)rcAlloc(sizeof(unsigned short)*maxTris, RC_ALLOC_PERM); + if (!mesh.regs) + { + ctx->log(RC_LOG_ERROR, "rcBuildPolyMesh: Out of memory 'mesh.regs' (%d).", maxTris); + return false; + } + mesh.areas = (unsigned char*)rcAlloc(sizeof(unsigned char)*maxTris, RC_ALLOC_PERM); + if (!mesh.areas) + { + ctx->log(RC_LOG_ERROR, "rcBuildPolyMesh: Out of memory 'mesh.areas' (%d).", maxTris); + return false; + } + + mesh.nverts = 0; + mesh.npolys = 0; + mesh.nvp = nvp; + mesh.maxpolys = maxTris; + + memset(mesh.verts, 0, sizeof(unsigned short)*maxVertices*3); + memset(mesh.polys, 0xff, sizeof(unsigned short)*maxTris*nvp*2); + memset(mesh.regs, 0, sizeof(unsigned short)*maxTris); + memset(mesh.areas, 0, sizeof(unsigned char)*maxTris); + + rcScopedDelete nextVert = (int*)rcAlloc(sizeof(int)*maxVertices, RC_ALLOC_TEMP); + if (!nextVert) + { + ctx->log(RC_LOG_ERROR, "rcBuildPolyMesh: Out of memory 'nextVert' (%d).", maxVertices); + return false; + } + memset(nextVert, 0, sizeof(int)*maxVertices); + + rcScopedDelete firstVert = (int*)rcAlloc(sizeof(int)*VERTEX_BUCKET_COUNT, RC_ALLOC_TEMP); + if (!firstVert) + { + ctx->log(RC_LOG_ERROR, "rcBuildPolyMesh: Out of memory 'firstVert' (%d).", VERTEX_BUCKET_COUNT); + return false; + } + for (int i = 0; i < VERTEX_BUCKET_COUNT; ++i) + firstVert[i] = -1; + + rcScopedDelete indices = (int*)rcAlloc(sizeof(int)*maxVertsPerCont, RC_ALLOC_TEMP); + if (!indices) + { + ctx->log(RC_LOG_ERROR, "rcBuildPolyMesh: Out of memory 'indices' (%d).", maxVertsPerCont); + return false; + } + rcScopedDelete tris = (int*)rcAlloc(sizeof(int)*maxVertsPerCont*3, RC_ALLOC_TEMP); + if (!tris) + { + ctx->log(RC_LOG_ERROR, "rcBuildPolyMesh: Out of memory 'tris' (%d).", maxVertsPerCont*3); + return false; + } + rcScopedDelete polys = (unsigned short*)rcAlloc(sizeof(unsigned short)*(maxVertsPerCont+1)*nvp, RC_ALLOC_TEMP); + if (!polys) + { + ctx->log(RC_LOG_ERROR, "rcBuildPolyMesh: Out of memory 'polys' (%d).", maxVertsPerCont*nvp); + return false; + } + unsigned short* tmpPoly = &polys[maxVertsPerCont*nvp]; + + for (int i = 0; i < cset.nconts; ++i) + { + rcContour& cont = cset.conts[i]; + + // Skip null contours. + if (cont.nverts < 3) + continue; + + // Triangulate contour + for (int j = 0; j < cont.nverts; ++j) + indices[j] = j; + + int ntris = triangulate(cont.nverts, cont.verts, &indices[0], &tris[0]); + if (ntris <= 0) + { + // Bad triangulation, should not happen. +/* printf("\tconst float bmin[3] = {%ff,%ff,%ff};\n", cset.bmin[0], cset.bmin[1], cset.bmin[2]); + printf("\tconst float cs = %ff;\n", cset.cs); + printf("\tconst float ch = %ff;\n", cset.ch); + printf("\tconst int verts[] = {\n"); + for (int k = 0; k < cont.nverts; ++k) + { + const int* v = &cont.verts[k*4]; + printf("\t\t%d,%d,%d,%d,\n", v[0], v[1], v[2], v[3]); + } + printf("\t};\n\tconst int nverts = sizeof(verts)/(sizeof(int)*4);\n");*/ + ctx->log(RC_LOG_WARNING, "rcBuildPolyMesh: Bad triangulation Contour %d.", i); + ntris = -ntris; + } + + // Add and merge vertices. + for (int j = 0; j < cont.nverts; ++j) + { + const int* v = &cont.verts[j*4]; + indices[j] = addVertex((unsigned short)v[0], (unsigned short)v[1], (unsigned short)v[2], + mesh.verts, firstVert, nextVert, mesh.nverts); + if (v[3] & RC_BORDER_VERTEX) + { + // This vertex should be removed. + vflags[indices[j]] = 1; + } + } + + // Build initial polygons. + int npolys = 0; + memset(polys, 0xff, maxVertsPerCont*nvp*sizeof(unsigned short)); + for (int j = 0; j < ntris; ++j) + { + int* t = &tris[j*3]; + if (t[0] != t[1] && t[0] != t[2] && t[1] != t[2]) + { + polys[npolys*nvp+0] = (unsigned short)indices[t[0]]; + polys[npolys*nvp+1] = (unsigned short)indices[t[1]]; + polys[npolys*nvp+2] = (unsigned short)indices[t[2]]; + npolys++; + } + } + if (!npolys) + continue; + + // Merge polygons. + if (nvp > 3) + { + for(;;) + { + // Find best polygons to merge. + int bestMergeVal = 0; + int bestPa = 0, bestPb = 0, bestEa = 0, bestEb = 0; + + for (int j = 0; j < npolys-1; ++j) + { + unsigned short* pj = &polys[j*nvp]; + for (int k = j+1; k < npolys; ++k) + { + unsigned short* pk = &polys[k*nvp]; + int ea, eb; + int v = getPolyMergeValue(pj, pk, mesh.verts, ea, eb, nvp); + if (v > bestMergeVal) + { + bestMergeVal = v; + bestPa = j; + bestPb = k; + bestEa = ea; + bestEb = eb; + } + } + } + + if (bestMergeVal > 0) + { + // Found best, merge. + unsigned short* pa = &polys[bestPa*nvp]; + unsigned short* pb = &polys[bestPb*nvp]; + mergePolys(pa, pb, bestEa, bestEb, tmpPoly, nvp); + unsigned short* lastPoly = &polys[(npolys-1)*nvp]; + if (pb != lastPoly) + memcpy(pb, lastPoly, sizeof(unsigned short)*nvp); + npolys--; + } + else + { + // Could not merge any polygons, stop. + break; + } + } + } + + // Store polygons. + for (int j = 0; j < npolys; ++j) + { + unsigned short* p = &mesh.polys[mesh.npolys*nvp*2]; + unsigned short* q = &polys[j*nvp]; + for (int k = 0; k < nvp; ++k) + p[k] = q[k]; + mesh.regs[mesh.npolys] = cont.reg; + mesh.areas[mesh.npolys] = cont.area; + mesh.npolys++; + if (mesh.npolys > maxTris) + { + ctx->log(RC_LOG_ERROR, "rcBuildPolyMesh: Too many polygons %d (max:%d).", mesh.npolys, maxTris); + return false; + } + } + } + + + // Remove edge vertices. + for (int i = 0; i < mesh.nverts; ++i) + { + if (vflags[i]) + { + if (!canRemoveVertex(ctx, mesh, (unsigned short)i)) + continue; + if (!removeVertex(ctx, mesh, (unsigned short)i, maxTris)) + { + // Failed to remove vertex + ctx->log(RC_LOG_ERROR, "rcBuildPolyMesh: Failed to remove edge vertex %d.", i); + return false; + } + // Remove vertex + // Note: mesh.nverts is already decremented inside removeVertex()! + // Fixup vertex flags + for (int j = i; j < mesh.nverts; ++j) + vflags[j] = vflags[j+1]; + --i; + } + } + + // Calculate adjacency. + if (!buildMeshAdjacency(mesh.polys, mesh.npolys, mesh.nverts, nvp)) + { + ctx->log(RC_LOG_ERROR, "rcBuildPolyMesh: Adjacency failed."); + return false; + } + + // Find portal edges + if (mesh.borderSize > 0) + { + const int w = cset.width; + const int h = cset.height; + for (int i = 0; i < mesh.npolys; ++i) + { + unsigned short* p = &mesh.polys[i*2*nvp]; + for (int j = 0; j < nvp; ++j) + { + if (p[j] == RC_MESH_NULL_IDX) break; + // Skip connected edges. + if (p[nvp+j] != RC_MESH_NULL_IDX) + continue; + int nj = j+1; + if (nj >= nvp || p[nj] == RC_MESH_NULL_IDX) nj = 0; + const unsigned short* va = &mesh.verts[p[j]*3]; + const unsigned short* vb = &mesh.verts[p[nj]*3]; + + if ((int)va[0] == 0 && (int)vb[0] == 0) + p[nvp+j] = 0x8000 | 0; + else if ((int)va[2] == h && (int)vb[2] == h) + p[nvp+j] = 0x8000 | 1; + else if ((int)va[0] == w && (int)vb[0] == w) + p[nvp+j] = 0x8000 | 2; + else if ((int)va[2] == 0 && (int)vb[2] == 0) + p[nvp+j] = 0x8000 | 3; + } + } + } + + // Just allocate the mesh flags array. The user is resposible to fill it. + mesh.flags = (unsigned short*)rcAlloc(sizeof(unsigned short)*mesh.npolys, RC_ALLOC_PERM); + if (!mesh.flags) + { + ctx->log(RC_LOG_ERROR, "rcBuildPolyMesh: Out of memory 'mesh.flags' (%d).", mesh.npolys); + return false; + } + memset(mesh.flags, 0, sizeof(unsigned short) * mesh.npolys); + + if (mesh.nverts > 0xffff) + { + ctx->log(RC_LOG_ERROR, "rcBuildPolyMesh: The resulting mesh has too many vertices %d (max %d). Data can be corrupted.", mesh.nverts, 0xffff); + } + if (mesh.npolys > 0xffff) + { + ctx->log(RC_LOG_ERROR, "rcBuildPolyMesh: The resulting mesh has too many polygons %d (max %d). Data can be corrupted.", mesh.npolys, 0xffff); + } + + ctx->stopTimer(RC_TIMER_BUILD_POLYMESH); + + return true; +} + +/// @see rcAllocPolyMesh, rcPolyMesh +bool rcMergePolyMeshes(rcContext* ctx, rcPolyMesh** meshes, const int nmeshes, rcPolyMesh& mesh) +{ + rcAssert(ctx); + + if (!nmeshes || !meshes) + return true; + + ctx->startTimer(RC_TIMER_MERGE_POLYMESH); + + mesh.nvp = meshes[0]->nvp; + mesh.cs = meshes[0]->cs; + mesh.ch = meshes[0]->ch; + rcVcopy(mesh.bmin, meshes[0]->bmin); + rcVcopy(mesh.bmax, meshes[0]->bmax); + + int maxVerts = 0; + int maxPolys = 0; + int maxVertsPerMesh = 0; + for (int i = 0; i < nmeshes; ++i) + { + rcVmin(mesh.bmin, meshes[i]->bmin); + rcVmax(mesh.bmax, meshes[i]->bmax); + maxVertsPerMesh = rcMax(maxVertsPerMesh, meshes[i]->nverts); + maxVerts += meshes[i]->nverts; + maxPolys += meshes[i]->npolys; + } + + mesh.nverts = 0; + mesh.verts = (unsigned short*)rcAlloc(sizeof(unsigned short)*maxVerts*3, RC_ALLOC_PERM); + if (!mesh.verts) + { + ctx->log(RC_LOG_ERROR, "rcMergePolyMeshes: Out of memory 'mesh.verts' (%d).", maxVerts*3); + return false; + } + + mesh.npolys = 0; + mesh.polys = (unsigned short*)rcAlloc(sizeof(unsigned short)*maxPolys*2*mesh.nvp, RC_ALLOC_PERM); + if (!mesh.polys) + { + ctx->log(RC_LOG_ERROR, "rcMergePolyMeshes: Out of memory 'mesh.polys' (%d).", maxPolys*2*mesh.nvp); + return false; + } + memset(mesh.polys, 0xff, sizeof(unsigned short)*maxPolys*2*mesh.nvp); + + mesh.regs = (unsigned short*)rcAlloc(sizeof(unsigned short)*maxPolys, RC_ALLOC_PERM); + if (!mesh.regs) + { + ctx->log(RC_LOG_ERROR, "rcMergePolyMeshes: Out of memory 'mesh.regs' (%d).", maxPolys); + return false; + } + memset(mesh.regs, 0, sizeof(unsigned short)*maxPolys); + + mesh.areas = (unsigned char*)rcAlloc(sizeof(unsigned char)*maxPolys, RC_ALLOC_PERM); + if (!mesh.areas) + { + ctx->log(RC_LOG_ERROR, "rcMergePolyMeshes: Out of memory 'mesh.areas' (%d).", maxPolys); + return false; + } + memset(mesh.areas, 0, sizeof(unsigned char)*maxPolys); + + mesh.flags = (unsigned short*)rcAlloc(sizeof(unsigned short)*maxPolys, RC_ALLOC_PERM); + if (!mesh.flags) + { + ctx->log(RC_LOG_ERROR, "rcMergePolyMeshes: Out of memory 'mesh.flags' (%d).", maxPolys); + return false; + } + memset(mesh.flags, 0, sizeof(unsigned short)*maxPolys); + + rcScopedDelete nextVert = (int*)rcAlloc(sizeof(int)*maxVerts, RC_ALLOC_TEMP); + if (!nextVert) + { + ctx->log(RC_LOG_ERROR, "rcMergePolyMeshes: Out of memory 'nextVert' (%d).", maxVerts); + return false; + } + memset(nextVert, 0, sizeof(int)*maxVerts); + + rcScopedDelete firstVert = (int*)rcAlloc(sizeof(int)*VERTEX_BUCKET_COUNT, RC_ALLOC_TEMP); + if (!firstVert) + { + ctx->log(RC_LOG_ERROR, "rcMergePolyMeshes: Out of memory 'firstVert' (%d).", VERTEX_BUCKET_COUNT); + return false; + } + for (int i = 0; i < VERTEX_BUCKET_COUNT; ++i) + firstVert[i] = -1; + + rcScopedDelete vremap = (unsigned short*)rcAlloc(sizeof(unsigned short)*maxVertsPerMesh, RC_ALLOC_PERM); + if (!vremap) + { + ctx->log(RC_LOG_ERROR, "rcMergePolyMeshes: Out of memory 'vremap' (%d).", maxVertsPerMesh); + return false; + } + memset(vremap, 0, sizeof(unsigned short)*maxVertsPerMesh); + + for (int i = 0; i < nmeshes; ++i) + { + const rcPolyMesh* pmesh = meshes[i]; + + const unsigned short ox = (unsigned short)floorf((pmesh->bmin[0]-mesh.bmin[0])/mesh.cs+0.5f); + const unsigned short oz = (unsigned short)floorf((pmesh->bmin[2]-mesh.bmin[2])/mesh.cs+0.5f); + + for (int j = 0; j < pmesh->nverts; ++j) + { + unsigned short* v = &pmesh->verts[j*3]; + vremap[j] = addVertex(v[0]+ox, v[1], v[2]+oz, + mesh.verts, firstVert, nextVert, mesh.nverts); + } + + for (int j = 0; j < pmesh->npolys; ++j) + { + unsigned short* tgt = &mesh.polys[mesh.npolys*2*mesh.nvp]; + unsigned short* src = &pmesh->polys[j*2*mesh.nvp]; + mesh.regs[mesh.npolys] = pmesh->regs[j]; + mesh.areas[mesh.npolys] = pmesh->areas[j]; + mesh.flags[mesh.npolys] = pmesh->flags[j]; + mesh.npolys++; + for (int k = 0; k < mesh.nvp; ++k) + { + if (src[k] == RC_MESH_NULL_IDX) break; + tgt[k] = vremap[src[k]]; + } + } + } + + // Calculate adjacency. + if (!buildMeshAdjacency(mesh.polys, mesh.npolys, mesh.nverts, mesh.nvp)) + { + ctx->log(RC_LOG_ERROR, "rcMergePolyMeshes: Adjacency failed."); + return false; + } + + if (mesh.nverts > 0xffff) + { + ctx->log(RC_LOG_ERROR, "rcMergePolyMeshes: The resulting mesh has too many vertices %d (max %d). Data can be corrupted.", mesh.nverts, 0xffff); + } + if (mesh.npolys > 0xffff) + { + ctx->log(RC_LOG_ERROR, "rcMergePolyMeshes: The resulting mesh has too many polygons %d (max %d). Data can be corrupted.", mesh.npolys, 0xffff); + } + + ctx->stopTimer(RC_TIMER_MERGE_POLYMESH); + + return true; +} + +bool rcCopyPolyMesh(rcContext* ctx, const rcPolyMesh& src, rcPolyMesh& dst) +{ + rcAssert(ctx); + + // Destination must be empty. + rcAssert(dst.verts == 0); + rcAssert(dst.polys == 0); + rcAssert(dst.regs == 0); + rcAssert(dst.areas == 0); + rcAssert(dst.flags == 0); + + dst.nverts = src.nverts; + dst.npolys = src.npolys; + dst.maxpolys = src.npolys; + dst.nvp = src.nvp; + rcVcopy(dst.bmin, src.bmin); + rcVcopy(dst.bmax, src.bmax); + dst.cs = src.cs; + dst.ch = src.ch; + dst.borderSize = src.borderSize; + + dst.verts = (unsigned short*)rcAlloc(sizeof(unsigned short)*src.nverts*3, RC_ALLOC_PERM); + if (!dst.verts) + { + ctx->log(RC_LOG_ERROR, "rcCopyPolyMesh: Out of memory 'dst.verts' (%d).", src.nverts*3); + return false; + } + memcpy(dst.verts, src.verts, sizeof(unsigned short)*src.nverts*3); + + dst.polys = (unsigned short*)rcAlloc(sizeof(unsigned short)*src.npolys*2*src.nvp, RC_ALLOC_PERM); + if (!dst.polys) + { + ctx->log(RC_LOG_ERROR, "rcCopyPolyMesh: Out of memory 'dst.polys' (%d).", src.npolys*2*src.nvp); + return false; + } + memcpy(dst.polys, src.polys, sizeof(unsigned short)*src.npolys*2*src.nvp); + + dst.regs = (unsigned short*)rcAlloc(sizeof(unsigned short)*src.npolys, RC_ALLOC_PERM); + if (!dst.regs) + { + ctx->log(RC_LOG_ERROR, "rcCopyPolyMesh: Out of memory 'dst.regs' (%d).", src.npolys); + return false; + } + memcpy(dst.regs, src.regs, sizeof(unsigned short)*src.npolys); + + dst.areas = (unsigned char*)rcAlloc(sizeof(unsigned char)*src.npolys, RC_ALLOC_PERM); + if (!dst.areas) + { + ctx->log(RC_LOG_ERROR, "rcCopyPolyMesh: Out of memory 'dst.areas' (%d).", src.npolys); + return false; + } + memcpy(dst.areas, src.areas, sizeof(unsigned char)*src.npolys); + + dst.flags = (unsigned short*)rcAlloc(sizeof(unsigned short)*src.npolys, RC_ALLOC_PERM); + if (!dst.flags) + { + ctx->log(RC_LOG_ERROR, "rcCopyPolyMesh: Out of memory 'dst.flags' (%d).", src.npolys); + return false; + } + memcpy(dst.flags, src.flags, sizeof(unsigned char)*src.npolys); + + return true; +} diff --git a/KREngine/3rdparty/recast/source/RecastMeshDetail.cpp b/KREngine/3rdparty/recast/source/RecastMeshDetail.cpp new file mode 100755 index 0000000..77438fd --- /dev/null +++ b/KREngine/3rdparty/recast/source/RecastMeshDetail.cpp @@ -0,0 +1,1245 @@ +// +// Copyright (c) 2009-2010 Mikko Mononen memon@inside.org +// +// This software is provided 'as-is', without any express or implied +// warranty. In no event will the authors be held liable for any damages +// arising from the use of this software. +// Permission is granted to anyone to use this software for any purpose, +// including commercial applications, and to alter it and redistribute it +// freely, subject to the following restrictions: +// 1. The origin of this software must not be misrepresented; you must not +// claim that you wrote the original software. If you use this software +// in a product, an acknowledgment in the product documentation would be +// appreciated but is not required. +// 2. Altered source versions must be plainly marked as such, and must not be +// misrepresented as being the original software. +// 3. This notice may not be removed or altered from any source distribution. +// + +#include +#define _USE_MATH_DEFINES +#include +#include +#include +#include +#include "Recast.h" +#include "RecastAlloc.h" +#include "RecastAssert.h" + + +static const unsigned RC_UNSET_HEIGHT = 0xffff; + +struct rcHeightPatch +{ + inline rcHeightPatch() : data(0), xmin(0), ymin(0), width(0), height(0) {} + inline ~rcHeightPatch() { rcFree(data); } + unsigned short* data; + int xmin, ymin, width, height; +}; + + +inline float vdot2(const float* a, const float* b) +{ + return a[0]*b[0] + a[2]*b[2]; +} + +inline float vdistSq2(const float* p, const float* q) +{ + const float dx = q[0] - p[0]; + const float dy = q[2] - p[2]; + return dx*dx + dy*dy; +} + +inline float vdist2(const float* p, const float* q) +{ + return sqrtf(vdistSq2(p,q)); +} + +inline float vcross2(const float* p1, const float* p2, const float* p3) +{ + const float u1 = p2[0] - p1[0]; + const float v1 = p2[2] - p1[2]; + const float u2 = p3[0] - p1[0]; + const float v2 = p3[2] - p1[2]; + return u1 * v2 - v1 * u2; +} + +static bool circumCircle(const float* p1, const float* p2, const float* p3, + float* c, float& r) +{ + static const float EPS = 1e-6f; + + const float cp = vcross2(p1, p2, p3); + if (fabsf(cp) > EPS) + { + const float p1Sq = vdot2(p1,p1); + const float p2Sq = vdot2(p2,p2); + const float p3Sq = vdot2(p3,p3); + c[0] = (p1Sq*(p2[2]-p3[2]) + p2Sq*(p3[2]-p1[2]) + p3Sq*(p1[2]-p2[2])) / (2*cp); + c[2] = (p1Sq*(p3[0]-p2[0]) + p2Sq*(p1[0]-p3[0]) + p3Sq*(p2[0]-p1[0])) / (2*cp); + r = vdist2(c, p1); + return true; + } + + c[0] = p1[0]; + c[2] = p1[2]; + r = 0; + return false; +} + +static float distPtTri(const float* p, const float* a, const float* b, const float* c) +{ + float v0[3], v1[3], v2[3]; + rcVsub(v0, c,a); + rcVsub(v1, b,a); + rcVsub(v2, p,a); + + const float dot00 = vdot2(v0, v0); + const float dot01 = vdot2(v0, v1); + const float dot02 = vdot2(v0, v2); + const float dot11 = vdot2(v1, v1); + const float dot12 = vdot2(v1, v2); + + // Compute barycentric coordinates + const float invDenom = 1.0f / (dot00 * dot11 - dot01 * dot01); + const float u = (dot11 * dot02 - dot01 * dot12) * invDenom; + float v = (dot00 * dot12 - dot01 * dot02) * invDenom; + + // If point lies inside the triangle, return interpolated y-coord. + static const float EPS = 1e-4f; + if (u >= -EPS && v >= -EPS && (u+v) <= 1+EPS) + { + const float y = a[1] + v0[1]*u + v1[1]*v; + return fabsf(y-p[1]); + } + return FLT_MAX; +} + +static float distancePtSeg(const float* pt, const float* p, const float* q) +{ + float pqx = q[0] - p[0]; + float pqy = q[1] - p[1]; + float pqz = q[2] - p[2]; + float dx = pt[0] - p[0]; + float dy = pt[1] - p[1]; + float dz = pt[2] - p[2]; + float d = pqx*pqx + pqy*pqy + pqz*pqz; + float t = pqx*dx + pqy*dy + pqz*dz; + if (d > 0) + t /= d; + if (t < 0) + t = 0; + else if (t > 1) + t = 1; + + dx = p[0] + t*pqx - pt[0]; + dy = p[1] + t*pqy - pt[1]; + dz = p[2] + t*pqz - pt[2]; + + return dx*dx + dy*dy + dz*dz; +} + +static float distancePtSeg2d(const float* pt, const float* p, const float* q) +{ + float pqx = q[0] - p[0]; + float pqz = q[2] - p[2]; + float dx = pt[0] - p[0]; + float dz = pt[2] - p[2]; + float d = pqx*pqx + pqz*pqz; + float t = pqx*dx + pqz*dz; + if (d > 0) + t /= d; + if (t < 0) + t = 0; + else if (t > 1) + t = 1; + + dx = p[0] + t*pqx - pt[0]; + dz = p[2] + t*pqz - pt[2]; + + return dx*dx + dz*dz; +} + +static float distToTriMesh(const float* p, const float* verts, const int /*nverts*/, const int* tris, const int ntris) +{ + float dmin = FLT_MAX; + for (int i = 0; i < ntris; ++i) + { + const float* va = &verts[tris[i*4+0]*3]; + const float* vb = &verts[tris[i*4+1]*3]; + const float* vc = &verts[tris[i*4+2]*3]; + float d = distPtTri(p, va,vb,vc); + if (d < dmin) + dmin = d; + } + if (dmin == FLT_MAX) return -1; + return dmin; +} + +static float distToPoly(int nvert, const float* verts, const float* p) +{ + + float dmin = FLT_MAX; + int i, j, c = 0; + for (i = 0, j = nvert-1; i < nvert; j = i++) + { + const float* vi = &verts[i*3]; + const float* vj = &verts[j*3]; + if (((vi[2] > p[2]) != (vj[2] > p[2])) && + (p[0] < (vj[0]-vi[0]) * (p[2]-vi[2]) / (vj[2]-vi[2]) + vi[0]) ) + c = !c; + dmin = rcMin(dmin, distancePtSeg2d(p, vj, vi)); + } + return c ? -dmin : dmin; +} + + +static unsigned short getHeight(const float fx, const float fy, const float fz, + const float /*cs*/, const float ics, const float ch, + const rcHeightPatch& hp) +{ + int ix = (int)floorf(fx*ics + 0.01f); + int iz = (int)floorf(fz*ics + 0.01f); + ix = rcClamp(ix-hp.xmin, 0, hp.width - 1); + iz = rcClamp(iz-hp.ymin, 0, hp.height - 1); + unsigned short h = hp.data[ix+iz*hp.width]; + if (h == RC_UNSET_HEIGHT) + { + // Special case when data might be bad. + // Find nearest neighbour pixel which has valid height. + const int off[8*2] = { -1,0, -1,-1, 0,-1, 1,-1, 1,0, 1,1, 0,1, -1,1}; + float dmin = FLT_MAX; + for (int i = 0; i < 8; ++i) + { + const int nx = ix+off[i*2+0]; + const int nz = iz+off[i*2+1]; + if (nx < 0 || nz < 0 || nx >= hp.width || nz >= hp.height) continue; + const unsigned short nh = hp.data[nx+nz*hp.width]; + if (nh == RC_UNSET_HEIGHT) continue; + + const float d = fabsf(nh*ch - fy); + if (d < dmin) + { + h = nh; + dmin = d; + } + +/* const float dx = (nx+0.5f)*cs - fx; + const float dz = (nz+0.5f)*cs - fz; + const float d = dx*dx+dz*dz; + if (d < dmin) + { + h = nh; + dmin = d; + } */ + } + } + return h; +} + + +enum EdgeValues +{ + UNDEF = -1, + HULL = -2, +}; + +static int findEdge(const int* edges, int nedges, int s, int t) +{ + for (int i = 0; i < nedges; i++) + { + const int* e = &edges[i*4]; + if ((e[0] == s && e[1] == t) || (e[0] == t && e[1] == s)) + return i; + } + return UNDEF; +} + +static int addEdge(rcContext* ctx, int* edges, int& nedges, const int maxEdges, int s, int t, int l, int r) +{ + if (nedges >= maxEdges) + { + ctx->log(RC_LOG_ERROR, "addEdge: Too many edges (%d/%d).", nedges, maxEdges); + return UNDEF; + } + + // Add edge if not already in the triangulation. + int e = findEdge(edges, nedges, s, t); + if (e == UNDEF) + { + int* edge = &edges[nedges*4]; + edge[0] = s; + edge[1] = t; + edge[2] = l; + edge[3] = r; + return nedges++; + } + else + { + return UNDEF; + } +} + +static void updateLeftFace(int* e, int s, int t, int f) +{ + if (e[0] == s && e[1] == t && e[2] == UNDEF) + e[2] = f; + else if (e[1] == s && e[0] == t && e[3] == UNDEF) + e[3] = f; +} + +static int overlapSegSeg2d(const float* a, const float* b, const float* c, const float* d) +{ + const float a1 = vcross2(a, b, d); + const float a2 = vcross2(a, b, c); + if (a1*a2 < 0.0f) + { + float a3 = vcross2(c, d, a); + float a4 = a3 + a2 - a1; + if (a3 * a4 < 0.0f) + return 1; + } + return 0; +} + +static bool overlapEdges(const float* pts, const int* edges, int nedges, int s1, int t1) +{ + for (int i = 0; i < nedges; ++i) + { + const int s0 = edges[i*4+0]; + const int t0 = edges[i*4+1]; + // Same or connected edges do not overlap. + if (s0 == s1 || s0 == t1 || t0 == s1 || t0 == t1) + continue; + if (overlapSegSeg2d(&pts[s0*3],&pts[t0*3], &pts[s1*3],&pts[t1*3])) + return true; + } + return false; +} + +static void completeFacet(rcContext* ctx, const float* pts, int npts, int* edges, int& nedges, const int maxEdges, int& nfaces, int e) +{ + static const float EPS = 1e-5f; + + int* edge = &edges[e*4]; + + // Cache s and t. + int s,t; + if (edge[2] == UNDEF) + { + s = edge[0]; + t = edge[1]; + } + else if (edge[3] == UNDEF) + { + s = edge[1]; + t = edge[0]; + } + else + { + // Edge already completed. + return; + } + + // Find best point on left of edge. + int pt = npts; + float c[3] = {0,0,0}; + float r = -1; + for (int u = 0; u < npts; ++u) + { + if (u == s || u == t) continue; + if (vcross2(&pts[s*3], &pts[t*3], &pts[u*3]) > EPS) + { + if (r < 0) + { + // The circle is not updated yet, do it now. + pt = u; + circumCircle(&pts[s*3], &pts[t*3], &pts[u*3], c, r); + continue; + } + const float d = vdist2(c, &pts[u*3]); + const float tol = 0.001f; + if (d > r*(1+tol)) + { + // Outside current circumcircle, skip. + continue; + } + else if (d < r*(1-tol)) + { + // Inside safe circumcircle, update circle. + pt = u; + circumCircle(&pts[s*3], &pts[t*3], &pts[u*3], c, r); + } + else + { + // Inside epsilon circum circle, do extra tests to make sure the edge is valid. + // s-u and t-u cannot overlap with s-pt nor t-pt if they exists. + if (overlapEdges(pts, edges, nedges, s,u)) + continue; + if (overlapEdges(pts, edges, nedges, t,u)) + continue; + // Edge is valid. + pt = u; + circumCircle(&pts[s*3], &pts[t*3], &pts[u*3], c, r); + } + } + } + + // Add new triangle or update edge info if s-t is on hull. + if (pt < npts) + { + // Update face information of edge being completed. + updateLeftFace(&edges[e*4], s, t, nfaces); + + // Add new edge or update face info of old edge. + e = findEdge(edges, nedges, pt, s); + if (e == UNDEF) + addEdge(ctx, edges, nedges, maxEdges, pt, s, nfaces, UNDEF); + else + updateLeftFace(&edges[e*4], pt, s, nfaces); + + // Add new edge or update face info of old edge. + e = findEdge(edges, nedges, t, pt); + if (e == UNDEF) + addEdge(ctx, edges, nedges, maxEdges, t, pt, nfaces, UNDEF); + else + updateLeftFace(&edges[e*4], t, pt, nfaces); + + nfaces++; + } + else + { + updateLeftFace(&edges[e*4], s, t, HULL); + } +} + +static void delaunayHull(rcContext* ctx, const int npts, const float* pts, + const int nhull, const int* hull, + rcIntArray& tris, rcIntArray& edges) +{ + int nfaces = 0; + int nedges = 0; + const int maxEdges = npts*10; + edges.resize(maxEdges*4); + + for (int i = 0, j = nhull-1; i < nhull; j=i++) + addEdge(ctx, &edges[0], nedges, maxEdges, hull[j],hull[i], HULL, UNDEF); + + int currentEdge = 0; + while (currentEdge < nedges) + { + if (edges[currentEdge*4+2] == UNDEF) + completeFacet(ctx, pts, npts, &edges[0], nedges, maxEdges, nfaces, currentEdge); + if (edges[currentEdge*4+3] == UNDEF) + completeFacet(ctx, pts, npts, &edges[0], nedges, maxEdges, nfaces, currentEdge); + currentEdge++; + } + + // Create tris + tris.resize(nfaces*4); + for (int i = 0; i < nfaces*4; ++i) + tris[i] = -1; + + for (int i = 0; i < nedges; ++i) + { + const int* e = &edges[i*4]; + if (e[3] >= 0) + { + // Left face + int* t = &tris[e[3]*4]; + if (t[0] == -1) + { + t[0] = e[0]; + t[1] = e[1]; + } + else if (t[0] == e[1]) + t[2] = e[0]; + else if (t[1] == e[0]) + t[2] = e[1]; + } + if (e[2] >= 0) + { + // Right + int* t = &tris[e[2]*4]; + if (t[0] == -1) + { + t[0] = e[1]; + t[1] = e[0]; + } + else if (t[0] == e[0]) + t[2] = e[1]; + else if (t[1] == e[1]) + t[2] = e[0]; + } + } + + for (int i = 0; i < tris.size()/4; ++i) + { + int* t = &tris[i*4]; + if (t[0] == -1 || t[1] == -1 || t[2] == -1) + { + ctx->log(RC_LOG_WARNING, "delaunayHull: Removing dangling face %d [%d,%d,%d].", i, t[0],t[1],t[2]); + t[0] = tris[tris.size()-4]; + t[1] = tris[tris.size()-3]; + t[2] = tris[tris.size()-2]; + t[3] = tris[tris.size()-1]; + tris.resize(tris.size()-4); + --i; + } + } +} + + +inline float getJitterX(const int i) +{ + return (((i * 0x8da6b343) & 0xffff) / 65535.0f * 2.0f) - 1.0f; +} + +inline float getJitterY(const int i) +{ + return (((i * 0xd8163841) & 0xffff) / 65535.0f * 2.0f) - 1.0f; +} + +static bool buildPolyDetail(rcContext* ctx, const float* in, const int nin, + const float sampleDist, const float sampleMaxError, + const rcCompactHeightfield& chf, const rcHeightPatch& hp, + float* verts, int& nverts, rcIntArray& tris, + rcIntArray& edges, rcIntArray& samples) +{ + static const int MAX_VERTS = 127; + static const int MAX_TRIS = 255; // Max tris for delaunay is 2n-2-k (n=num verts, k=num hull verts). + static const int MAX_VERTS_PER_EDGE = 32; + float edge[(MAX_VERTS_PER_EDGE+1)*3]; + int hull[MAX_VERTS]; + int nhull = 0; + + nverts = 0; + + for (int i = 0; i < nin; ++i) + rcVcopy(&verts[i*3], &in[i*3]); + nverts = nin; + + const float cs = chf.cs; + const float ics = 1.0f/cs; + + // Tessellate outlines. + // This is done in separate pass in order to ensure + // seamless height values across the ply boundaries. + if (sampleDist > 0) + { + for (int i = 0, j = nin-1; i < nin; j=i++) + { + const float* vj = &in[j*3]; + const float* vi = &in[i*3]; + bool swapped = false; + // Make sure the segments are always handled in same order + // using lexological sort or else there will be seams. + if (fabsf(vj[0]-vi[0]) < 1e-6f) + { + if (vj[2] > vi[2]) + { + rcSwap(vj,vi); + swapped = true; + } + } + else + { + if (vj[0] > vi[0]) + { + rcSwap(vj,vi); + swapped = true; + } + } + // Create samples along the edge. + float dx = vi[0] - vj[0]; + float dy = vi[1] - vj[1]; + float dz = vi[2] - vj[2]; + float d = sqrtf(dx*dx + dz*dz); + int nn = 1 + (int)floorf(d/sampleDist); + if (nn >= MAX_VERTS_PER_EDGE) nn = MAX_VERTS_PER_EDGE-1; + if (nverts+nn >= MAX_VERTS) + nn = MAX_VERTS-1-nverts; + + for (int k = 0; k <= nn; ++k) + { + float u = (float)k/(float)nn; + float* pos = &edge[k*3]; + pos[0] = vj[0] + dx*u; + pos[1] = vj[1] + dy*u; + pos[2] = vj[2] + dz*u; + pos[1] = getHeight(pos[0],pos[1],pos[2], cs, ics, chf.ch, hp)*chf.ch; + } + // Simplify samples. + int idx[MAX_VERTS_PER_EDGE] = {0,nn}; + int nidx = 2; + for (int k = 0; k < nidx-1; ) + { + const int a = idx[k]; + const int b = idx[k+1]; + const float* va = &edge[a*3]; + const float* vb = &edge[b*3]; + // Find maximum deviation along the segment. + float maxd = 0; + int maxi = -1; + for (int m = a+1; m < b; ++m) + { + float dev = distancePtSeg(&edge[m*3],va,vb); + if (dev > maxd) + { + maxd = dev; + maxi = m; + } + } + // If the max deviation is larger than accepted error, + // add new point, else continue to next segment. + if (maxi != -1 && maxd > rcSqr(sampleMaxError)) + { + for (int m = nidx; m > k; --m) + idx[m] = idx[m-1]; + idx[k+1] = maxi; + nidx++; + } + else + { + ++k; + } + } + + hull[nhull++] = j; + // Add new vertices. + if (swapped) + { + for (int k = nidx-2; k > 0; --k) + { + rcVcopy(&verts[nverts*3], &edge[idx[k]*3]); + hull[nhull++] = nverts; + nverts++; + } + } + else + { + for (int k = 1; k < nidx-1; ++k) + { + rcVcopy(&verts[nverts*3], &edge[idx[k]*3]); + hull[nhull++] = nverts; + nverts++; + } + } + } + } + + + // Tessellate the base mesh. + edges.resize(0); + tris.resize(0); + + delaunayHull(ctx, nverts, verts, nhull, hull, tris, edges); + + if (tris.size() == 0) + { + // Could not triangulate the poly, make sure there is some valid data there. + ctx->log(RC_LOG_WARNING, "buildPolyDetail: Could not triangulate polygon, adding default data."); + for (int i = 2; i < nverts; ++i) + { + tris.push(0); + tris.push(i-1); + tris.push(i); + tris.push(0); + } + return true; + } + + if (sampleDist > 0) + { + // Create sample locations in a grid. + float bmin[3], bmax[3]; + rcVcopy(bmin, in); + rcVcopy(bmax, in); + for (int i = 1; i < nin; ++i) + { + rcVmin(bmin, &in[i*3]); + rcVmax(bmax, &in[i*3]); + } + int x0 = (int)floorf(bmin[0]/sampleDist); + int x1 = (int)ceilf(bmax[0]/sampleDist); + int z0 = (int)floorf(bmin[2]/sampleDist); + int z1 = (int)ceilf(bmax[2]/sampleDist); + samples.resize(0); + for (int z = z0; z < z1; ++z) + { + for (int x = x0; x < x1; ++x) + { + float pt[3]; + pt[0] = x*sampleDist; + pt[1] = (bmax[1]+bmin[1])*0.5f; + pt[2] = z*sampleDist; + // Make sure the samples are not too close to the edges. + if (distToPoly(nin,in,pt) > -sampleDist/2) continue; + samples.push(x); + samples.push(getHeight(pt[0], pt[1], pt[2], cs, ics, chf.ch, hp)); + samples.push(z); + samples.push(0); // Not added + } + } + + // Add the samples starting from the one that has the most + // error. The procedure stops when all samples are added + // or when the max error is within treshold. + const int nsamples = samples.size()/4; + for (int iter = 0; iter < nsamples; ++iter) + { + if (nverts >= MAX_VERTS) + break; + + // Find sample with most error. + float bestpt[3] = {0,0,0}; + float bestd = 0; + int besti = -1; + for (int i = 0; i < nsamples; ++i) + { + const int* s = &samples[i*4]; + if (s[3]) continue; // skip added. + float pt[3]; + // The sample location is jittered to get rid of some bad triangulations + // which are cause by symmetrical data from the grid structure. + pt[0] = s[0]*sampleDist + getJitterX(i)*cs*0.1f; + pt[1] = s[1]*chf.ch; + pt[2] = s[2]*sampleDist + getJitterY(i)*cs*0.1f; + float d = distToTriMesh(pt, verts, nverts, &tris[0], tris.size()/4); + if (d < 0) continue; // did not hit the mesh. + if (d > bestd) + { + bestd = d; + besti = i; + rcVcopy(bestpt,pt); + } + } + // If the max error is within accepted threshold, stop tesselating. + if (bestd <= sampleMaxError || besti == -1) + break; + // Mark sample as added. + samples[besti*4+3] = 1; + // Add the new sample point. + rcVcopy(&verts[nverts*3],bestpt); + nverts++; + + // Create new triangulation. + // TODO: Incremental add instead of full rebuild. + edges.resize(0); + tris.resize(0); + delaunayHull(ctx, nverts, verts, nhull, hull, tris, edges); + } + } + + const int ntris = tris.size()/4; + if (ntris > MAX_TRIS) + { + tris.resize(MAX_TRIS*4); + ctx->log(RC_LOG_ERROR, "rcBuildPolyMeshDetail: Shrinking triangle count from %d to max %d.", ntris, MAX_TRIS); + } + + return true; +} + +static void getHeightData(const rcCompactHeightfield& chf, + const unsigned short* poly, const int npoly, + const unsigned short* verts, const int bs, + rcHeightPatch& hp, rcIntArray& stack) +{ + // Floodfill the heightfield to get 2D height data, + // starting at vertex locations as seeds. + + // Note: Reads to the compact heightfield are offset by border size (bs) + // since border size offset is already removed from the polymesh vertices. + + memset(hp.data, 0, sizeof(unsigned short)*hp.width*hp.height); + + stack.resize(0); + + static const int offset[9*2] = + { + 0,0, -1,-1, 0,-1, 1,-1, 1,0, 1,1, 0,1, -1,1, -1,0, + }; + + // Use poly vertices as seed points for the flood fill. + for (int j = 0; j < npoly; ++j) + { + int cx = 0, cz = 0, ci =-1; + int dmin = RC_UNSET_HEIGHT; + for (int k = 0; k < 9; ++k) + { + const int ax = (int)verts[poly[j]*3+0] + offset[k*2+0]; + const int ay = (int)verts[poly[j]*3+1]; + const int az = (int)verts[poly[j]*3+2] + offset[k*2+1]; + if (ax < hp.xmin || ax >= hp.xmin+hp.width || + az < hp.ymin || az >= hp.ymin+hp.height) + continue; + + const rcCompactCell& c = chf.cells[(ax+bs)+(az+bs)*chf.width]; + for (int i = (int)c.index, ni = (int)(c.index+c.count); i < ni; ++i) + { + const rcCompactSpan& s = chf.spans[i]; + int d = rcAbs(ay - (int)s.y); + if (d < dmin) + { + cx = ax; + cz = az; + ci = i; + dmin = d; + } + } + } + if (ci != -1) + { + stack.push(cx); + stack.push(cz); + stack.push(ci); + } + } + + // Find center of the polygon using flood fill. + int pcx = 0, pcz = 0; + for (int j = 0; j < npoly; ++j) + { + pcx += (int)verts[poly[j]*3+0]; + pcz += (int)verts[poly[j]*3+2]; + } + pcx /= npoly; + pcz /= npoly; + + for (int i = 0; i < stack.size(); i += 3) + { + int cx = stack[i+0]; + int cy = stack[i+1]; + int idx = cx-hp.xmin+(cy-hp.ymin)*hp.width; + hp.data[idx] = 1; + } + + while (stack.size() > 0) + { + int ci = stack.pop(); + int cy = stack.pop(); + int cx = stack.pop(); + + // Check if close to center of the polygon. + if (rcAbs(cx-pcx) <= 1 && rcAbs(cy-pcz) <= 1) + { + stack.resize(0); + stack.push(cx); + stack.push(cy); + stack.push(ci); + break; + } + + const rcCompactSpan& cs = chf.spans[ci]; + + for (int dir = 0; dir < 4; ++dir) + { + if (rcGetCon(cs, dir) == RC_NOT_CONNECTED) continue; + + const int ax = cx + rcGetDirOffsetX(dir); + const int ay = cy + rcGetDirOffsetY(dir); + + if (ax < hp.xmin || ax >= (hp.xmin+hp.width) || + ay < hp.ymin || ay >= (hp.ymin+hp.height)) + continue; + + if (hp.data[ax-hp.xmin+(ay-hp.ymin)*hp.width] != 0) + continue; + + const int ai = (int)chf.cells[(ax+bs)+(ay+bs)*chf.width].index + rcGetCon(cs, dir); + + int idx = ax-hp.xmin+(ay-hp.ymin)*hp.width; + hp.data[idx] = 1; + + stack.push(ax); + stack.push(ay); + stack.push(ai); + } + } + + memset(hp.data, 0xff, sizeof(unsigned short)*hp.width*hp.height); + + // Mark start locations. + for (int i = 0; i < stack.size(); i += 3) + { + int cx = stack[i+0]; + int cy = stack[i+1]; + int ci = stack[i+2]; + int idx = cx-hp.xmin+(cy-hp.ymin)*hp.width; + const rcCompactSpan& cs = chf.spans[ci]; + hp.data[idx] = cs.y; + } + + static const int RETRACT_SIZE = 256; + int head = 0; + + while (head*3 < stack.size()) + { + int cx = stack[head*3+0]; + int cy = stack[head*3+1]; + int ci = stack[head*3+2]; + head++; + if (head >= RETRACT_SIZE) + { + head = 0; + if (stack.size() > RETRACT_SIZE*3) + memmove(&stack[0], &stack[RETRACT_SIZE*3], sizeof(int)*(stack.size()-RETRACT_SIZE*3)); + stack.resize(stack.size()-RETRACT_SIZE*3); + } + + const rcCompactSpan& cs = chf.spans[ci]; + for (int dir = 0; dir < 4; ++dir) + { + if (rcGetCon(cs, dir) == RC_NOT_CONNECTED) continue; + + const int ax = cx + rcGetDirOffsetX(dir); + const int ay = cy + rcGetDirOffsetY(dir); + + if (ax < hp.xmin || ax >= (hp.xmin+hp.width) || + ay < hp.ymin || ay >= (hp.ymin+hp.height)) + continue; + + if (hp.data[ax-hp.xmin+(ay-hp.ymin)*hp.width] != RC_UNSET_HEIGHT) + continue; + + const int ai = (int)chf.cells[(ax+bs)+(ay+bs)*chf.width].index + rcGetCon(cs, dir); + + const rcCompactSpan& as = chf.spans[ai]; + int idx = ax-hp.xmin+(ay-hp.ymin)*hp.width; + hp.data[idx] = as.y; + + stack.push(ax); + stack.push(ay); + stack.push(ai); + } + } + +} + +static unsigned char getEdgeFlags(const float* va, const float* vb, + const float* vpoly, const int npoly) +{ + // Return true if edge (va,vb) is part of the polygon. + static const float thrSqr = rcSqr(0.001f); + for (int i = 0, j = npoly-1; i < npoly; j=i++) + { + if (distancePtSeg2d(va, &vpoly[j*3], &vpoly[i*3]) < thrSqr && + distancePtSeg2d(vb, &vpoly[j*3], &vpoly[i*3]) < thrSqr) + return 1; + } + return 0; +} + +static unsigned char getTriFlags(const float* va, const float* vb, const float* vc, + const float* vpoly, const int npoly) +{ + unsigned char flags = 0; + flags |= getEdgeFlags(va,vb,vpoly,npoly) << 0; + flags |= getEdgeFlags(vb,vc,vpoly,npoly) << 2; + flags |= getEdgeFlags(vc,va,vpoly,npoly) << 4; + return flags; +} + +/// @par +/// +/// See the #rcConfig documentation for more information on the configuration parameters. +/// +/// @see rcAllocPolyMeshDetail, rcPolyMesh, rcCompactHeightfield, rcPolyMeshDetail, rcConfig +bool rcBuildPolyMeshDetail(rcContext* ctx, const rcPolyMesh& mesh, const rcCompactHeightfield& chf, + const float sampleDist, const float sampleMaxError, + rcPolyMeshDetail& dmesh) +{ + rcAssert(ctx); + + ctx->startTimer(RC_TIMER_BUILD_POLYMESHDETAIL); + + if (mesh.nverts == 0 || mesh.npolys == 0) + return true; + + const int nvp = mesh.nvp; + const float cs = mesh.cs; + const float ch = mesh.ch; + const float* orig = mesh.bmin; + const int borderSize = mesh.borderSize; + + rcIntArray edges(64); + rcIntArray tris(512); + rcIntArray stack(512); + rcIntArray samples(512); + float verts[256*3]; + rcHeightPatch hp; + int nPolyVerts = 0; + int maxhw = 0, maxhh = 0; + + rcScopedDelete bounds = (int*)rcAlloc(sizeof(int)*mesh.npolys*4, RC_ALLOC_TEMP); + if (!bounds) + { + ctx->log(RC_LOG_ERROR, "rcBuildPolyMeshDetail: Out of memory 'bounds' (%d).", mesh.npolys*4); + return false; + } + rcScopedDelete poly = (float*)rcAlloc(sizeof(float)*nvp*3, RC_ALLOC_TEMP); + if (!poly) + { + ctx->log(RC_LOG_ERROR, "rcBuildPolyMeshDetail: Out of memory 'poly' (%d).", nvp*3); + return false; + } + + // Find max size for a polygon area. + for (int i = 0; i < mesh.npolys; ++i) + { + const unsigned short* p = &mesh.polys[i*nvp*2]; + int& xmin = bounds[i*4+0]; + int& xmax = bounds[i*4+1]; + int& ymin = bounds[i*4+2]; + int& ymax = bounds[i*4+3]; + xmin = chf.width; + xmax = 0; + ymin = chf.height; + ymax = 0; + for (int j = 0; j < nvp; ++j) + { + if(p[j] == RC_MESH_NULL_IDX) break; + const unsigned short* v = &mesh.verts[p[j]*3]; + xmin = rcMin(xmin, (int)v[0]); + xmax = rcMax(xmax, (int)v[0]); + ymin = rcMin(ymin, (int)v[2]); + ymax = rcMax(ymax, (int)v[2]); + nPolyVerts++; + } + xmin = rcMax(0,xmin-1); + xmax = rcMin(chf.width,xmax+1); + ymin = rcMax(0,ymin-1); + ymax = rcMin(chf.height,ymax+1); + if (xmin >= xmax || ymin >= ymax) continue; + maxhw = rcMax(maxhw, xmax-xmin); + maxhh = rcMax(maxhh, ymax-ymin); + } + + hp.data = (unsigned short*)rcAlloc(sizeof(unsigned short)*maxhw*maxhh, RC_ALLOC_TEMP); + if (!hp.data) + { + ctx->log(RC_LOG_ERROR, "rcBuildPolyMeshDetail: Out of memory 'hp.data' (%d).", maxhw*maxhh); + return false; + } + + dmesh.nmeshes = mesh.npolys; + dmesh.nverts = 0; + dmesh.ntris = 0; + dmesh.meshes = (unsigned int*)rcAlloc(sizeof(unsigned int)*dmesh.nmeshes*4, RC_ALLOC_PERM); + if (!dmesh.meshes) + { + ctx->log(RC_LOG_ERROR, "rcBuildPolyMeshDetail: Out of memory 'dmesh.meshes' (%d).", dmesh.nmeshes*4); + return false; + } + + int vcap = nPolyVerts+nPolyVerts/2; + int tcap = vcap*2; + + dmesh.nverts = 0; + dmesh.verts = (float*)rcAlloc(sizeof(float)*vcap*3, RC_ALLOC_PERM); + if (!dmesh.verts) + { + ctx->log(RC_LOG_ERROR, "rcBuildPolyMeshDetail: Out of memory 'dmesh.verts' (%d).", vcap*3); + return false; + } + dmesh.ntris = 0; + dmesh.tris = (unsigned char*)rcAlloc(sizeof(unsigned char*)*tcap*4, RC_ALLOC_PERM); + if (!dmesh.tris) + { + ctx->log(RC_LOG_ERROR, "rcBuildPolyMeshDetail: Out of memory 'dmesh.tris' (%d).", tcap*4); + return false; + } + + for (int i = 0; i < mesh.npolys; ++i) + { + const unsigned short* p = &mesh.polys[i*nvp*2]; + + // Store polygon vertices for processing. + int npoly = 0; + for (int j = 0; j < nvp; ++j) + { + if(p[j] == RC_MESH_NULL_IDX) break; + const unsigned short* v = &mesh.verts[p[j]*3]; + poly[j*3+0] = v[0]*cs; + poly[j*3+1] = v[1]*ch; + poly[j*3+2] = v[2]*cs; + npoly++; + } + + // Get the height data from the area of the polygon. + hp.xmin = bounds[i*4+0]; + hp.ymin = bounds[i*4+2]; + hp.width = bounds[i*4+1]-bounds[i*4+0]; + hp.height = bounds[i*4+3]-bounds[i*4+2]; + getHeightData(chf, p, npoly, mesh.verts, borderSize, hp, stack); + + // Build detail mesh. + int nverts = 0; + if (!buildPolyDetail(ctx, poly, npoly, + sampleDist, sampleMaxError, + chf, hp, verts, nverts, tris, + edges, samples)) + { + return false; + } + + // Move detail verts to world space. + for (int j = 0; j < nverts; ++j) + { + verts[j*3+0] += orig[0]; + verts[j*3+1] += orig[1] + chf.ch; // Is this offset necessary? + verts[j*3+2] += orig[2]; + } + // Offset poly too, will be used to flag checking. + for (int j = 0; j < npoly; ++j) + { + poly[j*3+0] += orig[0]; + poly[j*3+1] += orig[1]; + poly[j*3+2] += orig[2]; + } + + // Store detail submesh. + const int ntris = tris.size()/4; + + dmesh.meshes[i*4+0] = (unsigned int)dmesh.nverts; + dmesh.meshes[i*4+1] = (unsigned int)nverts; + dmesh.meshes[i*4+2] = (unsigned int)dmesh.ntris; + dmesh.meshes[i*4+3] = (unsigned int)ntris; + + // Store vertices, allocate more memory if necessary. + if (dmesh.nverts+nverts > vcap) + { + while (dmesh.nverts+nverts > vcap) + vcap += 256; + + float* newv = (float*)rcAlloc(sizeof(float)*vcap*3, RC_ALLOC_PERM); + if (!newv) + { + ctx->log(RC_LOG_ERROR, "rcBuildPolyMeshDetail: Out of memory 'newv' (%d).", vcap*3); + return false; + } + if (dmesh.nverts) + memcpy(newv, dmesh.verts, sizeof(float)*3*dmesh.nverts); + rcFree(dmesh.verts); + dmesh.verts = newv; + } + for (int j = 0; j < nverts; ++j) + { + dmesh.verts[dmesh.nverts*3+0] = verts[j*3+0]; + dmesh.verts[dmesh.nverts*3+1] = verts[j*3+1]; + dmesh.verts[dmesh.nverts*3+2] = verts[j*3+2]; + dmesh.nverts++; + } + + // Store triangles, allocate more memory if necessary. + if (dmesh.ntris+ntris > tcap) + { + while (dmesh.ntris+ntris > tcap) + tcap += 256; + unsigned char* newt = (unsigned char*)rcAlloc(sizeof(unsigned char)*tcap*4, RC_ALLOC_PERM); + if (!newt) + { + ctx->log(RC_LOG_ERROR, "rcBuildPolyMeshDetail: Out of memory 'newt' (%d).", tcap*4); + return false; + } + if (dmesh.ntris) + memcpy(newt, dmesh.tris, sizeof(unsigned char)*4*dmesh.ntris); + rcFree(dmesh.tris); + dmesh.tris = newt; + } + for (int j = 0; j < ntris; ++j) + { + const int* t = &tris[j*4]; + dmesh.tris[dmesh.ntris*4+0] = (unsigned char)t[0]; + dmesh.tris[dmesh.ntris*4+1] = (unsigned char)t[1]; + dmesh.tris[dmesh.ntris*4+2] = (unsigned char)t[2]; + dmesh.tris[dmesh.ntris*4+3] = getTriFlags(&verts[t[0]*3], &verts[t[1]*3], &verts[t[2]*3], poly, npoly); + dmesh.ntris++; + } + } + + ctx->stopTimer(RC_TIMER_BUILD_POLYMESHDETAIL); + + return true; +} + +/// @see rcAllocPolyMeshDetail, rcPolyMeshDetail +bool rcMergePolyMeshDetails(rcContext* ctx, rcPolyMeshDetail** meshes, const int nmeshes, rcPolyMeshDetail& mesh) +{ + rcAssert(ctx); + + ctx->startTimer(RC_TIMER_MERGE_POLYMESHDETAIL); + + int maxVerts = 0; + int maxTris = 0; + int maxMeshes = 0; + + for (int i = 0; i < nmeshes; ++i) + { + if (!meshes[i]) continue; + maxVerts += meshes[i]->nverts; + maxTris += meshes[i]->ntris; + maxMeshes += meshes[i]->nmeshes; + } + + mesh.nmeshes = 0; + mesh.meshes = (unsigned int*)rcAlloc(sizeof(unsigned int)*maxMeshes*4, RC_ALLOC_PERM); + if (!mesh.meshes) + { + ctx->log(RC_LOG_ERROR, "rcBuildPolyMeshDetail: Out of memory 'pmdtl.meshes' (%d).", maxMeshes*4); + return false; + } + + mesh.ntris = 0; + mesh.tris = (unsigned char*)rcAlloc(sizeof(unsigned char)*maxTris*4, RC_ALLOC_PERM); + if (!mesh.tris) + { + ctx->log(RC_LOG_ERROR, "rcBuildPolyMeshDetail: Out of memory 'dmesh.tris' (%d).", maxTris*4); + return false; + } + + mesh.nverts = 0; + mesh.verts = (float*)rcAlloc(sizeof(float)*maxVerts*3, RC_ALLOC_PERM); + if (!mesh.verts) + { + ctx->log(RC_LOG_ERROR, "rcBuildPolyMeshDetail: Out of memory 'dmesh.verts' (%d).", maxVerts*3); + return false; + } + + // Merge datas. + for (int i = 0; i < nmeshes; ++i) + { + rcPolyMeshDetail* dm = meshes[i]; + if (!dm) continue; + for (int j = 0; j < dm->nmeshes; ++j) + { + unsigned int* dst = &mesh.meshes[mesh.nmeshes*4]; + unsigned int* src = &dm->meshes[j*4]; + dst[0] = (unsigned int)mesh.nverts+src[0]; + dst[1] = src[1]; + dst[2] = (unsigned int)mesh.ntris+src[2]; + dst[3] = src[3]; + mesh.nmeshes++; + } + + for (int k = 0; k < dm->nverts; ++k) + { + rcVcopy(&mesh.verts[mesh.nverts*3], &dm->verts[k*3]); + mesh.nverts++; + } + for (int k = 0; k < dm->ntris; ++k) + { + mesh.tris[mesh.ntris*4+0] = dm->tris[k*4+0]; + mesh.tris[mesh.ntris*4+1] = dm->tris[k*4+1]; + mesh.tris[mesh.ntris*4+2] = dm->tris[k*4+2]; + mesh.tris[mesh.ntris*4+3] = dm->tris[k*4+3]; + mesh.ntris++; + } + } + + ctx->stopTimer(RC_TIMER_MERGE_POLYMESHDETAIL); + + return true; +} + diff --git a/KREngine/3rdparty/recast/source/RecastRasterization.cpp b/KREngine/3rdparty/recast/source/RecastRasterization.cpp new file mode 100755 index 0000000..d2bb7c9 --- /dev/null +++ b/KREngine/3rdparty/recast/source/RecastRasterization.cpp @@ -0,0 +1,387 @@ +// +// Copyright (c) 2009-2010 Mikko Mononen memon@inside.org +// +// This software is provided 'as-is', without any express or implied +// warranty. In no event will the authors be held liable for any damages +// arising from the use of this software. +// Permission is granted to anyone to use this software for any purpose, +// including commercial applications, and to alter it and redistribute it +// freely, subject to the following restrictions: +// 1. The origin of this software must not be misrepresented; you must not +// claim that you wrote the original software. If you use this software +// in a product, an acknowledgment in the product documentation would be +// appreciated but is not required. +// 2. Altered source versions must be plainly marked as such, and must not be +// misrepresented as being the original software. +// 3. This notice may not be removed or altered from any source distribution. +// + +#define _USE_MATH_DEFINES +#include +#include +#include "Recast.h" +#include "RecastAlloc.h" +#include "RecastAssert.h" + +inline bool overlapBounds(const float* amin, const float* amax, const float* bmin, const float* bmax) +{ + bool overlap = true; + overlap = (amin[0] > bmax[0] || amax[0] < bmin[0]) ? false : overlap; + overlap = (amin[1] > bmax[1] || amax[1] < bmin[1]) ? false : overlap; + overlap = (amin[2] > bmax[2] || amax[2] < bmin[2]) ? false : overlap; + return overlap; +} + +inline bool overlapInterval(unsigned short amin, unsigned short amax, + unsigned short bmin, unsigned short bmax) +{ + if (amax < bmin) return false; + if (amin > bmax) return false; + return true; +} + + +static rcSpan* allocSpan(rcHeightfield& hf) +{ + // If running out of memory, allocate new page and update the freelist. + if (!hf.freelist || !hf.freelist->next) + { + // Create new page. + // Allocate memory for the new pool. + rcSpanPool* pool = (rcSpanPool*)rcAlloc(sizeof(rcSpanPool), RC_ALLOC_PERM); + if (!pool) return 0; + pool->next = 0; + // Add the pool into the list of pools. + pool->next = hf.pools; + hf.pools = pool; + // Add new items to the free list. + rcSpan* freelist = hf.freelist; + rcSpan* head = &pool->items[0]; + rcSpan* it = &pool->items[RC_SPANS_PER_POOL]; + do + { + --it; + it->next = freelist; + freelist = it; + } + while (it != head); + hf.freelist = it; + } + + // Pop item from in front of the free list. + rcSpan* it = hf.freelist; + hf.freelist = hf.freelist->next; + return it; +} + +static void freeSpan(rcHeightfield& hf, rcSpan* ptr) +{ + if (!ptr) return; + // Add the node in front of the free list. + ptr->next = hf.freelist; + hf.freelist = ptr; +} + +static void addSpan(rcHeightfield& hf, const int x, const int y, + const unsigned short smin, const unsigned short smax, + const unsigned char area, const int flagMergeThr) +{ + + int idx = x + y*hf.width; + + rcSpan* s = allocSpan(hf); + s->smin = smin; + s->smax = smax; + s->area = area; + s->next = 0; + + // Empty cell, add he first span. + if (!hf.spans[idx]) + { + hf.spans[idx] = s; + return; + } + rcSpan* prev = 0; + rcSpan* cur = hf.spans[idx]; + + // Insert and merge spans. + while (cur) + { + if (cur->smin > s->smax) + { + // Current span is further than the new span, break. + break; + } + else if (cur->smax < s->smin) + { + // Current span is before the new span advance. + prev = cur; + cur = cur->next; + } + else + { + // Merge spans. + if (cur->smin < s->smin) + s->smin = cur->smin; + if (cur->smax > s->smax) + s->smax = cur->smax; + + // Merge flags. + if (rcAbs((int)s->smax - (int)cur->smax) <= flagMergeThr) + s->area = rcMax(s->area, cur->area); + + // Remove current span. + rcSpan* next = cur->next; + freeSpan(hf, cur); + if (prev) + prev->next = next; + else + hf.spans[idx] = next; + cur = next; + } + } + + // Insert new span. + if (prev) + { + s->next = prev->next; + prev->next = s; + } + else + { + s->next = hf.spans[idx]; + hf.spans[idx] = s; + } +} + +/// @par +/// +/// The span addition can be set to favor flags. If the span is merged to +/// another span and the new @p smax is within @p flagMergeThr units +/// from the existing span, the span flags are merged. +/// +/// @see rcHeightfield, rcSpan. +void rcAddSpan(rcContext* /*ctx*/, rcHeightfield& hf, const int x, const int y, + const unsigned short smin, const unsigned short smax, + const unsigned char area, const int flagMergeThr) +{ +// rcAssert(ctx); + addSpan(hf, x,y, smin, smax, area, flagMergeThr); +} + +static int clipPoly(const float* in, int n, float* out, float pnx, float pnz, float pd) +{ + float d[12]; + for (int i = 0; i < n; ++i) + d[i] = pnx*in[i*3+0] + pnz*in[i*3+2] + pd; + + int m = 0; + for (int i = 0, j = n-1; i < n; j=i, ++i) + { + bool ina = d[j] >= 0; + bool inb = d[i] >= 0; + if (ina != inb) + { + float s = d[j] / (d[j] - d[i]); + out[m*3+0] = in[j*3+0] + (in[i*3+0] - in[j*3+0])*s; + out[m*3+1] = in[j*3+1] + (in[i*3+1] - in[j*3+1])*s; + out[m*3+2] = in[j*3+2] + (in[i*3+2] - in[j*3+2])*s; + m++; + } + if (inb) + { + out[m*3+0] = in[i*3+0]; + out[m*3+1] = in[i*3+1]; + out[m*3+2] = in[i*3+2]; + m++; + } + } + return m; +} + +static void rasterizeTri(const float* v0, const float* v1, const float* v2, + const unsigned char area, rcHeightfield& hf, + const float* bmin, const float* bmax, + const float cs, const float ics, const float ich, + const int flagMergeThr) +{ + const int w = hf.width; + const int h = hf.height; + float tmin[3], tmax[3]; + const float by = bmax[1] - bmin[1]; + + // Calculate the bounding box of the triangle. + rcVcopy(tmin, v0); + rcVcopy(tmax, v0); + rcVmin(tmin, v1); + rcVmin(tmin, v2); + rcVmax(tmax, v1); + rcVmax(tmax, v2); + + // If the triangle does not touch the bbox of the heightfield, skip the triagle. + if (!overlapBounds(bmin, bmax, tmin, tmax)) + return; + + // Calculate the footpring of the triangle on the grid. + int x0 = (int)((tmin[0] - bmin[0])*ics); + int y0 = (int)((tmin[2] - bmin[2])*ics); + int x1 = (int)((tmax[0] - bmin[0])*ics); + int y1 = (int)((tmax[2] - bmin[2])*ics); + x0 = rcClamp(x0, 0, w-1); + y0 = rcClamp(y0, 0, h-1); + x1 = rcClamp(x1, 0, w-1); + y1 = rcClamp(y1, 0, h-1); + + // Clip the triangle into all grid cells it touches. + float in[7*3], out[7*3], inrow[7*3]; + + for (int y = y0; y <= y1; ++y) + { + // Clip polygon to row. + rcVcopy(&in[0], v0); + rcVcopy(&in[1*3], v1); + rcVcopy(&in[2*3], v2); + int nvrow = 3; + const float cz = bmin[2] + y*cs; + nvrow = clipPoly(in, nvrow, out, 0, 1, -cz); + if (nvrow < 3) continue; + nvrow = clipPoly(out, nvrow, inrow, 0, -1, cz+cs); + if (nvrow < 3) continue; + + for (int x = x0; x <= x1; ++x) + { + // Clip polygon to column. + int nv = nvrow; + const float cx = bmin[0] + x*cs; + nv = clipPoly(inrow, nv, out, 1, 0, -cx); + if (nv < 3) continue; + nv = clipPoly(out, nv, in, -1, 0, cx+cs); + if (nv < 3) continue; + + // Calculate min and max of the span. + float smin = in[1], smax = in[1]; + for (int i = 1; i < nv; ++i) + { + smin = rcMin(smin, in[i*3+1]); + smax = rcMax(smax, in[i*3+1]); + } + smin -= bmin[1]; + smax -= bmin[1]; + // Skip the span if it is outside the heightfield bbox + if (smax < 0.0f) continue; + if (smin > by) continue; + // Clamp the span to the heightfield bbox. + if (smin < 0.0f) smin = 0; + if (smax > by) smax = by; + + // Snap the span to the heightfield height grid. + unsigned short ismin = (unsigned short)rcClamp((int)floorf(smin * ich), 0, RC_SPAN_MAX_HEIGHT); + unsigned short ismax = (unsigned short)rcClamp((int)ceilf(smax * ich), (int)ismin+1, RC_SPAN_MAX_HEIGHT); + + addSpan(hf, x, y, ismin, ismax, area, flagMergeThr); + } + } +} + +/// @par +/// +/// No spans will be added if the triangle does not overlap the heightfield grid. +/// +/// @see rcHeightfield +void rcRasterizeTriangle(rcContext* ctx, const float* v0, const float* v1, const float* v2, + const unsigned char area, rcHeightfield& solid, + const int flagMergeThr) +{ + rcAssert(ctx); + + ctx->startTimer(RC_TIMER_RASTERIZE_TRIANGLES); + + const float ics = 1.0f/solid.cs; + const float ich = 1.0f/solid.ch; + rasterizeTri(v0, v1, v2, area, solid, solid.bmin, solid.bmax, solid.cs, ics, ich, flagMergeThr); + + ctx->stopTimer(RC_TIMER_RASTERIZE_TRIANGLES); +} + +/// @par +/// +/// Spans will only be added for triangles that overlap the heightfield grid. +/// +/// @see rcHeightfield +void rcRasterizeTriangles(rcContext* ctx, const float* verts, const int /*nv*/, + const int* tris, const unsigned char* areas, const int nt, + rcHeightfield& solid, const int flagMergeThr) +{ + rcAssert(ctx); + + ctx->startTimer(RC_TIMER_RASTERIZE_TRIANGLES); + + const float ics = 1.0f/solid.cs; + const float ich = 1.0f/solid.ch; + // Rasterize triangles. + for (int i = 0; i < nt; ++i) + { + const float* v0 = &verts[tris[i*3+0]*3]; + const float* v1 = &verts[tris[i*3+1]*3]; + const float* v2 = &verts[tris[i*3+2]*3]; + // Rasterize. + rasterizeTri(v0, v1, v2, areas[i], solid, solid.bmin, solid.bmax, solid.cs, ics, ich, flagMergeThr); + } + + ctx->stopTimer(RC_TIMER_RASTERIZE_TRIANGLES); +} + +/// @par +/// +/// Spans will only be added for triangles that overlap the heightfield grid. +/// +/// @see rcHeightfield +void rcRasterizeTriangles(rcContext* ctx, const float* verts, const int /*nv*/, + const unsigned short* tris, const unsigned char* areas, const int nt, + rcHeightfield& solid, const int flagMergeThr) +{ + rcAssert(ctx); + + ctx->startTimer(RC_TIMER_RASTERIZE_TRIANGLES); + + const float ics = 1.0f/solid.cs; + const float ich = 1.0f/solid.ch; + // Rasterize triangles. + for (int i = 0; i < nt; ++i) + { + const float* v0 = &verts[tris[i*3+0]*3]; + const float* v1 = &verts[tris[i*3+1]*3]; + const float* v2 = &verts[tris[i*3+2]*3]; + // Rasterize. + rasterizeTri(v0, v1, v2, areas[i], solid, solid.bmin, solid.bmax, solid.cs, ics, ich, flagMergeThr); + } + + ctx->stopTimer(RC_TIMER_RASTERIZE_TRIANGLES); +} + +/// @par +/// +/// Spans will only be added for triangles that overlap the heightfield grid. +/// +/// @see rcHeightfield +void rcRasterizeTriangles(rcContext* ctx, const float* verts, const unsigned char* areas, const int nt, + rcHeightfield& solid, const int flagMergeThr) +{ + rcAssert(ctx); + + ctx->startTimer(RC_TIMER_RASTERIZE_TRIANGLES); + + const float ics = 1.0f/solid.cs; + const float ich = 1.0f/solid.ch; + // Rasterize triangles. + for (int i = 0; i < nt; ++i) + { + const float* v0 = &verts[(i*3+0)*3]; + const float* v1 = &verts[(i*3+1)*3]; + const float* v2 = &verts[(i*3+2)*3]; + // Rasterize. + rasterizeTri(v0, v1, v2, areas[i], solid, solid.bmin, solid.bmax, solid.cs, ics, ich, flagMergeThr); + } + + ctx->stopTimer(RC_TIMER_RASTERIZE_TRIANGLES); +} diff --git a/KREngine/3rdparty/recast/source/RecastRegion.cpp b/KREngine/3rdparty/recast/source/RecastRegion.cpp new file mode 100755 index 0000000..76e631c --- /dev/null +++ b/KREngine/3rdparty/recast/source/RecastRegion.cpp @@ -0,0 +1,1337 @@ +// +// Copyright (c) 2009-2010 Mikko Mononen memon@inside.org +// +// This software is provided 'as-is', without any express or implied +// warranty. In no event will the authors be held liable for any damages +// arising from the use of this software. +// Permission is granted to anyone to use this software for any purpose, +// including commercial applications, and to alter it and redistribute it +// freely, subject to the following restrictions: +// 1. The origin of this software must not be misrepresented; you must not +// claim that you wrote the original software. If you use this software +// in a product, an acknowledgment in the product documentation would be +// appreciated but is not required. +// 2. Altered source versions must be plainly marked as such, and must not be +// misrepresented as being the original software. +// 3. This notice may not be removed or altered from any source distribution. +// + +#include +#define _USE_MATH_DEFINES +#include +#include +#include +#include +#include "Recast.h" +#include "RecastAlloc.h" +#include "RecastAssert.h" +#include + + +static void calculateDistanceField(rcCompactHeightfield& chf, unsigned short* src, unsigned short& maxDist) +{ + const int w = chf.width; + const int h = chf.height; + + // Init distance and points. + for (int i = 0; i < chf.spanCount; ++i) + src[i] = 0xffff; + + // Mark boundary cells. + for (int y = 0; y < h; ++y) + { + for (int x = 0; x < w; ++x) + { + const rcCompactCell& c = chf.cells[x+y*w]; + for (int i = (int)c.index, ni = (int)(c.index+c.count); i < ni; ++i) + { + const rcCompactSpan& s = chf.spans[i]; + const unsigned char area = chf.areas[i]; + + int nc = 0; + for (int dir = 0; dir < 4; ++dir) + { + if (rcGetCon(s, dir) != RC_NOT_CONNECTED) + { + const int ax = x + rcGetDirOffsetX(dir); + const int ay = y + rcGetDirOffsetY(dir); + const int ai = (int)chf.cells[ax+ay*w].index + rcGetCon(s, dir); + if (area == chf.areas[ai]) + nc++; + } + } + if (nc != 4) + src[i] = 0; + } + } + } + + + // Pass 1 + for (int y = 0; y < h; ++y) + { + for (int x = 0; x < w; ++x) + { + const rcCompactCell& c = chf.cells[x+y*w]; + for (int i = (int)c.index, ni = (int)(c.index+c.count); i < ni; ++i) + { + const rcCompactSpan& s = chf.spans[i]; + + if (rcGetCon(s, 0) != RC_NOT_CONNECTED) + { + // (-1,0) + const int ax = x + rcGetDirOffsetX(0); + const int ay = y + rcGetDirOffsetY(0); + const int ai = (int)chf.cells[ax+ay*w].index + rcGetCon(s, 0); + const rcCompactSpan& as = chf.spans[ai]; + if (src[ai]+2 < src[i]) + src[i] = src[ai]+2; + + // (-1,-1) + if (rcGetCon(as, 3) != RC_NOT_CONNECTED) + { + const int aax = ax + rcGetDirOffsetX(3); + const int aay = ay + rcGetDirOffsetY(3); + const int aai = (int)chf.cells[aax+aay*w].index + rcGetCon(as, 3); + if (src[aai]+3 < src[i]) + src[i] = src[aai]+3; + } + } + if (rcGetCon(s, 3) != RC_NOT_CONNECTED) + { + // (0,-1) + const int ax = x + rcGetDirOffsetX(3); + const int ay = y + rcGetDirOffsetY(3); + const int ai = (int)chf.cells[ax+ay*w].index + rcGetCon(s, 3); + const rcCompactSpan& as = chf.spans[ai]; + if (src[ai]+2 < src[i]) + src[i] = src[ai]+2; + + // (1,-1) + if (rcGetCon(as, 2) != RC_NOT_CONNECTED) + { + const int aax = ax + rcGetDirOffsetX(2); + const int aay = ay + rcGetDirOffsetY(2); + const int aai = (int)chf.cells[aax+aay*w].index + rcGetCon(as, 2); + if (src[aai]+3 < src[i]) + src[i] = src[aai]+3; + } + } + } + } + } + + // Pass 2 + for (int y = h-1; y >= 0; --y) + { + for (int x = w-1; x >= 0; --x) + { + const rcCompactCell& c = chf.cells[x+y*w]; + for (int i = (int)c.index, ni = (int)(c.index+c.count); i < ni; ++i) + { + const rcCompactSpan& s = chf.spans[i]; + + if (rcGetCon(s, 2) != RC_NOT_CONNECTED) + { + // (1,0) + const int ax = x + rcGetDirOffsetX(2); + const int ay = y + rcGetDirOffsetY(2); + const int ai = (int)chf.cells[ax+ay*w].index + rcGetCon(s, 2); + const rcCompactSpan& as = chf.spans[ai]; + if (src[ai]+2 < src[i]) + src[i] = src[ai]+2; + + // (1,1) + if (rcGetCon(as, 1) != RC_NOT_CONNECTED) + { + const int aax = ax + rcGetDirOffsetX(1); + const int aay = ay + rcGetDirOffsetY(1); + const int aai = (int)chf.cells[aax+aay*w].index + rcGetCon(as, 1); + if (src[aai]+3 < src[i]) + src[i] = src[aai]+3; + } + } + if (rcGetCon(s, 1) != RC_NOT_CONNECTED) + { + // (0,1) + const int ax = x + rcGetDirOffsetX(1); + const int ay = y + rcGetDirOffsetY(1); + const int ai = (int)chf.cells[ax+ay*w].index + rcGetCon(s, 1); + const rcCompactSpan& as = chf.spans[ai]; + if (src[ai]+2 < src[i]) + src[i] = src[ai]+2; + + // (-1,1) + if (rcGetCon(as, 0) != RC_NOT_CONNECTED) + { + const int aax = ax + rcGetDirOffsetX(0); + const int aay = ay + rcGetDirOffsetY(0); + const int aai = (int)chf.cells[aax+aay*w].index + rcGetCon(as, 0); + if (src[aai]+3 < src[i]) + src[i] = src[aai]+3; + } + } + } + } + } + + maxDist = 0; + for (int i = 0; i < chf.spanCount; ++i) + maxDist = rcMax(src[i], maxDist); + +} + +static unsigned short* boxBlur(rcCompactHeightfield& chf, int thr, + unsigned short* src, unsigned short* dst) +{ + const int w = chf.width; + const int h = chf.height; + + thr *= 2; + + for (int y = 0; y < h; ++y) + { + for (int x = 0; x < w; ++x) + { + const rcCompactCell& c = chf.cells[x+y*w]; + for (int i = (int)c.index, ni = (int)(c.index+c.count); i < ni; ++i) + { + const rcCompactSpan& s = chf.spans[i]; + const unsigned short cd = src[i]; + if (cd <= thr) + { + dst[i] = cd; + continue; + } + + int d = (int)cd; + for (int dir = 0; dir < 4; ++dir) + { + if (rcGetCon(s, dir) != RC_NOT_CONNECTED) + { + const int ax = x + rcGetDirOffsetX(dir); + const int ay = y + rcGetDirOffsetY(dir); + const int ai = (int)chf.cells[ax+ay*w].index + rcGetCon(s, dir); + d += (int)src[ai]; + + const rcCompactSpan& as = chf.spans[ai]; + const int dir2 = (dir+1) & 0x3; + if (rcGetCon(as, dir2) != RC_NOT_CONNECTED) + { + const int ax2 = ax + rcGetDirOffsetX(dir2); + const int ay2 = ay + rcGetDirOffsetY(dir2); + const int ai2 = (int)chf.cells[ax2+ay2*w].index + rcGetCon(as, dir2); + d += (int)src[ai2]; + } + else + { + d += cd; + } + } + else + { + d += cd*2; + } + } + dst[i] = (unsigned short)((d+5)/9); + } + } + } + return dst; +} + + +static bool floodRegion(int x, int y, int i, + unsigned short level, unsigned short r, + rcCompactHeightfield& chf, + unsigned short* srcReg, unsigned short* srcDist, + rcIntArray& stack) +{ + const int w = chf.width; + + const unsigned char area = chf.areas[i]; + + // Flood fill mark region. + stack.resize(0); + stack.push((int)x); + stack.push((int)y); + stack.push((int)i); + srcReg[i] = r; + srcDist[i] = 0; + + unsigned short lev = level >= 2 ? level-2 : 0; + int count = 0; + + while (stack.size() > 0) + { + int ci = stack.pop(); + int cy = stack.pop(); + int cx = stack.pop(); + + const rcCompactSpan& cs = chf.spans[ci]; + + // Check if any of the neighbours already have a valid region set. + unsigned short ar = 0; + for (int dir = 0; dir < 4; ++dir) + { + // 8 connected + if (rcGetCon(cs, dir) != RC_NOT_CONNECTED) + { + const int ax = cx + rcGetDirOffsetX(dir); + const int ay = cy + rcGetDirOffsetY(dir); + const int ai = (int)chf.cells[ax+ay*w].index + rcGetCon(cs, dir); + if (chf.areas[ai] != area) + continue; + unsigned short nr = srcReg[ai]; + if (nr & RC_BORDER_REG) // Do not take borders into account. + continue; + if (nr != 0 && nr != r) + ar = nr; + + const rcCompactSpan& as = chf.spans[ai]; + + const int dir2 = (dir+1) & 0x3; + if (rcGetCon(as, dir2) != RC_NOT_CONNECTED) + { + const int ax2 = ax + rcGetDirOffsetX(dir2); + const int ay2 = ay + rcGetDirOffsetY(dir2); + const int ai2 = (int)chf.cells[ax2+ay2*w].index + rcGetCon(as, dir2); + if (chf.areas[ai2] != area) + continue; + unsigned short nr2 = srcReg[ai2]; + if (nr2 != 0 && nr2 != r) + ar = nr2; + } + } + } + if (ar != 0) + { + srcReg[ci] = 0; + continue; + } + count++; + + // Expand neighbours. + for (int dir = 0; dir < 4; ++dir) + { + if (rcGetCon(cs, dir) != RC_NOT_CONNECTED) + { + const int ax = cx + rcGetDirOffsetX(dir); + const int ay = cy + rcGetDirOffsetY(dir); + const int ai = (int)chf.cells[ax+ay*w].index + rcGetCon(cs, dir); + if (chf.areas[ai] != area) + continue; + if (chf.dist[ai] >= lev && srcReg[ai] == 0) + { + srcReg[ai] = r; + srcDist[ai] = 0; + stack.push(ax); + stack.push(ay); + stack.push(ai); + } + } + } + } + + return count > 0; +} + +static unsigned short* expandRegions(int maxIter, unsigned short level, + rcCompactHeightfield& chf, + unsigned short* srcReg, unsigned short* srcDist, + unsigned short* dstReg, unsigned short* dstDist, + rcIntArray& stack) +{ + const int w = chf.width; + const int h = chf.height; + + // Find cells revealed by the raised level. + stack.resize(0); + for (int y = 0; y < h; ++y) + { + for (int x = 0; x < w; ++x) + { + const rcCompactCell& c = chf.cells[x+y*w]; + for (int i = (int)c.index, ni = (int)(c.index+c.count); i < ni; ++i) + { + if (chf.dist[i] >= level && srcReg[i] == 0 && chf.areas[i] != RC_NULL_AREA) + { + stack.push(x); + stack.push(y); + stack.push(i); + } + } + } + } + + int iter = 0; + while (stack.size() > 0) + { + int failed = 0; + + memcpy(dstReg, srcReg, sizeof(unsigned short)*chf.spanCount); + memcpy(dstDist, srcDist, sizeof(unsigned short)*chf.spanCount); + + for (int j = 0; j < stack.size(); j += 3) + { + int x = stack[j+0]; + int y = stack[j+1]; + int i = stack[j+2]; + if (i < 0) + { + failed++; + continue; + } + + unsigned short r = srcReg[i]; + unsigned short d2 = 0xffff; + const unsigned char area = chf.areas[i]; + const rcCompactSpan& s = chf.spans[i]; + for (int dir = 0; dir < 4; ++dir) + { + if (rcGetCon(s, dir) == RC_NOT_CONNECTED) continue; + const int ax = x + rcGetDirOffsetX(dir); + const int ay = y + rcGetDirOffsetY(dir); + const int ai = (int)chf.cells[ax+ay*w].index + rcGetCon(s, dir); + if (chf.areas[ai] != area) continue; + if (srcReg[ai] > 0 && (srcReg[ai] & RC_BORDER_REG) == 0) + { + if ((int)srcDist[ai]+2 < (int)d2) + { + r = srcReg[ai]; + d2 = srcDist[ai]+2; + } + } + } + if (r) + { + stack[j+2] = -1; // mark as used + dstReg[i] = r; + dstDist[i] = d2; + } + else + { + failed++; + } + } + + // rcSwap source and dest. + rcSwap(srcReg, dstReg); + rcSwap(srcDist, dstDist); + + if (failed*3 == stack.size()) + break; + + if (level > 0) + { + ++iter; + if (iter >= maxIter) + break; + } + } + + return srcReg; +} + + +struct rcRegion +{ + inline rcRegion(unsigned short i) : + spanCount(0), + id(i), + areaType(0), + remap(false), + visited(false) + {} + + int spanCount; // Number of spans belonging to this region + unsigned short id; // ID of the region + unsigned char areaType; // Are type. + bool remap; + bool visited; + rcIntArray connections; + rcIntArray floors; +}; + +static void removeAdjacentNeighbours(rcRegion& reg) +{ + // Remove adjacent duplicates. + for (int i = 0; i < reg.connections.size() && reg.connections.size() > 1; ) + { + int ni = (i+1) % reg.connections.size(); + if (reg.connections[i] == reg.connections[ni]) + { + // Remove duplicate + for (int j = i; j < reg.connections.size()-1; ++j) + reg.connections[j] = reg.connections[j+1]; + reg.connections.pop(); + } + else + ++i; + } +} + +static void replaceNeighbour(rcRegion& reg, unsigned short oldId, unsigned short newId) +{ + bool neiChanged = false; + for (int i = 0; i < reg.connections.size(); ++i) + { + if (reg.connections[i] == oldId) + { + reg.connections[i] = newId; + neiChanged = true; + } + } + for (int i = 0; i < reg.floors.size(); ++i) + { + if (reg.floors[i] == oldId) + reg.floors[i] = newId; + } + if (neiChanged) + removeAdjacentNeighbours(reg); +} + +static bool canMergeWithRegion(const rcRegion& rega, const rcRegion& regb) +{ + if (rega.areaType != regb.areaType) + return false; + int n = 0; + for (int i = 0; i < rega.connections.size(); ++i) + { + if (rega.connections[i] == regb.id) + n++; + } + if (n > 1) + return false; + for (int i = 0; i < rega.floors.size(); ++i) + { + if (rega.floors[i] == regb.id) + return false; + } + return true; +} + +static void addUniqueFloorRegion(rcRegion& reg, int n) +{ + for (int i = 0; i < reg.floors.size(); ++i) + if (reg.floors[i] == n) + return; + reg.floors.push(n); +} + +static bool mergeRegions(rcRegion& rega, rcRegion& regb) +{ + unsigned short aid = rega.id; + unsigned short bid = regb.id; + + // Duplicate current neighbourhood. + rcIntArray acon; + acon.resize(rega.connections.size()); + for (int i = 0; i < rega.connections.size(); ++i) + acon[i] = rega.connections[i]; + rcIntArray& bcon = regb.connections; + + // Find insertion point on A. + int insa = -1; + for (int i = 0; i < acon.size(); ++i) + { + if (acon[i] == bid) + { + insa = i; + break; + } + } + if (insa == -1) + return false; + + // Find insertion point on B. + int insb = -1; + for (int i = 0; i < bcon.size(); ++i) + { + if (bcon[i] == aid) + { + insb = i; + break; + } + } + if (insb == -1) + return false; + + // Merge neighbours. + rega.connections.resize(0); + for (int i = 0, ni = acon.size(); i < ni-1; ++i) + rega.connections.push(acon[(insa+1+i) % ni]); + + for (int i = 0, ni = bcon.size(); i < ni-1; ++i) + rega.connections.push(bcon[(insb+1+i) % ni]); + + removeAdjacentNeighbours(rega); + + for (int j = 0; j < regb.floors.size(); ++j) + addUniqueFloorRegion(rega, regb.floors[j]); + rega.spanCount += regb.spanCount; + regb.spanCount = 0; + regb.connections.resize(0); + + return true; +} + +static bool isRegionConnectedToBorder(const rcRegion& reg) +{ + // Region is connected to border if + // one of the neighbours is null id. + for (int i = 0; i < reg.connections.size(); ++i) + { + if (reg.connections[i] == 0) + return true; + } + return false; +} + +static bool isSolidEdge(rcCompactHeightfield& chf, unsigned short* srcReg, + int x, int y, int i, int dir) +{ + const rcCompactSpan& s = chf.spans[i]; + unsigned short r = 0; + if (rcGetCon(s, dir) != RC_NOT_CONNECTED) + { + const int ax = x + rcGetDirOffsetX(dir); + const int ay = y + rcGetDirOffsetY(dir); + const int ai = (int)chf.cells[ax+ay*chf.width].index + rcGetCon(s, dir); + r = srcReg[ai]; + } + if (r == srcReg[i]) + return false; + return true; +} + +static void walkContour(int x, int y, int i, int dir, + rcCompactHeightfield& chf, + unsigned short* srcReg, + rcIntArray& cont) +{ + int startDir = dir; + int starti = i; + + const rcCompactSpan& ss = chf.spans[i]; + unsigned short curReg = 0; + if (rcGetCon(ss, dir) != RC_NOT_CONNECTED) + { + const int ax = x + rcGetDirOffsetX(dir); + const int ay = y + rcGetDirOffsetY(dir); + const int ai = (int)chf.cells[ax+ay*chf.width].index + rcGetCon(ss, dir); + curReg = srcReg[ai]; + } + cont.push(curReg); + + int iter = 0; + while (++iter < 40000) + { + const rcCompactSpan& s = chf.spans[i]; + + if (isSolidEdge(chf, srcReg, x, y, i, dir)) + { + // Choose the edge corner + unsigned short r = 0; + if (rcGetCon(s, dir) != RC_NOT_CONNECTED) + { + const int ax = x + rcGetDirOffsetX(dir); + const int ay = y + rcGetDirOffsetY(dir); + const int ai = (int)chf.cells[ax+ay*chf.width].index + rcGetCon(s, dir); + r = srcReg[ai]; + } + if (r != curReg) + { + curReg = r; + cont.push(curReg); + } + + dir = (dir+1) & 0x3; // Rotate CW + } + else + { + int ni = -1; + const int nx = x + rcGetDirOffsetX(dir); + const int ny = y + rcGetDirOffsetY(dir); + if (rcGetCon(s, dir) != RC_NOT_CONNECTED) + { + const rcCompactCell& nc = chf.cells[nx+ny*chf.width]; + ni = (int)nc.index + rcGetCon(s, dir); + } + if (ni == -1) + { + // Should not happen. + return; + } + x = nx; + y = ny; + i = ni; + dir = (dir+3) & 0x3; // Rotate CCW + } + + if (starti == i && startDir == dir) + { + break; + } + } + + // Remove adjacent duplicates. + if (cont.size() > 1) + { + for (int j = 0; j < cont.size(); ) + { + int nj = (j+1) % cont.size(); + if (cont[j] == cont[nj]) + { + for (int k = j; k < cont.size()-1; ++k) + cont[k] = cont[k+1]; + cont.pop(); + } + else + ++j; + } + } +} + +static bool filterSmallRegions(rcContext* ctx, int minRegionArea, int mergeRegionSize, + unsigned short& maxRegionId, + rcCompactHeightfield& chf, + unsigned short* srcReg) +{ + const int w = chf.width; + const int h = chf.height; + + const int nreg = maxRegionId+1; + rcRegion* regions = (rcRegion*)rcAlloc(sizeof(rcRegion)*nreg, RC_ALLOC_TEMP); + if (!regions) + { + ctx->log(RC_LOG_ERROR, "filterSmallRegions: Out of memory 'regions' (%d).", nreg); + return false; + } + + // Construct regions + for (int i = 0; i < nreg; ++i) + new(®ions[i]) rcRegion((unsigned short)i); + + // Find edge of a region and find connections around the contour. + for (int y = 0; y < h; ++y) + { + for (int x = 0; x < w; ++x) + { + const rcCompactCell& c = chf.cells[x+y*w]; + for (int i = (int)c.index, ni = (int)(c.index+c.count); i < ni; ++i) + { + unsigned short r = srcReg[i]; + if (r == 0 || r >= nreg) + continue; + + rcRegion& reg = regions[r]; + reg.spanCount++; + + + // Update floors. + for (int j = (int)c.index; j < ni; ++j) + { + if (i == j) continue; + unsigned short floorId = srcReg[j]; + if (floorId == 0 || floorId >= nreg) + continue; + addUniqueFloorRegion(reg, floorId); + } + + // Have found contour + if (reg.connections.size() > 0) + continue; + + reg.areaType = chf.areas[i]; + + // Check if this cell is next to a border. + int ndir = -1; + for (int dir = 0; dir < 4; ++dir) + { + if (isSolidEdge(chf, srcReg, x, y, i, dir)) + { + ndir = dir; + break; + } + } + + if (ndir != -1) + { + // The cell is at border. + // Walk around the contour to find all the neighbours. + walkContour(x, y, i, ndir, chf, srcReg, reg.connections); + } + } + } + } + + // Remove too small regions. + rcIntArray stack(32); + rcIntArray trace(32); + for (int i = 0; i < nreg; ++i) + { + rcRegion& reg = regions[i]; + if (reg.id == 0 || (reg.id & RC_BORDER_REG)) + continue; + if (reg.spanCount == 0) + continue; + if (reg.visited) + continue; + + // Count the total size of all the connected regions. + // Also keep track of the regions connects to a tile border. + bool connectsToBorder = false; + int spanCount = 0; + stack.resize(0); + trace.resize(0); + + reg.visited = true; + stack.push(i); + + while (stack.size()) + { + // Pop + int ri = stack.pop(); + + rcRegion& creg = regions[ri]; + + spanCount += creg.spanCount; + trace.push(ri); + + for (int j = 0; j < creg.connections.size(); ++j) + { + if (creg.connections[j] & RC_BORDER_REG) + { + connectsToBorder = true; + continue; + } + rcRegion& neireg = regions[creg.connections[j]]; + if (neireg.visited) + continue; + if (neireg.id == 0 || (neireg.id & RC_BORDER_REG)) + continue; + // Visit + stack.push(neireg.id); + neireg.visited = true; + } + } + + // If the accumulated regions size is too small, remove it. + // Do not remove areas which connect to tile borders + // as their size cannot be estimated correctly and removing them + // can potentially remove necessary areas. + if (spanCount < minRegionArea && !connectsToBorder) + { + // Kill all visited regions. + for (int j = 0; j < trace.size(); ++j) + { + regions[trace[j]].spanCount = 0; + regions[trace[j]].id = 0; + } + } + } + + // Merge too small regions to neighbour regions. + int mergeCount = 0 ; + do + { + mergeCount = 0; + for (int i = 0; i < nreg; ++i) + { + rcRegion& reg = regions[i]; + if (reg.id == 0 || (reg.id & RC_BORDER_REG)) + continue; + if (reg.spanCount == 0) + continue; + + // Check to see if the region should be merged. + if (reg.spanCount > mergeRegionSize && isRegionConnectedToBorder(reg)) + continue; + + // Small region with more than 1 connection. + // Or region which is not connected to a border at all. + // Find smallest neighbour region that connects to this one. + int smallest = 0xfffffff; + unsigned short mergeId = reg.id; + for (int j = 0; j < reg.connections.size(); ++j) + { + if (reg.connections[j] & RC_BORDER_REG) continue; + rcRegion& mreg = regions[reg.connections[j]]; + if (mreg.id == 0 || (mreg.id & RC_BORDER_REG)) continue; + if (mreg.spanCount < smallest && + canMergeWithRegion(reg, mreg) && + canMergeWithRegion(mreg, reg)) + { + smallest = mreg.spanCount; + mergeId = mreg.id; + } + } + // Found new id. + if (mergeId != reg.id) + { + unsigned short oldId = reg.id; + rcRegion& target = regions[mergeId]; + + // Merge neighbours. + if (mergeRegions(target, reg)) + { + // Fixup regions pointing to current region. + for (int j = 0; j < nreg; ++j) + { + if (regions[j].id == 0 || (regions[j].id & RC_BORDER_REG)) continue; + // If another region was already merged into current region + // change the nid of the previous region too. + if (regions[j].id == oldId) + regions[j].id = mergeId; + // Replace the current region with the new one if the + // current regions is neighbour. + replaceNeighbour(regions[j], oldId, mergeId); + } + mergeCount++; + } + } + } + } + while (mergeCount > 0); + + // Compress region Ids. + for (int i = 0; i < nreg; ++i) + { + regions[i].remap = false; + if (regions[i].id == 0) continue; // Skip nil regions. + if (regions[i].id & RC_BORDER_REG) continue; // Skip external regions. + regions[i].remap = true; + } + + unsigned short regIdGen = 0; + for (int i = 0; i < nreg; ++i) + { + if (!regions[i].remap) + continue; + unsigned short oldId = regions[i].id; + unsigned short newId = ++regIdGen; + for (int j = i; j < nreg; ++j) + { + if (regions[j].id == oldId) + { + regions[j].id = newId; + regions[j].remap = false; + } + } + } + maxRegionId = regIdGen; + + // Remap regions. + for (int i = 0; i < chf.spanCount; ++i) + { + if ((srcReg[i] & RC_BORDER_REG) == 0) + srcReg[i] = regions[srcReg[i]].id; + } + + for (int i = 0; i < nreg; ++i) + regions[i].~rcRegion(); + rcFree(regions); + + return true; +} + +/// @par +/// +/// This is usually the second to the last step in creating a fully built +/// compact heightfield. This step is required before regions are built +/// using #rcBuildRegions or #rcBuildRegionsMonotone. +/// +/// After this step, the distance data is available via the rcCompactHeightfield::maxDistance +/// and rcCompactHeightfield::dist fields. +/// +/// @see rcCompactHeightfield, rcBuildRegions, rcBuildRegionsMonotone +bool rcBuildDistanceField(rcContext* ctx, rcCompactHeightfield& chf) +{ + rcAssert(ctx); + + ctx->startTimer(RC_TIMER_BUILD_DISTANCEFIELD); + + if (chf.dist) + { + rcFree(chf.dist); + chf.dist = 0; + } + + unsigned short* src = (unsigned short*)rcAlloc(sizeof(unsigned short)*chf.spanCount, RC_ALLOC_TEMP); + if (!src) + { + ctx->log(RC_LOG_ERROR, "rcBuildDistanceField: Out of memory 'src' (%d).", chf.spanCount); + return false; + } + unsigned short* dst = (unsigned short*)rcAlloc(sizeof(unsigned short)*chf.spanCount, RC_ALLOC_TEMP); + if (!dst) + { + ctx->log(RC_LOG_ERROR, "rcBuildDistanceField: Out of memory 'dst' (%d).", chf.spanCount); + rcFree(src); + return false; + } + + unsigned short maxDist = 0; + + ctx->startTimer(RC_TIMER_BUILD_DISTANCEFIELD_DIST); + + calculateDistanceField(chf, src, maxDist); + chf.maxDistance = maxDist; + + ctx->stopTimer(RC_TIMER_BUILD_DISTANCEFIELD_DIST); + + ctx->startTimer(RC_TIMER_BUILD_DISTANCEFIELD_BLUR); + + // Blur + if (boxBlur(chf, 1, src, dst) != src) + rcSwap(src, dst); + + // Store distance. + chf.dist = src; + + ctx->stopTimer(RC_TIMER_BUILD_DISTANCEFIELD_BLUR); + + ctx->stopTimer(RC_TIMER_BUILD_DISTANCEFIELD); + + rcFree(dst); + + return true; +} + +static void paintRectRegion(int minx, int maxx, int miny, int maxy, unsigned short regId, + rcCompactHeightfield& chf, unsigned short* srcReg) +{ + const int w = chf.width; + for (int y = miny; y < maxy; ++y) + { + for (int x = minx; x < maxx; ++x) + { + const rcCompactCell& c = chf.cells[x+y*w]; + for (int i = (int)c.index, ni = (int)(c.index+c.count); i < ni; ++i) + { + if (chf.areas[i] != RC_NULL_AREA) + srcReg[i] = regId; + } + } + } +} + + +static const unsigned short RC_NULL_NEI = 0xffff; + +struct rcSweepSpan +{ + unsigned short rid; // row id + unsigned short id; // region id + unsigned short ns; // number samples + unsigned short nei; // neighbour id +}; + +/// @par +/// +/// Non-null regions will consist of connected, non-overlapping walkable spans that form a single contour. +/// Contours will form simple polygons. +/// +/// If multiple regions form an area that is smaller than @p minRegionArea, then all spans will be +/// re-assigned to the zero (null) region. +/// +/// Partitioning can result in smaller than necessary regions. @p mergeRegionArea helps +/// reduce unecessarily small regions. +/// +/// See the #rcConfig documentation for more information on the configuration parameters. +/// +/// The region data will be available via the rcCompactHeightfield::maxRegions +/// and rcCompactSpan::reg fields. +/// +/// @warning The distance field must be created using #rcBuildDistanceField before attempting to build regions. +/// +/// @see rcCompactHeightfield, rcCompactSpan, rcBuildDistanceField, rcBuildRegionsMonotone, rcConfig +bool rcBuildRegionsMonotone(rcContext* ctx, rcCompactHeightfield& chf, + const int borderSize, const int minRegionArea, const int mergeRegionArea) +{ + rcAssert(ctx); + + ctx->startTimer(RC_TIMER_BUILD_REGIONS); + + const int w = chf.width; + const int h = chf.height; + unsigned short id = 1; + + rcScopedDelete srcReg = (unsigned short*)rcAlloc(sizeof(unsigned short)*chf.spanCount, RC_ALLOC_TEMP); + if (!srcReg) + { + ctx->log(RC_LOG_ERROR, "rcBuildRegionsMonotone: Out of memory 'src' (%d).", chf.spanCount); + return false; + } + memset(srcReg,0,sizeof(unsigned short)*chf.spanCount); + + const int nsweeps = rcMax(chf.width,chf.height); + rcScopedDelete sweeps = (rcSweepSpan*)rcAlloc(sizeof(rcSweepSpan)*nsweeps, RC_ALLOC_TEMP); + if (!sweeps) + { + ctx->log(RC_LOG_ERROR, "rcBuildRegionsMonotone: Out of memory 'sweeps' (%d).", nsweeps); + return false; + } + + + // Mark border regions. + if (borderSize > 0) + { + // Make sure border will not overflow. + const int bw = rcMin(w, borderSize); + const int bh = rcMin(h, borderSize); + // Paint regions + paintRectRegion(0, bw, 0, h, id|RC_BORDER_REG, chf, srcReg); id++; + paintRectRegion(w-bw, w, 0, h, id|RC_BORDER_REG, chf, srcReg); id++; + paintRectRegion(0, w, 0, bh, id|RC_BORDER_REG, chf, srcReg); id++; + paintRectRegion(0, w, h-bh, h, id|RC_BORDER_REG, chf, srcReg); id++; + + chf.borderSize = borderSize; + } + + rcIntArray prev(256); + + // Sweep one line at a time. + for (int y = borderSize; y < h-borderSize; ++y) + { + // Collect spans from this row. + prev.resize(id+1); + memset(&prev[0],0,sizeof(int)*id); + unsigned short rid = 1; + + for (int x = borderSize; x < w-borderSize; ++x) + { + const rcCompactCell& c = chf.cells[x+y*w]; + + for (int i = (int)c.index, ni = (int)(c.index+c.count); i < ni; ++i) + { + const rcCompactSpan& s = chf.spans[i]; + if (chf.areas[i] == RC_NULL_AREA) continue; + + // -x + unsigned short previd = 0; + if (rcGetCon(s, 0) != RC_NOT_CONNECTED) + { + const int ax = x + rcGetDirOffsetX(0); + const int ay = y + rcGetDirOffsetY(0); + const int ai = (int)chf.cells[ax+ay*w].index + rcGetCon(s, 0); + if ((srcReg[ai] & RC_BORDER_REG) == 0 && chf.areas[i] == chf.areas[ai]) + previd = srcReg[ai]; + } + + if (!previd) + { + previd = rid++; + sweeps[previd].rid = previd; + sweeps[previd].ns = 0; + sweeps[previd].nei = 0; + } + + // -y + if (rcGetCon(s,3) != RC_NOT_CONNECTED) + { + const int ax = x + rcGetDirOffsetX(3); + const int ay = y + rcGetDirOffsetY(3); + const int ai = (int)chf.cells[ax+ay*w].index + rcGetCon(s, 3); + if (srcReg[ai] && (srcReg[ai] & RC_BORDER_REG) == 0 && chf.areas[i] == chf.areas[ai]) + { + unsigned short nr = srcReg[ai]; + if (!sweeps[previd].nei || sweeps[previd].nei == nr) + { + sweeps[previd].nei = nr; + sweeps[previd].ns++; + prev[nr]++; + } + else + { + sweeps[previd].nei = RC_NULL_NEI; + } + } + } + + srcReg[i] = previd; + } + } + + // Create unique ID. + for (int i = 1; i < rid; ++i) + { + if (sweeps[i].nei != RC_NULL_NEI && sweeps[i].nei != 0 && + prev[sweeps[i].nei] == (int)sweeps[i].ns) + { + sweeps[i].id = sweeps[i].nei; + } + else + { + sweeps[i].id = id++; + } + } + + // Remap IDs + for (int x = borderSize; x < w-borderSize; ++x) + { + const rcCompactCell& c = chf.cells[x+y*w]; + + for (int i = (int)c.index, ni = (int)(c.index+c.count); i < ni; ++i) + { + if (srcReg[i] > 0 && srcReg[i] < rid) + srcReg[i] = sweeps[srcReg[i]].id; + } + } + } + + ctx->startTimer(RC_TIMER_BUILD_REGIONS_FILTER); + + // Filter out small regions. + chf.maxRegions = id; + if (!filterSmallRegions(ctx, minRegionArea, mergeRegionArea, chf.maxRegions, chf, srcReg)) + return false; + + ctx->stopTimer(RC_TIMER_BUILD_REGIONS_FILTER); + + // Store the result out. + for (int i = 0; i < chf.spanCount; ++i) + chf.spans[i].reg = srcReg[i]; + + ctx->stopTimer(RC_TIMER_BUILD_REGIONS); + + return true; +} + +/// @par +/// +/// Non-null regions will consist of connected, non-overlapping walkable spans that form a single contour. +/// Contours will form simple polygons. +/// +/// If multiple regions form an area that is smaller than @p minRegionArea, then all spans will be +/// re-assigned to the zero (null) region. +/// +/// Watershed partitioning can result in smaller than necessary regions, especially in diagonal corridors. +/// @p mergeRegionArea helps reduce unecessarily small regions. +/// +/// See the #rcConfig documentation for more information on the configuration parameters. +/// +/// The region data will be available via the rcCompactHeightfield::maxRegions +/// and rcCompactSpan::reg fields. +/// +/// @warning The distance field must be created using #rcBuildDistanceField before attempting to build regions. +/// +/// @see rcCompactHeightfield, rcCompactSpan, rcBuildDistanceField, rcBuildRegionsMonotone, rcConfig +bool rcBuildRegions(rcContext* ctx, rcCompactHeightfield& chf, + const int borderSize, const int minRegionArea, const int mergeRegionArea) +{ + rcAssert(ctx); + + ctx->startTimer(RC_TIMER_BUILD_REGIONS); + + const int w = chf.width; + const int h = chf.height; + + rcScopedDelete buf = (unsigned short*)rcAlloc(sizeof(unsigned short)*chf.spanCount*4, RC_ALLOC_TEMP); + if (!buf) + { + ctx->log(RC_LOG_ERROR, "rcBuildRegions: Out of memory 'tmp' (%d).", chf.spanCount*4); + return false; + } + + ctx->startTimer(RC_TIMER_BUILD_REGIONS_WATERSHED); + + rcIntArray stack(1024); + rcIntArray visited(1024); + + unsigned short* srcReg = buf; + unsigned short* srcDist = buf+chf.spanCount; + unsigned short* dstReg = buf+chf.spanCount*2; + unsigned short* dstDist = buf+chf.spanCount*3; + + memset(srcReg, 0, sizeof(unsigned short)*chf.spanCount); + memset(srcDist, 0, sizeof(unsigned short)*chf.spanCount); + + unsigned short regionId = 1; + unsigned short level = (chf.maxDistance+1) & ~1; + + // TODO: Figure better formula, expandIters defines how much the + // watershed "overflows" and simplifies the regions. Tying it to + // agent radius was usually good indication how greedy it could be. +// const int expandIters = 4 + walkableRadius * 2; + const int expandIters = 8; + + if (borderSize > 0) + { + // Make sure border will not overflow. + const int bw = rcMin(w, borderSize); + const int bh = rcMin(h, borderSize); + // Paint regions + paintRectRegion(0, bw, 0, h, regionId|RC_BORDER_REG, chf, srcReg); regionId++; + paintRectRegion(w-bw, w, 0, h, regionId|RC_BORDER_REG, chf, srcReg); regionId++; + paintRectRegion(0, w, 0, bh, regionId|RC_BORDER_REG, chf, srcReg); regionId++; + paintRectRegion(0, w, h-bh, h, regionId|RC_BORDER_REG, chf, srcReg); regionId++; + + chf.borderSize = borderSize; + } + + while (level > 0) + { + level = level >= 2 ? level-2 : 0; + + ctx->startTimer(RC_TIMER_BUILD_REGIONS_EXPAND); + + // Expand current regions until no empty connected cells found. + if (expandRegions(expandIters, level, chf, srcReg, srcDist, dstReg, dstDist, stack) != srcReg) + { + rcSwap(srcReg, dstReg); + rcSwap(srcDist, dstDist); + } + + ctx->stopTimer(RC_TIMER_BUILD_REGIONS_EXPAND); + + ctx->startTimer(RC_TIMER_BUILD_REGIONS_FLOOD); + + // Mark new regions with IDs. + for (int y = 0; y < h; ++y) + { + for (int x = 0; x < w; ++x) + { + const rcCompactCell& c = chf.cells[x+y*w]; + for (int i = (int)c.index, ni = (int)(c.index+c.count); i < ni; ++i) + { + if (chf.dist[i] < level || srcReg[i] != 0 || chf.areas[i] == RC_NULL_AREA) + continue; + if (floodRegion(x, y, i, level, regionId, chf, srcReg, srcDist, stack)) + regionId++; + } + } + } + + ctx->stopTimer(RC_TIMER_BUILD_REGIONS_FLOOD); + } + + // Expand current regions until no empty connected cells found. + if (expandRegions(expandIters*8, 0, chf, srcReg, srcDist, dstReg, dstDist, stack) != srcReg) + { + rcSwap(srcReg, dstReg); + rcSwap(srcDist, dstDist); + } + + ctx->stopTimer(RC_TIMER_BUILD_REGIONS_WATERSHED); + + ctx->startTimer(RC_TIMER_BUILD_REGIONS_FILTER); + + // Filter out small regions. + chf.maxRegions = regionId; + if (!filterSmallRegions(ctx, minRegionArea, mergeRegionArea, chf.maxRegions, chf, srcReg)) + return false; + + ctx->stopTimer(RC_TIMER_BUILD_REGIONS_FILTER); + + // Write the result out. + for (int i = 0; i < chf.spanCount; ++i) + chf.spans[i].reg = srcReg[i]; + + ctx->stopTimer(RC_TIMER_BUILD_REGIONS); + + return true; +} + + diff --git a/KREngine/kraken/tinyxml2.cpp b/KREngine/3rdparty/tinyxml2/tinyxml2.cpp similarity index 100% rename from KREngine/kraken/tinyxml2.cpp rename to KREngine/3rdparty/tinyxml2/tinyxml2.cpp diff --git a/KREngine/kraken/tinyxml2.h b/KREngine/3rdparty/tinyxml2/tinyxml2.h similarity index 100% rename from KREngine/kraken/tinyxml2.h rename to KREngine/3rdparty/tinyxml2/tinyxml2.h diff --git a/KREngine/kraken/tinyxml2_readme.txt b/KREngine/3rdparty/tinyxml2/tinyxml2_readme.txt similarity index 100% rename from KREngine/kraken/tinyxml2_readme.txt rename to KREngine/3rdparty/tinyxml2/tinyxml2_readme.txt diff --git a/KREngine/Kraken.xcodeproj/project.pbxproj b/KREngine/Kraken.xcodeproj/project.pbxproj index 18e49e4..61a5b5e 100644 --- a/KREngine/Kraken.xcodeproj/project.pbxproj +++ b/KREngine/Kraken.xcodeproj/project.pbxproj @@ -44,6 +44,32 @@ E416AA9A16713749000F6786 /* KRAnimationCurveManager.h in Headers */ = {isa = PBXBuildFile; fileRef = E416AA9816713749000F6786 /* KRAnimationCurveManager.h */; settings = {ATTRIBUTES = (Public, ); }; }; E416AA9C1671375C000F6786 /* KRAnimationCurveManager.cpp in Sources */ = {isa = PBXBuildFile; fileRef = E416AA9B1671375C000F6786 /* KRAnimationCurveManager.cpp */; }; E416AA9D1671375C000F6786 /* KRAnimationCurveManager.cpp in Sources */ = {isa = PBXBuildFile; fileRef = E416AA9B1671375C000F6786 /* KRAnimationCurveManager.cpp */; }; + E416E4881879111300FC2EEB /* Recast.h in Headers */ = {isa = PBXBuildFile; fileRef = E416E4851879111300FC2EEB /* Recast.h */; }; + E416E4891879111300FC2EEB /* Recast.h in Headers */ = {isa = PBXBuildFile; fileRef = E416E4851879111300FC2EEB /* Recast.h */; }; + E416E48A1879111300FC2EEB /* RecastAlloc.h in Headers */ = {isa = PBXBuildFile; fileRef = E416E4861879111300FC2EEB /* RecastAlloc.h */; }; + E416E48B1879111300FC2EEB /* RecastAlloc.h in Headers */ = {isa = PBXBuildFile; fileRef = E416E4861879111300FC2EEB /* RecastAlloc.h */; }; + E416E48C1879111300FC2EEB /* RecastAssert.h in Headers */ = {isa = PBXBuildFile; fileRef = E416E4871879111300FC2EEB /* RecastAssert.h */; }; + E416E48D1879111300FC2EEB /* RecastAssert.h in Headers */ = {isa = PBXBuildFile; fileRef = E416E4871879111300FC2EEB /* RecastAssert.h */; }; + E416E4981879112700FC2EEB /* Recast.cpp in Sources */ = {isa = PBXBuildFile; fileRef = E416E48E1879112700FC2EEB /* Recast.cpp */; }; + E416E4991879112700FC2EEB /* Recast.cpp in Sources */ = {isa = PBXBuildFile; fileRef = E416E48E1879112700FC2EEB /* Recast.cpp */; }; + E416E49A1879112700FC2EEB /* RecastAlloc.cpp in Sources */ = {isa = PBXBuildFile; fileRef = E416E48F1879112700FC2EEB /* RecastAlloc.cpp */; }; + E416E49B1879112700FC2EEB /* RecastAlloc.cpp in Sources */ = {isa = PBXBuildFile; fileRef = E416E48F1879112700FC2EEB /* RecastAlloc.cpp */; }; + E416E49C1879112700FC2EEB /* RecastArea.cpp in Sources */ = {isa = PBXBuildFile; fileRef = E416E4901879112700FC2EEB /* RecastArea.cpp */; }; + E416E49D1879112700FC2EEB /* RecastArea.cpp in Sources */ = {isa = PBXBuildFile; fileRef = E416E4901879112700FC2EEB /* RecastArea.cpp */; }; + E416E49E1879112700FC2EEB /* RecastContour.cpp in Sources */ = {isa = PBXBuildFile; fileRef = E416E4911879112700FC2EEB /* RecastContour.cpp */; }; + E416E49F1879112700FC2EEB /* RecastContour.cpp in Sources */ = {isa = PBXBuildFile; fileRef = E416E4911879112700FC2EEB /* RecastContour.cpp */; }; + E416E4A01879112700FC2EEB /* RecastFilter.cpp in Sources */ = {isa = PBXBuildFile; fileRef = E416E4921879112700FC2EEB /* RecastFilter.cpp */; }; + E416E4A11879112700FC2EEB /* RecastFilter.cpp in Sources */ = {isa = PBXBuildFile; fileRef = E416E4921879112700FC2EEB /* RecastFilter.cpp */; }; + E416E4A21879112700FC2EEB /* RecastLayers.cpp in Sources */ = {isa = PBXBuildFile; fileRef = E416E4931879112700FC2EEB /* RecastLayers.cpp */; }; + E416E4A31879112700FC2EEB /* RecastLayers.cpp in Sources */ = {isa = PBXBuildFile; fileRef = E416E4931879112700FC2EEB /* RecastLayers.cpp */; }; + E416E4A41879112700FC2EEB /* RecastMesh.cpp in Sources */ = {isa = PBXBuildFile; fileRef = E416E4941879112700FC2EEB /* RecastMesh.cpp */; }; + E416E4A51879112700FC2EEB /* RecastMesh.cpp in Sources */ = {isa = PBXBuildFile; fileRef = E416E4941879112700FC2EEB /* RecastMesh.cpp */; }; + E416E4A61879112700FC2EEB /* RecastMeshDetail.cpp in Sources */ = {isa = PBXBuildFile; fileRef = E416E4951879112700FC2EEB /* RecastMeshDetail.cpp */; }; + E416E4A71879112700FC2EEB /* RecastMeshDetail.cpp in Sources */ = {isa = PBXBuildFile; fileRef = E416E4951879112700FC2EEB /* RecastMeshDetail.cpp */; }; + E416E4A81879112700FC2EEB /* RecastRasterization.cpp in Sources */ = {isa = PBXBuildFile; fileRef = E416E4961879112700FC2EEB /* RecastRasterization.cpp */; }; + E416E4A91879112700FC2EEB /* RecastRasterization.cpp in Sources */ = {isa = PBXBuildFile; fileRef = E416E4961879112700FC2EEB /* RecastRasterization.cpp */; }; + E416E4AA1879112700FC2EEB /* RecastRegion.cpp in Sources */ = {isa = PBXBuildFile; fileRef = E416E4971879112700FC2EEB /* RecastRegion.cpp */; }; + E416E4AB1879112700FC2EEB /* RecastRegion.cpp in Sources */ = {isa = PBXBuildFile; fileRef = E416E4971879112700FC2EEB /* RecastRegion.cpp */; }; E41843921678704000DBD6CF /* KRCollider.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 104A335C1672D31B001C8BA6 /* KRCollider.cpp */; }; E41B6BA816BE436100B510EB /* CoreAudio.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = E41B6BA716BE436100B510EB /* CoreAudio.framework */; }; E41B6BAA16BE437800B510EB /* CoreAudio.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = E41B6BA916BE437800B510EB /* CoreAudio.framework */; }; @@ -112,6 +138,28 @@ E45134B91746A4A300443C21 /* KRBehavior.h in Headers */ = {isa = PBXBuildFile; fileRef = E45134B51746A4A300443C21 /* KRBehavior.h */; settings = {ATTRIBUTES = (Public, ); }; }; E459040416C30CC5002B00A0 /* AudioUnit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = E459040316C30CC5002B00A0 /* AudioUnit.framework */; }; E459040616C30CD9002B00A0 /* AudioUnit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = E459040516C30CD9002B00A0 /* AudioUnit.framework */; }; + E45E03B118790DD1006DA23F /* PVRTArray.h in Headers */ = {isa = PBXBuildFile; fileRef = E45E03A418790DD1006DA23F /* PVRTArray.h */; }; + E45E03B218790DD1006DA23F /* PVRTDecompress.h in Headers */ = {isa = PBXBuildFile; fileRef = E45E03A518790DD1006DA23F /* PVRTDecompress.h */; }; + E45E03B318790DD1006DA23F /* PVRTError.h in Headers */ = {isa = PBXBuildFile; fileRef = E45E03A618790DD1006DA23F /* PVRTError.h */; }; + E45E03B418790DD1006DA23F /* PVRTexture.h in Headers */ = {isa = PBXBuildFile; fileRef = E45E03A718790DD1006DA23F /* PVRTexture.h */; }; + E45E03B518790DD1006DA23F /* PVRTextureDefines.h in Headers */ = {isa = PBXBuildFile; fileRef = E45E03A818790DD1006DA23F /* PVRTextureDefines.h */; }; + E45E03B618790DD1006DA23F /* PVRTextureFormat.h in Headers */ = {isa = PBXBuildFile; fileRef = E45E03A918790DD1006DA23F /* PVRTextureFormat.h */; }; + E45E03B718790DD1006DA23F /* PVRTextureHeader.h in Headers */ = {isa = PBXBuildFile; fileRef = E45E03AA18790DD1006DA23F /* PVRTextureHeader.h */; }; + E45E03B818790DD1006DA23F /* PVRTextureUtilities.h in Headers */ = {isa = PBXBuildFile; fileRef = E45E03AB18790DD1006DA23F /* PVRTextureUtilities.h */; }; + E45E03B918790DD1006DA23F /* PVRTextureVersion.h in Headers */ = {isa = PBXBuildFile; fileRef = E45E03AC18790DD1006DA23F /* PVRTextureVersion.h */; }; + E45E03BA18790DD1006DA23F /* PVRTGlobal.h in Headers */ = {isa = PBXBuildFile; fileRef = E45E03AD18790DD1006DA23F /* PVRTGlobal.h */; }; + E45E03BB18790DD1006DA23F /* PVRTMap.h in Headers */ = {isa = PBXBuildFile; fileRef = E45E03AE18790DD1006DA23F /* PVRTMap.h */; }; + E45E03BC18790DD1006DA23F /* PVRTString.h in Headers */ = {isa = PBXBuildFile; fileRef = E45E03AF18790DD1006DA23F /* PVRTString.h */; }; + E45E03BD18790DD1006DA23F /* PVRTTexture.h in Headers */ = {isa = PBXBuildFile; fileRef = E45E03B018790DD1006DA23F /* PVRTTexture.h */; }; + E45E03C018790DF5006DA23F /* libPVRTexLib.a in Frameworks */ = {isa = PBXBuildFile; fileRef = E45E03BF18790DF5006DA23F /* libPVRTexLib.a */; }; + E45E03C718790EC0006DA23F /* tinyxml2.cpp in Sources */ = {isa = PBXBuildFile; fileRef = E45E03C418790EC0006DA23F /* tinyxml2.cpp */; }; + E45E03C818790EC0006DA23F /* tinyxml2.cpp in Sources */ = {isa = PBXBuildFile; fileRef = E45E03C418790EC0006DA23F /* tinyxml2.cpp */; }; + E45E03C918790EC0006DA23F /* tinyxml2.h in Headers */ = {isa = PBXBuildFile; fileRef = E45E03C518790EC0006DA23F /* tinyxml2.h */; }; + E45E03CA18790EC0006DA23F /* tinyxml2.h in Headers */ = {isa = PBXBuildFile; fileRef = E45E03C518790EC0006DA23F /* tinyxml2.h */; }; + E45E03CD18790EFF006DA23F /* forsyth.cpp in Sources */ = {isa = PBXBuildFile; fileRef = E45E03CB18790EFF006DA23F /* forsyth.cpp */; }; + E45E03CE18790EFF006DA23F /* forsyth.cpp in Sources */ = {isa = PBXBuildFile; fileRef = E45E03CB18790EFF006DA23F /* forsyth.cpp */; }; + E45E03CF18790EFF006DA23F /* forsyth.h in Headers */ = {isa = PBXBuildFile; fileRef = E45E03CC18790EFF006DA23F /* forsyth.h */; }; + E45E03D018790EFF006DA23F /* forsyth.h in Headers */ = {isa = PBXBuildFile; fileRef = E45E03CC18790EFF006DA23F /* forsyth.h */; }; E460292616681CFF00261BB9 /* KRTextureAnimated.h in Headers */ = {isa = PBXBuildFile; fileRef = E460292516681CFE00261BB9 /* KRTextureAnimated.h */; settings = {ATTRIBUTES = (); }; }; E460292816681D1000261BB9 /* KRTextureAnimated.cpp in Sources */ = {isa = PBXBuildFile; fileRef = E460292716681D1000261BB9 /* KRTextureAnimated.cpp */; }; E460292B16682BF700261BB9 /* libfbxsdk.a in Frameworks */ = {isa = PBXBuildFile; fileRef = E460292916682BD900261BB9 /* libfbxsdk.a */; }; @@ -139,10 +187,6 @@ E468448217FFDF51001F1FA1 /* KRLocator.h in Headers */ = {isa = PBXBuildFile; fileRef = E468447E17FFDF51001F1FA1 /* KRLocator.h */; settings = {ATTRIBUTES = (Public, ); }; }; E46A6B6D1559E97D000DBD37 /* KRResource+blend.cpp in Sources */ = {isa = PBXBuildFile; fileRef = E46A6B6C1559E97D000DBD37 /* KRResource+blend.cpp */; }; E46A6B701559EF0A000DBD37 /* KRResource+blend.h in Headers */ = {isa = PBXBuildFile; fileRef = E46A6B6F1559EF0A000DBD37 /* KRResource+blend.h */; settings = {ATTRIBUTES = (Public, ); }; }; - E46C214515364BC8009CABF3 /* tinyxml2.cpp in Sources */ = {isa = PBXBuildFile; fileRef = E46C214215364BC8009CABF3 /* tinyxml2.cpp */; }; - E46C214615364BC8009CABF3 /* tinyxml2.cpp in Sources */ = {isa = PBXBuildFile; fileRef = E46C214215364BC8009CABF3 /* tinyxml2.cpp */; }; - E46C214715364BC8009CABF3 /* tinyxml2.h in Headers */ = {isa = PBXBuildFile; fileRef = E46C214315364BC8009CABF3 /* tinyxml2.h */; settings = {ATTRIBUTES = (); }; }; - E46C214815364BC8009CABF3 /* tinyxml2.h in Headers */ = {isa = PBXBuildFile; fileRef = E46C214315364BC8009CABF3 /* tinyxml2.h */; settings = {ATTRIBUTES = (Public, ); }; }; E46C214B15364DEC009CABF3 /* KRSceneManager.cpp in Sources */ = {isa = PBXBuildFile; fileRef = E46C214A15364DEC009CABF3 /* KRSceneManager.cpp */; }; E46C214C15364DEC009CABF3 /* KRSceneManager.cpp in Sources */ = {isa = PBXBuildFile; fileRef = E46C214A15364DEC009CABF3 /* KRSceneManager.cpp */; }; E46DBE7F1512AF0200D59F86 /* OpenGL.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = E46DBE7D1512AD4900D59F86 /* OpenGL.framework */; }; @@ -350,7 +394,6 @@ E4E6F6BE16BA5E0A00E410F8 /* volumetric_fog_downsampled_osx.vsh in Resources */ = {isa = PBXBuildFile; fileRef = E4E6F62816BA5D8300E410F8 /* volumetric_fog_downsampled_osx.vsh */; }; E4E6F6BF16BA5E0A00E410F8 /* volumetric_fog_osx.fsh in Resources */ = {isa = PBXBuildFile; fileRef = E4E6F62916BA5D8300E410F8 /* volumetric_fog_osx.fsh */; }; E4E6F6C016BA5E0A00E410F8 /* volumetric_fog_osx.vsh in Resources */ = {isa = PBXBuildFile; fileRef = E4E6F62A16BA5D8300E410F8 /* volumetric_fog_osx.vsh */; }; - E4EC73B8171F32780065299F /* forsyth.cpp in Sources */ = {isa = PBXBuildFile; fileRef = E4FE6AAA16B21D7E0058B8CE /* forsyth.cpp */; }; E4EC73C11720B1FF0065299F /* KRVector4.cpp in Sources */ = {isa = PBXBuildFile; fileRef = E4EC73BF1720B1FF0065299F /* KRVector4.cpp */; }; E4EC73C21720B1FF0065299F /* KRVector4.cpp in Sources */ = {isa = PBXBuildFile; fileRef = E4EC73BF1720B1FF0065299F /* KRVector4.cpp */; }; E4EC73C31720B1FF0065299F /* KRVector4.h in Headers */ = {isa = PBXBuildFile; fileRef = E4EC73C01720B1FF0065299F /* KRVector4.h */; }; @@ -391,9 +434,6 @@ E4F97552153633EF00FD60B2 /* KRMaterialManager.h in Headers */ = {isa = PBXBuildFile; fileRef = E491018413C99BDC0098455B /* KRMaterialManager.h */; settings = {ATTRIBUTES = (Public, ); }; }; E4F975531536340000FD60B2 /* KRTexture2D.cpp in Sources */ = {isa = PBXBuildFile; fileRef = E491018113C99BDC0098455B /* KRTexture2D.cpp */; }; E4F975541536340400FD60B2 /* KRTexture2D.h in Headers */ = {isa = PBXBuildFile; fileRef = E491018613C99BDC0098455B /* KRTexture2D.h */; settings = {ATTRIBUTES = (Public, ); }; }; - E4FE6AA816B21D660058B8CE /* forsyth.h in Headers */ = {isa = PBXBuildFile; fileRef = E4FE6AA716B21D660058B8CE /* forsyth.h */; settings = {ATTRIBUTES = (); }; }; - E4FE6AA916B21D660058B8CE /* forsyth.h in Headers */ = {isa = PBXBuildFile; fileRef = E4FE6AA716B21D660058B8CE /* forsyth.h */; settings = {ATTRIBUTES = (Public, ); }; }; - E4FE6AAB16B21D7E0058B8CE /* forsyth.cpp in Sources */ = {isa = PBXBuildFile; fileRef = E4FE6AAA16B21D7E0058B8CE /* forsyth.cpp */; }; /* End PBXBuildFile section */ /* Begin PBXBuildRule section */ @@ -411,7 +451,6 @@ /* Begin PBXFileReference section */ 104A335C1672D31B001C8BA6 /* KRCollider.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; lineEnding = 0; path = KRCollider.cpp; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.cpp; }; 104A335D1672D31C001C8BA6 /* KRCollider.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; lineEnding = 0; path = KRCollider.h; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.objcpp; }; - 10CC33A3168530A300BB9846 /* libPVRTexLib.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libPVRTexLib.a; path = Utilities/PVRTexLib/MacOS/libPVRTexLib.a; sourceTree = PVRSDK; }; E4030E4B160A3CF000592648 /* KRStockGeometry.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = KRStockGeometry.h; sourceTree = ""; }; E404701E18695DD200F01F42 /* KRTextureKTX.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = KRTextureKTX.cpp; sourceTree = ""; }; E404701F18695DD200F01F42 /* KRTextureKTX.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = KRTextureKTX.h; sourceTree = ""; }; @@ -431,6 +470,19 @@ E414F9AB1694DA37000B3D58 /* KRUnknown.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; lineEnding = 0; path = KRUnknown.h; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.objcpp; }; E416AA9816713749000F6786 /* KRAnimationCurveManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = KRAnimationCurveManager.h; sourceTree = ""; }; E416AA9B1671375C000F6786 /* KRAnimationCurveManager.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = KRAnimationCurveManager.cpp; sourceTree = ""; }; + E416E4851879111300FC2EEB /* Recast.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Recast.h; sourceTree = ""; }; + E416E4861879111300FC2EEB /* RecastAlloc.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RecastAlloc.h; sourceTree = ""; }; + E416E4871879111300FC2EEB /* RecastAssert.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RecastAssert.h; sourceTree = ""; }; + E416E48E1879112700FC2EEB /* Recast.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = Recast.cpp; sourceTree = ""; }; + E416E48F1879112700FC2EEB /* RecastAlloc.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = RecastAlloc.cpp; sourceTree = ""; }; + E416E4901879112700FC2EEB /* RecastArea.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = RecastArea.cpp; sourceTree = ""; }; + E416E4911879112700FC2EEB /* RecastContour.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = RecastContour.cpp; sourceTree = ""; }; + E416E4921879112700FC2EEB /* RecastFilter.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = RecastFilter.cpp; sourceTree = ""; }; + E416E4931879112700FC2EEB /* RecastLayers.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = RecastLayers.cpp; sourceTree = ""; }; + E416E4941879112700FC2EEB /* RecastMesh.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = RecastMesh.cpp; sourceTree = ""; }; + E416E4951879112700FC2EEB /* RecastMeshDetail.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = RecastMeshDetail.cpp; sourceTree = ""; }; + E416E4961879112700FC2EEB /* RecastRasterization.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = RecastRasterization.cpp; sourceTree = ""; }; + E416E4971879112700FC2EEB /* RecastRegion.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = RecastRegion.cpp; sourceTree = ""; }; E41AE1DD16B124CA00980428 /* font.tga */ = {isa = PBXFileReference; lastKnownFileType = file; path = font.tga; sourceTree = ""; }; E41B6BA716BE436100B510EB /* CoreAudio.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreAudio.framework; path = System/Library/Frameworks/CoreAudio.framework; sourceTree = SDKROOT; }; E41B6BA916BE437800B510EB /* CoreAudio.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreAudio.framework; path = Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.8.sdk/System/Library/Frameworks/CoreAudio.framework; sourceTree = DEVELOPER_DIR; }; @@ -471,6 +523,25 @@ E45134B51746A4A300443C21 /* KRBehavior.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = KRBehavior.h; sourceTree = ""; }; E459040316C30CC5002B00A0 /* AudioUnit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AudioUnit.framework; path = Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.8.sdk/System/Library/Frameworks/AudioUnit.framework; sourceTree = DEVELOPER_DIR; }; E459040516C30CD9002B00A0 /* AudioUnit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AudioUnit.framework; path = System/Library/Frameworks/AudioUnit.framework; sourceTree = SDKROOT; }; + E45E03A418790DD1006DA23F /* PVRTArray.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PVRTArray.h; sourceTree = ""; }; + E45E03A518790DD1006DA23F /* PVRTDecompress.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PVRTDecompress.h; sourceTree = ""; }; + E45E03A618790DD1006DA23F /* PVRTError.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PVRTError.h; sourceTree = ""; }; + E45E03A718790DD1006DA23F /* PVRTexture.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PVRTexture.h; sourceTree = ""; }; + E45E03A818790DD1006DA23F /* PVRTextureDefines.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PVRTextureDefines.h; sourceTree = ""; }; + E45E03A918790DD1006DA23F /* PVRTextureFormat.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PVRTextureFormat.h; sourceTree = ""; }; + E45E03AA18790DD1006DA23F /* PVRTextureHeader.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PVRTextureHeader.h; sourceTree = ""; }; + E45E03AB18790DD1006DA23F /* PVRTextureUtilities.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PVRTextureUtilities.h; sourceTree = ""; }; + E45E03AC18790DD1006DA23F /* PVRTextureVersion.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PVRTextureVersion.h; sourceTree = ""; }; + E45E03AD18790DD1006DA23F /* PVRTGlobal.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PVRTGlobal.h; sourceTree = ""; }; + E45E03AE18790DD1006DA23F /* PVRTMap.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PVRTMap.h; sourceTree = ""; }; + E45E03AF18790DD1006DA23F /* PVRTString.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PVRTString.h; sourceTree = ""; }; + E45E03B018790DD1006DA23F /* PVRTTexture.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PVRTTexture.h; sourceTree = ""; }; + E45E03BF18790DF5006DA23F /* libPVRTexLib.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libPVRTexLib.a; sourceTree = ""; }; + E45E03C318790EC0006DA23F /* tinyxml2_readme.txt */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = tinyxml2_readme.txt; sourceTree = ""; }; + E45E03C418790EC0006DA23F /* tinyxml2.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = tinyxml2.cpp; sourceTree = ""; }; + E45E03C518790EC0006DA23F /* tinyxml2.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = tinyxml2.h; sourceTree = ""; }; + E45E03CB18790EFF006DA23F /* forsyth.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = forsyth.cpp; sourceTree = ""; }; + E45E03CC18790EFF006DA23F /* forsyth.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = forsyth.h; sourceTree = ""; }; E460292516681CFE00261BB9 /* KRTextureAnimated.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = KRTextureAnimated.h; sourceTree = ""; }; E460292716681D1000261BB9 /* KRTextureAnimated.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = KRTextureAnimated.cpp; sourceTree = ""; }; E460292916682BD900261BB9 /* libfbxsdk.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libfbxsdk.a; path = "../../FBX SDK/2014.2/lib/ios-i386/release/libfbxsdk.a"; sourceTree = FBXSDK; }; @@ -486,9 +557,6 @@ E468447E17FFDF51001F1FA1 /* KRLocator.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = KRLocator.h; sourceTree = ""; }; E46A6B6C1559E97D000DBD37 /* KRResource+blend.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = "KRResource+blend.cpp"; sourceTree = ""; }; E46A6B6F1559EF0A000DBD37 /* KRResource+blend.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "KRResource+blend.h"; sourceTree = ""; }; - E46C214115364BC8009CABF3 /* tinyxml2_readme.txt */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = tinyxml2_readme.txt; sourceTree = ""; }; - E46C214215364BC8009CABF3 /* tinyxml2.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = tinyxml2.cpp; sourceTree = ""; }; - E46C214315364BC8009CABF3 /* tinyxml2.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = tinyxml2.h; sourceTree = ""; }; E46C214915364DDB009CABF3 /* KRSceneManager.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = KRSceneManager.h; sourceTree = ""; }; E46C214A15364DEC009CABF3 /* KRSceneManager.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = KRSceneManager.cpp; sourceTree = ""; }; E46DBE7D1512AD4900D59F86 /* OpenGL.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = OpenGL.framework; path = ../../../../MacOSX.platform/Developer/SDKs/MacOSX10.7.sdk/System/Library/Frameworks/OpenGL.framework; sourceTree = SDKROOT; }; @@ -658,8 +726,6 @@ E4F027F91698116000D4427D /* AudioToolbox.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AudioToolbox.framework; path = System/Library/Frameworks/AudioToolbox.framework; sourceTree = SDKROOT; }; E4F975311536220900FD60B2 /* KRNode.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; lineEnding = 0; path = KRNode.h; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.objcpp; }; E4F975351536221C00FD60B2 /* KRNode.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; lineEnding = 0; path = KRNode.cpp; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.cpp; }; - E4FE6AA716B21D660058B8CE /* forsyth.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = forsyth.h; sourceTree = ""; }; - E4FE6AAA16B21D7E0058B8CE /* forsyth.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = forsyth.cpp; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -681,6 +747,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + E45E03C018790DF5006DA23F /* libPVRTexLib.a in Frameworks */, E459040416C30CC5002B00A0 /* AudioUnit.framework in Frameworks */, E41B6BAA16BE437800B510EB /* CoreAudio.framework in Frameworks */, E4F027F71698115600D4427D /* AudioToolbox.framework in Frameworks */, @@ -736,6 +803,42 @@ name = AnimationCurve; sourceTree = ""; }; + E416E482187910DF00FC2EEB /* recast */ = { + isa = PBXGroup; + children = ( + E416E484187910FB00FC2EEB /* include */, + E416E483187910ED00FC2EEB /* source */, + ); + path = recast; + sourceTree = ""; + }; + E416E483187910ED00FC2EEB /* source */ = { + isa = PBXGroup; + children = ( + E416E48E1879112700FC2EEB /* Recast.cpp */, + E416E48F1879112700FC2EEB /* RecastAlloc.cpp */, + E416E4901879112700FC2EEB /* RecastArea.cpp */, + E416E4911879112700FC2EEB /* RecastContour.cpp */, + E416E4921879112700FC2EEB /* RecastFilter.cpp */, + E416E4931879112700FC2EEB /* RecastLayers.cpp */, + E416E4941879112700FC2EEB /* RecastMesh.cpp */, + E416E4951879112700FC2EEB /* RecastMeshDetail.cpp */, + E416E4961879112700FC2EEB /* RecastRasterization.cpp */, + E416E4971879112700FC2EEB /* RecastRegion.cpp */, + ); + path = source; + sourceTree = ""; + }; + E416E484187910FB00FC2EEB /* include */ = { + isa = PBXGroup; + children = ( + E416E4851879111300FC2EEB /* Recast.h */, + E416E4861879111300FC2EEB /* RecastAlloc.h */, + E416E4871879111300FC2EEB /* RecastAssert.h */, + ); + path = include; + sourceTree = ""; + }; E41AE1DF16B125EC00980428 /* Shaders */ = { isa = PBXGroup; children = ( @@ -809,6 +912,73 @@ path = kraken_standard_assets; sourceTree = ""; }; + E45E03A118790D92006DA23F /* 3rdparty */ = { + isa = PBXGroup; + children = ( + E416E482187910DF00FC2EEB /* recast */, + E45E03C218790E55006DA23F /* tinyxml2 */, + E45E03C118790E4F006DA23F /* forsyth */, + E45E03A218790DA3006DA23F /* pvrtexlib */, + ); + path = 3rdparty; + sourceTree = ""; + }; + E45E03A218790DA3006DA23F /* pvrtexlib */ = { + isa = PBXGroup; + children = ( + E45E03BE18790DDB006DA23F /* static_osx */, + E45E03A318790DB8006DA23F /* include */, + ); + path = pvrtexlib; + sourceTree = ""; + }; + E45E03A318790DB8006DA23F /* include */ = { + isa = PBXGroup; + children = ( + E45E03A418790DD1006DA23F /* PVRTArray.h */, + E45E03A518790DD1006DA23F /* PVRTDecompress.h */, + E45E03A618790DD1006DA23F /* PVRTError.h */, + E45E03A718790DD1006DA23F /* PVRTexture.h */, + E45E03A818790DD1006DA23F /* PVRTextureDefines.h */, + E45E03A918790DD1006DA23F /* PVRTextureFormat.h */, + E45E03AA18790DD1006DA23F /* PVRTextureHeader.h */, + E45E03AB18790DD1006DA23F /* PVRTextureUtilities.h */, + E45E03AC18790DD1006DA23F /* PVRTextureVersion.h */, + E45E03AD18790DD1006DA23F /* PVRTGlobal.h */, + E45E03AE18790DD1006DA23F /* PVRTMap.h */, + E45E03AF18790DD1006DA23F /* PVRTString.h */, + E45E03B018790DD1006DA23F /* PVRTTexture.h */, + ); + path = include; + sourceTree = ""; + }; + E45E03BE18790DDB006DA23F /* static_osx */ = { + isa = PBXGroup; + children = ( + E45E03BF18790DF5006DA23F /* libPVRTexLib.a */, + ); + path = static_osx; + sourceTree = ""; + }; + E45E03C118790E4F006DA23F /* forsyth */ = { + isa = PBXGroup; + children = ( + E45E03CB18790EFF006DA23F /* forsyth.cpp */, + E45E03CC18790EFF006DA23F /* forsyth.h */, + ); + path = forsyth; + sourceTree = ""; + }; + E45E03C218790E55006DA23F /* tinyxml2 */ = { + isa = PBXGroup; + children = ( + E45E03C318790EC0006DA23F /* tinyxml2_readme.txt */, + E45E03C418790EC0006DA23F /* tinyxml2.cpp */, + E45E03C518790EC0006DA23F /* tinyxml2.h */, + ); + path = tinyxml2; + sourceTree = ""; + }; E461A170152E598200F2044A /* Resources */ = { isa = PBXGroup; children = ( @@ -858,16 +1028,6 @@ name = Math; sourceTree = ""; }; - E46C214015364BB8009CABF3 /* tinyxml2 */ = { - isa = PBXGroup; - children = ( - E46C214115364BC8009CABF3 /* tinyxml2_readme.txt */, - E46C214215364BC8009CABF3 /* tinyxml2.cpp */, - E46C214315364BC8009CABF3 /* tinyxml2.h */, - ); - name = tinyxml2; - sourceTree = ""; - }; E488399915F92BA300BD66D5 /* Managers */ = { isa = PBXGroup; children = ( @@ -1040,6 +1200,7 @@ E491015613C99B9D0098455B = { isa = PBXGroup; children = ( + E45E03A118790D92006DA23F /* 3rdparty */, E437849616C4881A0037FD43 /* kraken_standard_assets */, E491016613C99B9E0098455B /* kraken */, E4C8E50C16B9B5ED0031DDCB /* kraken_ios */, @@ -1064,7 +1225,6 @@ E491016613C99B9E0098455B /* kraken */ = { isa = PBXGroup; children = ( - E4F9753815362A5200FD60B2 /* 3rdparty */, E488399915F92BA300BD66D5 /* Managers */, E461A173152E59DF00F2044A /* Math */, E461A170152E598200F2044A /* Resources */, @@ -1120,7 +1280,6 @@ children = ( E459040316C30CC5002B00A0 /* AudioUnit.framework */, E41B6BA916BE437800B510EB /* CoreAudio.framework */, - 10CC33A3168530A300BB9846 /* libPVRTexLib.a */, E460292916682BD900261BB9 /* libfbxsdk.a */, E4BBBB9A1512A48200F43B5B /* Foundation.framework */, E46DBE7D1512AD4900D59F86 /* OpenGL.framework */, @@ -1219,24 +1378,6 @@ name = Frameworks; sourceTree = ""; }; - E4F9753815362A5200FD60B2 /* 3rdparty */ = { - isa = PBXGroup; - children = ( - E4FE6AA516B21D330058B8CE /* forsyth */, - E46C214015364BB8009CABF3 /* tinyxml2 */, - ); - name = 3rdparty; - sourceTree = ""; - }; - E4FE6AA516B21D330058B8CE /* forsyth */ = { - isa = PBXGroup; - children = ( - E4FE6AA716B21D660058B8CE /* forsyth.h */, - E4FE6AAA16B21D7E0058B8CE /* forsyth.cpp */, - ); - name = forsyth; - sourceTree = ""; - }; /* End PBXGroup section */ /* Begin PBXHeadersBuildPhase section */ @@ -1252,6 +1393,7 @@ E491018A13C99BDC0098455B /* KREngine.h in Headers */, E491018E13C99BDC0098455B /* KRMat4.h in Headers */, E491019B13C99BDC0098455B /* KRMeshManager.h in Headers */, + E416E48C1879111300FC2EEB /* RecastAssert.h in Headers */, E491019213C99BDC0098455B /* KRMesh.h in Headers */, E491019613C99BDC0098455B /* KRVector3.h in Headers */, E47C25A213F4F65A00FF4370 /* KRShaderManager.h in Headers */, @@ -1260,6 +1402,7 @@ E414BAE7143557D200A668C4 /* KRScene.h in Headers */, E48B3CBD14393DF5000C50E2 /* KRCamera.h in Headers */, E40F982E184A7A2700CFA4D8 /* KRMeshQuad.h in Headers */, + E45E03CF18790EFF006DA23F /* forsyth.h in Headers */, E497B94A151BCEE900D3DC67 /* KRResource.h in Headers */, E461A152152E54B500F2044A /* KRLight.h in Headers */, E461A15C152E563100F2044A /* KRDirectionalLight.h in Headers */, @@ -1267,7 +1410,6 @@ E468448117FFDF51001F1FA1 /* KRLocator.h in Headers */, E43F70E71824D9AB00136169 /* KRTextureStreamer.h in Headers */, E4F975321536220900FD60B2 /* KRNode.h in Headers */, - E46C214715364BC8009CABF3 /* tinyxml2.h in Headers */, E48C696F15374F5B00232E28 /* KRContext.h in Headers */, E46F4A0B155E002100CCF8B8 /* KRDataBlock.h in Headers */, E42CB1EC158446940066E0D8 /* KRQuaternion.h in Headers */, @@ -1280,6 +1422,7 @@ E488399E15F92BE000BD66D5 /* KRBundleManager.h in Headers */, E4030E4C160A3CF000592648 /* KRStockGeometry.h in Headers */, E4B175AE161F5A1000B8FB80 /* KRTexture.h in Headers */, + E416E48A1879111300FC2EEB /* RecastAlloc.h in Headers */, E4B175B4161F5FAF00B8FB80 /* KRTextureCube.h in Headers */, E4CA10E51637BD0A005D9400 /* KRTexturePVR.h in Headers */, E4CA10EC1637BD47005D9400 /* KRTextureTGA.h in Headers */, @@ -1310,9 +1453,10 @@ E499BF2016AE755B007FCDBE /* KRPointLight.h in Headers */, E499BF2116AE75A7007FCDBE /* KREngine-common.h in Headers */, E404702218695DD200F01F42 /* KRTextureKTX.h in Headers */, + E45E03C918790EC0006DA23F /* tinyxml2.h in Headers */, E4F027D016979CE200D4427D /* KRAudioSample.h in Headers */, E450273B16E0491D00FDEC5C /* KRReverbZone.h in Headers */, - E4FE6AA816B21D660058B8CE /* forsyth.h in Headers */, + E416E4881879111300FC2EEB /* Recast.h in Headers */, E4AE635F1704FB0A00B460CD /* KRLODGroup.h in Headers */, E4EC73C31720B1FF0065299F /* KRVector4.h in Headers */, E48CF944173453990005EBBB /* KRFloat.h in Headers */, @@ -1325,11 +1469,14 @@ isa = PBXHeadersBuildPhase; buildActionMask = 2147483647; files = ( + E45E03BD18790DD1006DA23F /* PVRTTexture.h in Headers */, E497B948151BB89D00D3DC67 /* KRVector2.h in Headers */, E4D0683F1512A790005FFBEB /* KRVector3.h in Headers */, E461A177152E5C6600F2044A /* KRMat4.h in Headers */, E4F975461536327C00FD60B2 /* KRMeshManager.h in Headers */, + E45E03B618790DD1006DA23F /* PVRTextureFormat.h in Headers */, E497B94B151BCEE900D3DC67 /* KRResource.h in Headers */, + E45E03B318790DD1006DA23F /* PVRTError.h in Headers */, E43B0AD915DDCA0F00A5CB9F /* KRContextObject.h in Headers */, E4F975331536220900FD60B2 /* KRNode.h in Headers */, E4B2A4391523B027004CB0EC /* KRMaterial.h in Headers */, @@ -1341,6 +1488,7 @@ E46DBE851512B9FA00D59F86 /* KREngine-common.h in Headers */, E488399715F928CA00BD66D5 /* KRBundle.h in Headers */, E4F9754C153632F000FD60B2 /* KRCamera.h in Headers */, + E45E03BA18790DD1006DA23F /* PVRTGlobal.h in Headers */, E4F975501536333500FD60B2 /* KRMesh.h in Headers */, E488399F15F92BE000BD66D5 /* KRBundleManager.h in Headers */, E4AFC6BC15F7C95D00DDB4C8 /* KRSceneManager.h in Headers */, @@ -1354,47 +1502,59 @@ E428C3051669627900A16EDF /* KRAnimationCurve.h in Headers */, E48C697015374F5B00232E28 /* KRContext.h in Headers */, E46F4A10155E004100CCF8B8 /* KRDataBlock.cpp in Headers */, - E46C214815364BC8009CABF3 /* tinyxml2.h in Headers */, + E45E03B418790DD1006DA23F /* PVRTexture.h in Headers */, E4D13365153767FF0070068C /* KRShaderManager.h in Headers */, E40BA45715EFF79500D7C3DD /* KRAABB.h in Headers */, E428C312166971FF00A16EDF /* KRAnimationLayer.h in Headers */, E4AFC6B615F7C46800DDB4C8 /* KRAABB.cpp in Headers */, + E45E03B818790DD1006DA23F /* PVRTextureUtilities.h in Headers */, E428C3171669A24B00A16EDF /* KRAnimationAttribute.h in Headers */, E4AFC6BE15F7C9E600DDB4C8 /* KROctreeNode.h in Headers */, + E416E48D1879111300FC2EEB /* RecastAssert.h in Headers */, + E45E03B918790DD1006DA23F /* PVRTextureVersion.h in Headers */, E4AFC6BD15F7C9DA00DDB4C8 /* KROctree.h in Headers */, E46A6B701559EF0A000DBD37 /* KRResource+blend.h in Headers */, E416AA9A16713749000F6786 /* KRAnimationCurveManager.h in Headers */, + E416E4891879111300FC2EEB /* Recast.h in Headers */, E42CB1ED158446940066E0D8 /* KRQuaternion.h in Headers */, E4030E4D160A3CF000592648 /* KRStockGeometry.h in Headers */, E4B175AF161F5A1000B8FB80 /* KRTexture.h in Headers */, E480BE691671C641004EC8AD /* KRBone.h in Headers */, E4B175B5161F5FAF00B8FB80 /* KRTextureCube.h in Headers */, + E45E03B118790DD1006DA23F /* PVRTArray.h in Headers */, + E45E03CA18790EC0006DA23F /* tinyxml2.h in Headers */, E4CA10E61637BD0A005D9400 /* KRTexturePVR.h in Headers */, E4CA10ED1637BD47005D9400 /* KRTextureTGA.h in Headers */, + E45E03D018790EFF006DA23F /* forsyth.h in Headers */, E4CA11751639CBD6005D9400 /* KRViewport.h in Headers */, E461A15D152E563100F2044A /* KRDirectionalLight.h in Headers */, E461A169152E570700F2044A /* KRSpotLight.h in Headers */, + E45E03B218790DD1006DA23F /* PVRTDecompress.h in Headers */, E4C454B9167BD236003586CD /* KRHitInfo.h in Headers */, E4324BA516444C0D0043185B /* KRParticleSystem.h in Headers */, E4324BAC16444DEF0043185B /* KRParticleSystemNewtonian.h in Headers */, + E45E03B518790DD1006DA23F /* PVRTextureDefines.h in Headers */, E4C454AD167BB8EC003586CD /* KRMeshCube.h in Headers */, E404702318695DD200F01F42 /* KRTextureKTX.h in Headers */, E414F9A91694D977000B3D58 /* KRUnknownManager.h in Headers */, E48B68181697794F00D99917 /* KRAudioSource.h in Headers */, E4F027CA16979CCD00D4427D /* KRAudioManager.h in Headers */, E4F027D116979CE200D4427D /* KRAudioSample.h in Headers */, + E45E03B718790DD1006DA23F /* PVRTextureHeader.h in Headers */, E4F027E11697BFFF00D4427D /* KRAudioBuffer.h in Headers */, E4943234169E08D200BCB891 /* KRAmbientZone.h in Headers */, E414F9AF1694DA37000B3D58 /* KRUnknown.h in Headers */, E499BF2216AE760F007FCDBE /* krengine_osx.h in Headers */, - E4FE6AA916B21D660058B8CE /* forsyth.h in Headers */, E4C454B3167BC04C003586CD /* KRMeshSphere.h in Headers */, E450273C16E0491D00FDEC5C /* KRReverbZone.h in Headers */, + E45E03BB18790DD1006DA23F /* PVRTMap.h in Headers */, E44F38251683B23000399B5D /* KRRenderSettings.h in Headers */, + E45E03BC18790DD1006DA23F /* PVRTString.h in Headers */, E499BF1D16AE74FF007FCDBE /* KRTextureAnimated.h in Headers */, E4EC73C41720B1FF0065299F /* KRVector4.h in Headers */, E468448217FFDF51001F1FA1 /* KRLocator.h in Headers */, E43F70DF181B20E400136169 /* KRLODSet.h in Headers */, + E416E48B1879111300FC2EEB /* RecastAlloc.h in Headers */, E4AE63601704FB0A00B460CD /* KRLODGroup.h in Headers */, E45134B91746A4A300443C21 /* KRBehavior.h in Headers */, E43F71021824E73100136169 /* KRMeshStreamer.h in Headers */, @@ -1608,41 +1768,52 @@ E491019113C99BDC0098455B /* KRMesh.cpp in Sources */, E491019313C99BDC0098455B /* KRMaterialManager.cpp in Sources */, E491019413C99BDC0098455B /* KRMaterial.cpp in Sources */, + E416E49E1879112700FC2EEB /* RecastContour.cpp in Sources */, E491019713C99BDC0098455B /* KRVector3.cpp in Sources */, E491019813C99BDC0098455B /* KRTextureManager.cpp in Sources */, E491019913C99BDC0098455B /* KRTexture2D.cpp in Sources */, + E416E4AA1879112700FC2EEB /* RecastRegion.cpp in Sources */, E491019A13C99BDC0098455B /* KRMeshManager.cpp in Sources */, + E45E03C718790EC0006DA23F /* tinyxml2.cpp in Sources */, + E45E03CD18790EFF006DA23F /* forsyth.cpp in Sources */, E47C25A713F4F6AB00FF4370 /* KRShaderManager.cpp in Sources */, E47C25A913F4F6DD00FF4370 /* KRShader.cpp in Sources */, + E416E4A41879112700FC2EEB /* RecastMesh.cpp in Sources */, E43F70DC181B20E400136169 /* KRLODSet.cpp in Sources */, E414BAE51435558900A668C4 /* KRModel.cpp in Sources */, E414BAE91435585A00A668C4 /* KRScene.cpp in Sources */, E48B3CC014393E30000C50E2 /* KRCamera.cpp in Sources */, + E416E4A61879112700FC2EEB /* RecastMeshDetail.cpp in Sources */, E497B946151BA99500D3DC67 /* KRVector2.cpp in Sources */, E497B94D151BCF2500D3DC67 /* KRResource.cpp in Sources */, + E416E4981879112700FC2EEB /* Recast.cpp in Sources */, E497B950151BD2CE00D3DC67 /* KRResource+obj.cpp in Sources */, E43F70FF1824E73100136169 /* KRMeshStreamer.mm in Sources */, E461A156152E54F800F2044A /* KRLight.cpp in Sources */, E461A159152E557E00F2044A /* KRPointLight.cpp in Sources */, E468447F17FFDF51001F1FA1 /* KRLocator.cpp in Sources */, E461A15F152E565700F2044A /* KRDirectionalLight.cpp in Sources */, + E416E4A21879112700FC2EEB /* RecastLayers.cpp in Sources */, E461A165152E56C000F2044A /* KRSpotLight.cpp in Sources */, E4F975361536221C00FD60B2 /* KRNode.cpp in Sources */, - E46C214515364BC8009CABF3 /* tinyxml2.cpp in Sources */, E46C214B15364DEC009CABF3 /* KRSceneManager.cpp in Sources */, E48C697215374F7E00232E28 /* KRContext.cpp in Sources */, E46F4A0E155E003000CCF8B8 /* KRDataBlock.cpp in Sources */, + E416E49A1879112700FC2EEB /* RecastAlloc.cpp in Sources */, E42CB1F0158446AB0066E0D8 /* KRQuaternion.cpp in Sources */, E43B0AD615DDCA0F00A5CB9F /* KRContextObject.cpp in Sources */, E4924C2615EE95E800B965C6 /* KROctree.cpp in Sources */, + E416E4A81879112700FC2EEB /* RecastRasterization.cpp in Sources */, E4924C2B15EE96AB00B965C6 /* KROctreeNode.cpp in Sources */, E404702018695DD200F01F42 /* KRTextureKTX.cpp in Sources */, E40BA45415EFF79500D7C3DD /* KRAABB.cpp in Sources */, + E416E49C1879112700FC2EEB /* RecastArea.cpp in Sources */, E488399415F928CA00BD66D5 /* KRBundle.cpp in Sources */, E488399C15F92BE000BD66D5 /* KRBundleManager.cpp in Sources */, E4B175AC161F5A1000B8FB80 /* KRTexture.cpp in Sources */, E4B175B2161F5FAF00B8FB80 /* KRTextureCube.cpp in Sources */, E4CA10E91637BD2B005D9400 /* KRTexturePVR.cpp in Sources */, + E416E4A01879112700FC2EEB /* RecastFilter.cpp in Sources */, E4CA10EF1637BD58005D9400 /* KRTextureTGA.cpp in Sources */, E4CA11781639CC90005D9400 /* KRViewport.cpp in Sources */, E4324BA816444C230043185B /* KRParticleSystem.cpp in Sources */, @@ -1670,7 +1841,6 @@ E4F027CE16979CE200D4427D /* KRAudioSample.cpp in Sources */, E4F027DE1697BFFF00D4427D /* KRAudioBuffer.cpp in Sources */, E4943231169E08D200BCB891 /* KRAmbientZone.cpp in Sources */, - E4FE6AAB16B21D7E0058B8CE /* forsyth.cpp in Sources */, E450273916E0491D00FDEC5C /* KRReverbZone.cpp in Sources */, E4AE635D1704FB0A00B460CD /* KRLODGroup.cpp in Sources */, E4EC73C11720B1FF0065299F /* KRVector4.cpp in Sources */, @@ -1683,7 +1853,6 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - E4EC73B8171F32780065299F /* forsyth.cpp in Sources */, E499BF2516AE8C20007FCDBE /* KREngine.mm in Sources */, 10CC33A5168534F000BB9846 /* KRCamera.cpp in Sources */, E460292C166834AB00261BB9 /* KRTextureAnimated.cpp in Sources */, @@ -1691,6 +1860,7 @@ E461A175152E5C4800F2044A /* KRLight.cpp in Sources */, E4BBBBA71512A6DC00F43B5B /* KRVector3.cpp in Sources */, E4B2A43B1523B02E004CB0EC /* KRMaterial.cpp in Sources */, + E416E4A71879112700FC2EEB /* RecastMeshDetail.cpp in Sources */, E4BBBB8E1512A40300F43B5B /* krengine_osx.mm in Sources */, E468448017FFDF51001F1FA1 /* KRLocator.cpp in Sources */, E497B947151BA99500D3DC67 /* KRVector2.cpp in Sources */, @@ -1698,18 +1868,23 @@ E497B951151BD2CE00D3DC67 /* KRResource+obj.cpp in Sources */, E497B954151BEDA600D3DC67 /* KRResource+fbx.cpp in Sources */, E4F97551153633E200FD60B2 /* KRMaterialManager.cpp in Sources */, + E416E49F1879112700FC2EEB /* RecastContour.cpp in Sources */, E461A15A152E557E00F2044A /* KRPointLight.cpp in Sources */, + E45E03C818790EC0006DA23F /* tinyxml2.cpp in Sources */, E4F9754F1536333200FD60B2 /* KRMesh.cpp in Sources */, E4F9754B153632D800FD60B2 /* KRMeshManager.cpp in Sources */, + E416E4A31879112700FC2EEB /* RecastLayers.cpp in Sources */, E461A160152E565700F2044A /* KRDirectionalLight.cpp in Sources */, E4F975531536340000FD60B2 /* KRTexture2D.cpp in Sources */, E4F9754015362CD400FD60B2 /* KRScene.cpp in Sources */, E461A166152E56C000F2044A /* KRSpotLight.cpp in Sources */, E4F9754315362D0F00FD60B2 /* KRModel.cpp in Sources */, + E416E4A11879112700FC2EEB /* RecastFilter.cpp in Sources */, + E45E03CE18790EFF006DA23F /* forsyth.cpp in Sources */, + E416E4991879112700FC2EEB /* Recast.cpp in Sources */, E4F975371536221C00FD60B2 /* KRNode.cpp in Sources */, E4F9754E1536331D00FD60B2 /* KRTextureManager.cpp in Sources */, E4D13367153768610070068C /* KRShader.cpp in Sources */, - E46C214615364BC8009CABF3 /* tinyxml2.cpp in Sources */, E4D13364153767ED0070068C /* KRShaderManager.cpp in Sources */, E4AFC6B915F7C7B200DDB4C8 /* KROctree.cpp in Sources */, E46C214C15364DEC009CABF3 /* KRSceneManager.cpp in Sources */, @@ -1719,6 +1894,7 @@ E46F4A0F155E003000CCF8B8 /* KRDataBlock.cpp in Sources */, E42CB1F1158446AB0066E0D8 /* KRQuaternion.cpp in Sources */, E4AFC6BB15F7C7D600DDB4C8 /* KROctreeNode.cpp in Sources */, + E416E4A51879112700FC2EEB /* RecastMesh.cpp in Sources */, E43B0AD715DDCA0F00A5CB9F /* KRContextObject.cpp in Sources */, E40BA45515EFF79500D7C3DD /* KRAABB.cpp in Sources */, E488399515F928CA00BD66D5 /* KRBundle.cpp in Sources */, @@ -1726,6 +1902,7 @@ E43F70DD181B20E400136169 /* KRLODSet.cpp in Sources */, E43F71001824E73100136169 /* KRMeshStreamer.mm in Sources */, E4B175AD161F5A1000B8FB80 /* KRTexture.cpp in Sources */, + E416E4AB1879112700FC2EEB /* RecastRegion.cpp in Sources */, E4B175B3161F5FAF00B8FB80 /* KRTextureCube.cpp in Sources */, E40F9833184A7BAC00CFA4D8 /* KRSprite.cpp in Sources */, E4CA10EA1637BD2B005D9400 /* KRTexturePVR.cpp in Sources */, @@ -1752,11 +1929,14 @@ E4F027C816979CCD00D4427D /* KRAudioManager.cpp in Sources */, E4F027CF16979CE200D4427D /* KRAudioSample.cpp in Sources */, E4F027DF1697BFFF00D4427D /* KRAudioBuffer.cpp in Sources */, + E416E4A91879112700FC2EEB /* RecastRasterization.cpp in Sources */, E4943232169E08D200BCB891 /* KRAmbientZone.cpp in Sources */, E404702118695DD200F01F42 /* KRTextureKTX.cpp in Sources */, E450273A16E0491D00FDEC5C /* KRReverbZone.cpp in Sources */, + E416E49B1879112700FC2EEB /* RecastAlloc.cpp in Sources */, E4AE635E1704FB0A00B460CD /* KRLODGroup.cpp in Sources */, E4EC73C21720B1FF0065299F /* KRVector4.cpp in Sources */, + E416E49D1879112700FC2EEB /* RecastArea.cpp in Sources */, E48CF943173453990005EBBB /* KRFloat.cpp in Sources */, E45134B71746A4A300443C21 /* KRBehavior.cpp in Sources */, ); From d38419b454194c8a36bea25da10b8b820280551b Mon Sep 17 00:00:00 2001 From: Peter Courtemanche Date: Mon, 6 Jan 2014 12:48:26 -0800 Subject: [PATCH 17/84] For iOS target - defined out parts of the TGA code that use GL constants that are not defined in GLES2 Commented out an assert() that crashes the app when the debug interface is asked to draw text at the bottom of the screen (i.e. the FPS) --HG-- branch : nfb --- KREngine/Kraken.xcodeproj/project.pbxproj | 6 ++---- KREngine/kraken/KRTexture.cpp | 4 +++- KREngine/kraken/KRTextureTGA.cpp | 19 +++++++++++++------ 3 files changed, 18 insertions(+), 11 deletions(-) diff --git a/KREngine/Kraken.xcodeproj/project.pbxproj b/KREngine/Kraken.xcodeproj/project.pbxproj index 18e49e4..f250401 100644 --- a/KREngine/Kraken.xcodeproj/project.pbxproj +++ b/KREngine/Kraken.xcodeproj/project.pbxproj @@ -10,6 +10,7 @@ 104A335E1672D31C001C8BA6 /* KRCollider.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 104A335C1672D31B001C8BA6 /* KRCollider.cpp */; }; 104A335F1672D31C001C8BA6 /* KRCollider.h in Headers */ = {isa = PBXBuildFile; fileRef = 104A335D1672D31C001C8BA6 /* KRCollider.h */; settings = {ATTRIBUTES = (); }; }; 10CC33A5168534F000BB9846 /* KRCamera.cpp in Sources */ = {isa = PBXBuildFile; fileRef = E48B3CBF14393E2F000C50E2 /* KRCamera.cpp */; }; + 60B07F8C187B42A8004710D4 /* KRTextureTGA.cpp in Sources */ = {isa = PBXBuildFile; fileRef = E4CA10EE1637BD58005D9400 /* KRTextureTGA.cpp */; }; E4030E4C160A3CF000592648 /* KRStockGeometry.h in Headers */ = {isa = PBXBuildFile; fileRef = E4030E4B160A3CF000592648 /* KRStockGeometry.h */; settings = {ATTRIBUTES = (); }; }; E4030E4D160A3CF000592648 /* KRStockGeometry.h in Headers */ = {isa = PBXBuildFile; fileRef = E4030E4B160A3CF000592648 /* KRStockGeometry.h */; settings = {ATTRIBUTES = (Public, ); }; }; E404702018695DD200F01F42 /* KRTextureKTX.cpp in Sources */ = {isa = PBXBuildFile; fileRef = E404701E18695DD200F01F42 /* KRTextureKTX.cpp */; }; @@ -228,7 +229,6 @@ E499BF1E16AE751E007FCDBE /* KRSceneManager.h in Headers */ = {isa = PBXBuildFile; fileRef = E46C214915364DDB009CABF3 /* KRSceneManager.h */; settings = {ATTRIBUTES = (); }; }; E499BF1F16AE753E007FCDBE /* KRCollider.h in Headers */ = {isa = PBXBuildFile; fileRef = 104A335D1672D31C001C8BA6 /* KRCollider.h */; settings = {ATTRIBUTES = (Public, ); }; }; E499BF2016AE755B007FCDBE /* KRPointLight.h in Headers */ = {isa = PBXBuildFile; fileRef = E461A157152E555400F2044A /* KRPointLight.h */; settings = {ATTRIBUTES = (); }; }; - E499BF2116AE75A7007FCDBE /* KREngine-common.h in Headers */ = {isa = PBXBuildFile; fileRef = E46DBE841512B9E200D59F86 /* KREngine-common.h */; settings = {ATTRIBUTES = (); }; }; E499BF2216AE760F007FCDBE /* krengine_osx.h in Headers */ = {isa = PBXBuildFile; fileRef = E4BBBB8C1512A40300F43B5B /* krengine_osx.h */; settings = {ATTRIBUTES = (Public, ); }; }; E499BF2316AE7636007FCDBE /* kraken-prefix.pch in Headers */ = {isa = PBXBuildFile; fileRef = E4BBBB8B1512A40300F43B5B /* kraken-prefix.pch */; }; E499BF2516AE8C20007FCDBE /* KREngine.mm in Sources */ = {isa = PBXBuildFile; fileRef = E491016F13C99BDC0098455B /* KREngine.mm */; }; @@ -277,7 +277,6 @@ E4CA10EA1637BD2B005D9400 /* KRTexturePVR.cpp in Sources */ = {isa = PBXBuildFile; fileRef = E4CA10E81637BD2B005D9400 /* KRTexturePVR.cpp */; }; E4CA10EC1637BD47005D9400 /* KRTextureTGA.h in Headers */ = {isa = PBXBuildFile; fileRef = E4CA10EB1637BD47005D9400 /* KRTextureTGA.h */; settings = {ATTRIBUTES = (); }; }; E4CA10ED1637BD47005D9400 /* KRTextureTGA.h in Headers */ = {isa = PBXBuildFile; fileRef = E4CA10EB1637BD47005D9400 /* KRTextureTGA.h */; settings = {ATTRIBUTES = (Public, ); }; }; - E4CA10EF1637BD58005D9400 /* KRTextureTGA.cpp in Sources */ = {isa = PBXBuildFile; fileRef = E4CA10EE1637BD58005D9400 /* KRTextureTGA.cpp */; }; E4CA10F01637BD58005D9400 /* KRTextureTGA.cpp in Sources */ = {isa = PBXBuildFile; fileRef = E4CA10EE1637BD58005D9400 /* KRTextureTGA.cpp */; }; E4CA10F61638BCAF005D9400 /* Accelerate.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = E4CA10F51638BCAE005D9400 /* Accelerate.framework */; }; E4CA10F81638BCBB005D9400 /* Accelerate.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = E4CA10F71638BCBB005D9400 /* Accelerate.framework */; }; @@ -1308,7 +1307,6 @@ E499BF1B16AE747C007FCDBE /* KRVector2.h in Headers */, E499BF1E16AE751E007FCDBE /* KRSceneManager.h in Headers */, E499BF2016AE755B007FCDBE /* KRPointLight.h in Headers */, - E499BF2116AE75A7007FCDBE /* KREngine-common.h in Headers */, E404702218695DD200F01F42 /* KRTextureKTX.h in Headers */, E4F027D016979CE200D4427D /* KRAudioSample.h in Headers */, E450273B16E0491D00FDEC5C /* KRReverbZone.h in Headers */, @@ -1611,6 +1609,7 @@ E491019713C99BDC0098455B /* KRVector3.cpp in Sources */, E491019813C99BDC0098455B /* KRTextureManager.cpp in Sources */, E491019913C99BDC0098455B /* KRTexture2D.cpp in Sources */, + 60B07F8C187B42A8004710D4 /* KRTextureTGA.cpp in Sources */, E491019A13C99BDC0098455B /* KRMeshManager.cpp in Sources */, E47C25A713F4F6AB00FF4370 /* KRShaderManager.cpp in Sources */, E47C25A913F4F6DD00FF4370 /* KRShader.cpp in Sources */, @@ -1643,7 +1642,6 @@ E4B175AC161F5A1000B8FB80 /* KRTexture.cpp in Sources */, E4B175B2161F5FAF00B8FB80 /* KRTextureCube.cpp in Sources */, E4CA10E91637BD2B005D9400 /* KRTexturePVR.cpp in Sources */, - E4CA10EF1637BD58005D9400 /* KRTextureTGA.cpp in Sources */, E4CA11781639CC90005D9400 /* KRViewport.cpp in Sources */, E4324BA816444C230043185B /* KRParticleSystem.cpp in Sources */, E4324BAE16444E120043185B /* KRParticleSystemNewtonian.cpp in Sources */, diff --git a/KREngine/kraken/KRTexture.cpp b/KREngine/kraken/KRTexture.cpp index af9f351..0f64489 100644 --- a/KREngine/kraken/KRTexture.cpp +++ b/KREngine/kraken/KRTexture.cpp @@ -79,7 +79,9 @@ void KRTexture::resize(int max_dim) if(!createGLTexture(target_dim)) { getContext().getTextureManager()->memoryChanged(-m_newTextureMemUsed); m_newTextureMemUsed = 0; - assert(false); +// assert(false); +// FINDME - assert commented out .. this crashes the app when running in the debug UI and changing 'Debug-Display' to show the framerate +// the FPS doesn't draw anymore either (the drop shadow draws but the text doesn't) } } } diff --git a/KREngine/kraken/KRTextureTGA.cpp b/KREngine/kraken/KRTextureTGA.cpp index 3451117..257be52 100644 --- a/KREngine/kraken/KRTextureTGA.cpp +++ b/KREngine/kraken/KRTextureTGA.cpp @@ -75,16 +75,23 @@ bool KRTextureTGA::uploadTexture(GLenum target, int lod_max_dim, int ¤t_lo m_pData->lock(); TGA_HEADER *pHeader = (TGA_HEADER *)m_pData->getStart(); unsigned char *pData = (unsigned char *)pHeader + (long)pHeader->idlength + (long)pHeader->colourmaplength * (long)pHeader->colourmaptype + sizeof(TGA_HEADER); - - + +// +// FINDME - many of the GL constants in here are not defined in GLES2 +#ifdef TARGET_OS_IPHONE + GLenum base_internal_format = GL_BGRA; +#else GLenum base_internal_format = pHeader->bitsperpixel == 24 ? GL_BGR : GL_BGRA; +#endif GLenum internal_format = 0; + +#ifndef TARGET_OS_IPHONE if(compress) { internal_format = pHeader->bitsperpixel == 24 ? GL_COMPRESSED_RGB_S3TC_DXT1_EXT : GL_COMPRESSED_RGBA_S3TC_DXT5_EXT; } +#endif - if(pHeader->colourmaptype != 0) { m_pData->unlock(); return false; // Mapped colors not supported @@ -169,7 +176,8 @@ KRTexture *KRTextureTGA::compress() GLDEBUG(glGenerateMipmap(GL_TEXTURE_2D)); GLint width = 0, height = 0, internal_format, base_internal_format; - + +#ifndef TARGET_OS_IPHONE GLDEBUG(glGetTexLevelParameteriv(GL_TEXTURE_2D, 0, GL_TEXTURE_WIDTH, &width)); GLDEBUG(glGetTexLevelParameteriv(GL_TEXTURE_2D, 0, GL_TEXTURE_HEIGHT, &height)); GLDEBUG(glGetTexLevelParameteriv(GL_TEXTURE_2D, 0, GL_TEXTURE_INTERNAL_FORMAT, &internal_format)); @@ -186,7 +194,6 @@ KRTexture *KRTextureTGA::compress() assert(false); // Not yet supported break; } - GLuint lod_level = 0; GLint compressed_size = 0; @@ -209,7 +216,7 @@ KRTexture *KRTextureTGA::compress() // err will equal GL_INVALID_VALUE when // assert(false); // Unexpected error } - +#endif GLDEBUG(glBindTexture(GL_TEXTURE_2D, 0)); getContext().getTextureManager()->selectTexture(0, NULL); From 5b77f22f2d8cf4222adfef2031f0e22358f39817 Mon Sep 17 00:00:00 2001 From: Peter Courtemanche Date: Tue, 7 Jan 2014 16:52:47 -0800 Subject: [PATCH 18/84] Changed AUGraph frame buffer size to match the AU render size (improves performance by about 10 times) Comment in AudioSample.cpp where we can hook in an alternate mp3 input streamer --HG-- branch : nfb --- KREngine/kraken/KRAudioManager.cpp | 3 +++ KREngine/kraken/KRAudioManager.h | 5 ++++- KREngine/kraken/KRAudioSample.cpp | 5 +++++ 3 files changed, 12 insertions(+), 1 deletion(-) diff --git a/KREngine/kraken/KRAudioManager.cpp b/KREngine/kraken/KRAudioManager.cpp index 4d8158f..fd7f485 100644 --- a/KREngine/kraken/KRAudioManager.cpp +++ b/KREngine/kraken/KRAudioManager.cpp @@ -215,8 +215,11 @@ void KRAudioManager::renderAudio(UInt32 inNumberFrames, AudioBufferList *ioData) uint64_t end_time = mach_absolute_time(); // uint64_t duration = (end_time - start_time) * m_timebase_info.numer / m_timebase_info.denom; // Nanoseconds +// double ms = duration; +// ms = ms / 1000000.0; // uint64_t max_duration = (uint64_t)inNumberFrames * 1000000000 / 44100; // fprintf(stderr, "audio load: %5.1f%% hrtf channels: %li\n", (float)(duration * 1000 / max_duration) / 10.0f, m_mapped_sources.size()); +// printf("ms %2.3f frames %ld audio load: %5.1f%% hrtf channels: %li\n", ms, (unsigned long) inNumberFrames, (float)(duration * 1000 / max_duration) / 10.0f, m_mapped_sources.size()); } float *KRAudioManager::getBlockAddress(int block_offset) diff --git a/KREngine/kraken/KRAudioManager.h b/KREngine/kraken/KRAudioManager.h index e3d0474..2c14c92 100644 --- a/KREngine/kraken/KRAudioManager.h +++ b/KREngine/kraken/KRAudioManager.h @@ -44,7 +44,10 @@ const int KRENGINE_AUDIO_MAX_POOL_SIZE = 32; const int KRENGINE_AUDIO_MAX_BUFFER_SIZE = 64*1024; const int KRENGINE_AUDIO_BUFFERS_PER_SOURCE = 3; -const int KRENGINE_AUDIO_BLOCK_LENGTH = 128; // Length of one block to process. Determines the latency of the audio system and sets size for FFT's used in HRTF convolution +const int KRENGINE_AUDIO_BLOCK_LENGTH = 1024; // Length of one block to process. Determines the latency of the audio system and sets size for FFT's used in HRTF convolution + // the AUGraph works in 1024 sample chunks. If we put a value of less then 1024 in here then we are making mutliple calls to our render functions without any + // improvement in latency and our audio render perfomance goes down significantly + const int KRENGINE_AUDIO_BLOCK_LOG2N = 7; // 2 ^ KRENGINE_AUDIO_BLOCK_LOG2N = KRENGINE_AUDIO_BLOCK_LENGTH const int KRENGINE_REVERB_MAX_FFT_LOG2 = 15; diff --git a/KREngine/kraken/KRAudioSample.cpp b/KREngine/kraken/KRAudioSample.cpp index b115711..f1f7cc4 100644 --- a/KREngine/kraken/KRAudioSample.cpp +++ b/KREngine/kraken/KRAudioSample.cpp @@ -231,6 +231,11 @@ void KRAudioSample::openFile() { // AudioFileInitializeWithCallbacks if(m_fileRef == NULL) { + +// printf("Call to KRAudioSample::openFile() with extension: %s\n", m_extension.c_str()); +// The m_extension is valid (it's either wav or mp3 for the files in Circa project) +// so we can key off the extension and use a different data handler for mp3 files if we want to +// // Temp variables UInt32 propertySize; From 516115f4d54349ee3c2eed3e32fcda7e8b81c68e Mon Sep 17 00:00:00 2001 From: Peter Courtemanche Date: Mon, 13 Jan 2014 11:54:21 -0800 Subject: [PATCH 19/84] Updated the constants defined for the audio block size --HG-- branch : nfb --- KREngine/kraken/KRAudioManager.h | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/KREngine/kraken/KRAudioManager.h b/KREngine/kraken/KRAudioManager.h index 2c14c92..c2bff56 100644 --- a/KREngine/kraken/KRAudioManager.h +++ b/KREngine/kraken/KRAudioManager.h @@ -44,12 +44,15 @@ const int KRENGINE_AUDIO_MAX_POOL_SIZE = 32; const int KRENGINE_AUDIO_MAX_BUFFER_SIZE = 64*1024; const int KRENGINE_AUDIO_BUFFERS_PER_SOURCE = 3; -const int KRENGINE_AUDIO_BLOCK_LENGTH = 1024; // Length of one block to process. Determines the latency of the audio system and sets size for FFT's used in HRTF convolution +const int KRENGINE_AUDIO_BLOCK_LOG2N = 7; // 2 ^ KRENGINE_AUDIO_BLOCK_LOG2N = KRENGINE_AUDIO_BLOCK_LENGTH + // 7 is 128, 8 -> 256, 9 -> 512, 10 -> 1024 (the size of the hardware (AUgraph) framebuffer) + // NOTE: the hrtf code use magic numbers everywhere and is hardcoded to 128 samples per frame + +const int KRENGINE_AUDIO_BLOCK_LENGTH = 1 << KRENGINE_AUDIO_BLOCK_LOG2N; + // Length of one block to process. Determines the latency of the audio system and sets size for FFT's used in HRTF convolution // the AUGraph works in 1024 sample chunks. If we put a value of less then 1024 in here then we are making mutliple calls to our render functions without any // improvement in latency and our audio render perfomance goes down significantly -const int KRENGINE_AUDIO_BLOCK_LOG2N = 7; // 2 ^ KRENGINE_AUDIO_BLOCK_LOG2N = KRENGINE_AUDIO_BLOCK_LENGTH - const int KRENGINE_REVERB_MAX_FFT_LOG2 = 15; const int KRENGINE_REVERB_WORKSPACE_SIZE = 1 << KRENGINE_REVERB_MAX_FFT_LOG2; From 45b86e8ccb2035235f79888a209899267cbcf76f Mon Sep 17 00:00:00 2001 From: Peter Courtemanche Date: Mon, 13 Jan 2014 15:16:30 -0800 Subject: [PATCH 20/84] Temporary fix to stop the app from crashing when it runs the texture resize routine with a size of 1024 for 2 minutes. Take a look at the FINDME comment. --HG-- branch : nfb --- KREngine/kraken/KRTextureManager.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/KREngine/kraken/KRTextureManager.cpp b/KREngine/kraken/KRTextureManager.cpp index 2f554c8..0e02202 100644 --- a/KREngine/kraken/KRTextureManager.cpp +++ b/KREngine/kraken/KRTextureManager.cpp @@ -329,6 +329,10 @@ void KRTextureManager::balanceTextureMemory() } } + if (maxDimActive > 512) maxDimActive = 512; // FINDME - hack to stop the texture resizer from blowing out its brains .. + // when the texture quality goes up to 1024, the app runs for about 2 mins and then + // runs out of memory. + // Resize active textures to balance the memory usage and mipmap levels for(std::set::iterator itr=m_activeTextures_streamer.begin(); itr != m_activeTextures_streamer.end() && memory_available > 0; itr++) { KRTexture *activeTexture = *itr; From d2f9434e13aafa7501608f0b07d38cfe77664527 Mon Sep 17 00:00:00 2001 From: Peter Courtemanche Date: Mon, 13 Jan 2014 19:03:41 -0800 Subject: [PATCH 21/84] =?UTF-8?q?Peter=E2=80=99s=20experiments=20with=20au?= =?UTF-8?q?dio=20..=20to=20be=20revised?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --HG-- branch : nfb --- KREngine/kraken/KRAudioManager.cpp | 78 +++++++++++++++++++++++++++--- KREngine/kraken/KRAudioManager.h | 5 +- 2 files changed, 75 insertions(+), 8 deletions(-) diff --git a/KREngine/kraken/KRAudioManager.cpp b/KREngine/kraken/KRAudioManager.cpp index fd7f485..dbd2be4 100644 --- a/KREngine/kraken/KRAudioManager.cpp +++ b/KREngine/kraken/KRAudioManager.cpp @@ -393,11 +393,12 @@ void KRAudioManager::renderBlock() if(m_enable_audio) { // ----====---- Render Direct / HRTF audio ----====---- - if(m_enable_hrtf) { - renderHRTF(); - } else { - renderITD(); - } +// if(m_enable_hrtf) { +// renderHRTF(); +// } else { +// renderITD(); +// } + renderHRTFbypass(); // ----====---- Render Indirect / Reverb channel ----====---- if(m_enable_reverb) { @@ -833,7 +834,7 @@ void KRAudioManager::initHRTF() sample->sample(0, 128, channel, spectral.realp, 1.0f, false); memset(spectral.realp + 128, 0, sizeof(float) * 128); memset(spectral.imagp, 0, sizeof(float) * 256); - vDSP_fft_zip(m_fft_setup[8 - KRENGINE_AUDIO_BLOCK_LOG2N], &spectral, 1, 8, kFFTDirection_Forward); +//**** vDSP_fft_zip(m_fft_setup[8 - KRENGINE_AUDIO_BLOCK_LOG2N], &spectral, 1, 8, kFFTDirection_Forward); m_hrtf_spectral[channel][pos] = spectral; } sample_index++; @@ -1778,6 +1779,71 @@ void KRAudioManager::renderHRTF() } } +void KRAudioManager::renderHRTFbypass() +{ + static float buffer[KRENGINE_AUDIO_BLOCK_LENGTH]; + + bool hack = true; + + unordered_multimap > >::iterator itr=m_mapped_sources.begin(); + while(itr != m_mapped_sources.end()) { + // Get the info for each sound source that should be run through the HRTF algorythm + KRVector2 source_direction = (*itr).first; + KRAudioSource *source = (*itr).second.first; + float gain_anticlick = (*itr).second.second.first; // this is the gain that we have at the start of the buffer + float gain = (*itr).second.second.second; // this is the gain that we want at the end of the buffer + + const char *unique = source->getSample().c_str(); // a unique identifier for a sound source +// printf("%s\n", unique); + + //??? Does the gain need to be stored into gain_anticlick when we are done? or is this done by the sound state update loop? + + source->sample(KRENGINE_AUDIO_BLOCK_LENGTH, 0, buffer, 1.0); + float *input = buffer; + + // Get pointers to the left and right channels of our stereo interleaved output buffer + int output_offset = (m_output_accumulation_block_start) % (KRENGINE_REVERB_MAX_SAMPLES * KRENGINE_MAX_OUTPUT_CHANNELS); + float *left = m_output_accumulation + output_offset; + float *right = left + 1; + + // panning +// if (hack) printf("Direction vector = %2.3f, %2.3f\n", source_direction.x, source_direction.y); + hack = false; + + // I think that -ve y is left, +ve y is right. + // Do the panning as a linear function since the distance gain is already set + double x = source_direction.x; + double y = source_direction.y; + double r = sqrt(x * x + y * y); + x = x / r; // normalized x + y = y / r; + + float vol_left = (-y + 1.0) / 1.5; if (vol_left < 0.05) vol_left = 0.05; if (vol_left > 1.0) vol_left = 1.0; + float vol_right = (y + 1.0) / 1.5; if (vol_right < 0.05) vol_right = 0.05; if (vol_right > 1.0) vol_right = 1.0; + +// vol_left = 1.0; +// vol_right = 1.0; +// printf("vol left = %2.3f, vol right = %2.3f for %2.3f, %2.3f\n", vol_left, vol_right, x, y); + + // Setup volume curve + float vol_start = gain_anticlick; + float vol_delta = (gain - gain_anticlick) / ((float) KRENGINE_AUDIO_BLOCK_LENGTH); + float mono = 0.0; + + for (long i = 0; i < KRENGINE_AUDIO_BLOCK_LENGTH; i++) { + mono = *input * vol_start; + *left += (mono * vol_left); + *right += (mono * vol_right); + vol_start += vol_delta; + left += 2; + right += 2; + input += 1; + } + + itr++; + } // end of while(itr) +} + void KRAudioManager::renderITD() { // FINDME, TODO - Need Inter-Temperal based phase shifting to support 3-d spatialized audio without headphones diff --git a/KREngine/kraken/KRAudioManager.h b/KREngine/kraken/KRAudioManager.h index c2bff56..6fbbb12 100644 --- a/KREngine/kraken/KRAudioManager.h +++ b/KREngine/kraken/KRAudioManager.h @@ -44,7 +44,7 @@ const int KRENGINE_AUDIO_MAX_POOL_SIZE = 32; const int KRENGINE_AUDIO_MAX_BUFFER_SIZE = 64*1024; const int KRENGINE_AUDIO_BUFFERS_PER_SOURCE = 3; -const int KRENGINE_AUDIO_BLOCK_LOG2N = 7; // 2 ^ KRENGINE_AUDIO_BLOCK_LOG2N = KRENGINE_AUDIO_BLOCK_LENGTH +const int KRENGINE_AUDIO_BLOCK_LOG2N = 10; // 2 ^ KRENGINE_AUDIO_BLOCK_LOG2N = KRENGINE_AUDIO_BLOCK_LENGTH // 7 is 128, 8 -> 256, 9 -> 512, 10 -> 1024 (the size of the hardware (AUgraph) framebuffer) // NOTE: the hrtf code use magic numbers everywhere and is hardcoded to 128 samples per frame @@ -218,7 +218,8 @@ private: void renderBlock(); void renderReverb(); void renderAmbient(); - void renderHRTF(); + void renderHRTF(); // render full HRTF + void renderHRTFbypass(); // render gain changes and panning relative to direction, but don't render HRTF void renderITD(); void renderReverbImpulseResponse(int impulse_response_offset, int frame_count_log2); From dea797a69c09bc557ba98e40499b9a69654bfedf Mon Sep 17 00:00:00 2001 From: Peter Courtemanche Date: Tue, 14 Jan 2014 10:22:42 -0800 Subject: [PATCH 22/84] Removing experimental code --HG-- branch : nfb --- KREngine/kraken/KRAudioManager.cpp | 78 +++--------------------------- KREngine/kraken/KRAudioManager.h | 5 +- 2 files changed, 8 insertions(+), 75 deletions(-) diff --git a/KREngine/kraken/KRAudioManager.cpp b/KREngine/kraken/KRAudioManager.cpp index dbd2be4..fd7f485 100644 --- a/KREngine/kraken/KRAudioManager.cpp +++ b/KREngine/kraken/KRAudioManager.cpp @@ -393,12 +393,11 @@ void KRAudioManager::renderBlock() if(m_enable_audio) { // ----====---- Render Direct / HRTF audio ----====---- -// if(m_enable_hrtf) { -// renderHRTF(); -// } else { -// renderITD(); -// } - renderHRTFbypass(); + if(m_enable_hrtf) { + renderHRTF(); + } else { + renderITD(); + } // ----====---- Render Indirect / Reverb channel ----====---- if(m_enable_reverb) { @@ -834,7 +833,7 @@ void KRAudioManager::initHRTF() sample->sample(0, 128, channel, spectral.realp, 1.0f, false); memset(spectral.realp + 128, 0, sizeof(float) * 128); memset(spectral.imagp, 0, sizeof(float) * 256); -//**** vDSP_fft_zip(m_fft_setup[8 - KRENGINE_AUDIO_BLOCK_LOG2N], &spectral, 1, 8, kFFTDirection_Forward); + vDSP_fft_zip(m_fft_setup[8 - KRENGINE_AUDIO_BLOCK_LOG2N], &spectral, 1, 8, kFFTDirection_Forward); m_hrtf_spectral[channel][pos] = spectral; } sample_index++; @@ -1779,71 +1778,6 @@ void KRAudioManager::renderHRTF() } } -void KRAudioManager::renderHRTFbypass() -{ - static float buffer[KRENGINE_AUDIO_BLOCK_LENGTH]; - - bool hack = true; - - unordered_multimap > >::iterator itr=m_mapped_sources.begin(); - while(itr != m_mapped_sources.end()) { - // Get the info for each sound source that should be run through the HRTF algorythm - KRVector2 source_direction = (*itr).first; - KRAudioSource *source = (*itr).second.first; - float gain_anticlick = (*itr).second.second.first; // this is the gain that we have at the start of the buffer - float gain = (*itr).second.second.second; // this is the gain that we want at the end of the buffer - - const char *unique = source->getSample().c_str(); // a unique identifier for a sound source -// printf("%s\n", unique); - - //??? Does the gain need to be stored into gain_anticlick when we are done? or is this done by the sound state update loop? - - source->sample(KRENGINE_AUDIO_BLOCK_LENGTH, 0, buffer, 1.0); - float *input = buffer; - - // Get pointers to the left and right channels of our stereo interleaved output buffer - int output_offset = (m_output_accumulation_block_start) % (KRENGINE_REVERB_MAX_SAMPLES * KRENGINE_MAX_OUTPUT_CHANNELS); - float *left = m_output_accumulation + output_offset; - float *right = left + 1; - - // panning -// if (hack) printf("Direction vector = %2.3f, %2.3f\n", source_direction.x, source_direction.y); - hack = false; - - // I think that -ve y is left, +ve y is right. - // Do the panning as a linear function since the distance gain is already set - double x = source_direction.x; - double y = source_direction.y; - double r = sqrt(x * x + y * y); - x = x / r; // normalized x - y = y / r; - - float vol_left = (-y + 1.0) / 1.5; if (vol_left < 0.05) vol_left = 0.05; if (vol_left > 1.0) vol_left = 1.0; - float vol_right = (y + 1.0) / 1.5; if (vol_right < 0.05) vol_right = 0.05; if (vol_right > 1.0) vol_right = 1.0; - -// vol_left = 1.0; -// vol_right = 1.0; -// printf("vol left = %2.3f, vol right = %2.3f for %2.3f, %2.3f\n", vol_left, vol_right, x, y); - - // Setup volume curve - float vol_start = gain_anticlick; - float vol_delta = (gain - gain_anticlick) / ((float) KRENGINE_AUDIO_BLOCK_LENGTH); - float mono = 0.0; - - for (long i = 0; i < KRENGINE_AUDIO_BLOCK_LENGTH; i++) { - mono = *input * vol_start; - *left += (mono * vol_left); - *right += (mono * vol_right); - vol_start += vol_delta; - left += 2; - right += 2; - input += 1; - } - - itr++; - } // end of while(itr) -} - void KRAudioManager::renderITD() { // FINDME, TODO - Need Inter-Temperal based phase shifting to support 3-d spatialized audio without headphones diff --git a/KREngine/kraken/KRAudioManager.h b/KREngine/kraken/KRAudioManager.h index 6fbbb12..c2bff56 100644 --- a/KREngine/kraken/KRAudioManager.h +++ b/KREngine/kraken/KRAudioManager.h @@ -44,7 +44,7 @@ const int KRENGINE_AUDIO_MAX_POOL_SIZE = 32; const int KRENGINE_AUDIO_MAX_BUFFER_SIZE = 64*1024; const int KRENGINE_AUDIO_BUFFERS_PER_SOURCE = 3; -const int KRENGINE_AUDIO_BLOCK_LOG2N = 10; // 2 ^ KRENGINE_AUDIO_BLOCK_LOG2N = KRENGINE_AUDIO_BLOCK_LENGTH +const int KRENGINE_AUDIO_BLOCK_LOG2N = 7; // 2 ^ KRENGINE_AUDIO_BLOCK_LOG2N = KRENGINE_AUDIO_BLOCK_LENGTH // 7 is 128, 8 -> 256, 9 -> 512, 10 -> 1024 (the size of the hardware (AUgraph) framebuffer) // NOTE: the hrtf code use magic numbers everywhere and is hardcoded to 128 samples per frame @@ -218,8 +218,7 @@ private: void renderBlock(); void renderReverb(); void renderAmbient(); - void renderHRTF(); // render full HRTF - void renderHRTFbypass(); // render gain changes and panning relative to direction, but don't render HRTF + void renderHRTF(); void renderITD(); void renderReverbImpulseResponse(int impulse_response_offset, int frame_count_log2); From 89f31f00ecbb32ce1d4117e4d11446827d9ff831 Mon Sep 17 00:00:00 2001 From: Peter Courtemanche Date: Tue, 14 Jan 2014 13:32:56 -0800 Subject: [PATCH 23/84] Added limiter and mute methods to KRAudioManager --HG-- branch : nfb --- KREngine/kraken/KRAudioManager.cpp | 115 ++++++++++++++++++++++++++++- KREngine/kraken/KRAudioManager.h | 4 +- 2 files changed, 117 insertions(+), 2 deletions(-) diff --git a/KREngine/kraken/KRAudioManager.cpp b/KREngine/kraken/KRAudioManager.cpp index fd7f485..8634bba 100644 --- a/KREngine/kraken/KRAudioManager.cpp +++ b/KREngine/kraken/KRAudioManager.cpp @@ -406,6 +406,9 @@ void KRAudioManager::renderBlock() // ----====---- Render Ambient Sound ----====---- renderAmbient(); + + // ----====---- Render Ambient Sound ----====---- + renderLimiter(); } // ----====---- Advance audio sources ----====---- @@ -1931,4 +1934,114 @@ void KRAudioManager::renderITD() */ -} \ No newline at end of file +} + +static bool audioIsMuted = false; +static bool audioShouldBecomeMuted = false; +static bool audioShouldBecomeUnmuted = false; + +void audioLimit_Mute(bool onNotOff) { + if (onNotOff) { + if (audioIsMuted) return; + audioShouldBecomeMuted = true; + audioShouldBecomeUnmuted = false; + } + else { + if (!audioIsMuted) return; + audioShouldBecomeMuted = false; + audioShouldBecomeUnmuted = true; + } +} + +float audioGetLimitParameters_Stereo(float *buffer, unsigned long framesize, + unsigned long *attack_sample_position, float *peak) +{ + float limitvol = 1.0; + long attack_position = -1; + *peak = 0.0; + float max = 0.0; + float amplitude = 0.0; + + float *src = buffer; + for (unsigned long i = 0; i < framesize * 2; i++) { + amplitude = fabs(*src); src++; + if (amplitude > max) max = amplitude; + if (amplitude > 0.995) if (attack_position < 0) attack_position = (i+1) / 2; + } + if (max > 0.995) limitvol = 0.995 / max; + *peak = max; + + if (attack_position < 0) attack_position = framesize; + *attack_sample_position = (unsigned long) attack_position; + return limitvol; +} // returns the new limit volume, *attack_sample_position tells how fast we need to reach the new limit + +void audioLimit_Stereo(float *stereo_buffer, unsigned long framesize) +{ + static float limit_value = 1.0; + + // (1) get the limiting parameters for the incoming audio data + float previouslimitvol = limit_value; + float peak; + unsigned long attack_sample_position; + + // (1a) check for a mute or unmute state then get the next limit volume + float nextlimitvol = 0.0; + if (audioIsMuted && audioShouldBecomeUnmuted) { audioIsMuted = false; audioShouldBecomeUnmuted = false; } + if (audioShouldBecomeMuted) { audioIsMuted = true; audioShouldBecomeMuted = false; } + if (!audioIsMuted) nextlimitvol = audioGetLimitParameters_Stereo(stereo_buffer, framesize, &attack_sample_position, &peak); + + // (1b) if no limiting is needed then return + if ((1.0 == nextlimitvol) && (1.0 == previouslimitvol)) { return; } // no limiting necessary + + // (2) calculate limiting factors + float deltavol = 0.0; + if (previouslimitvol != nextlimitvol) { + deltavol = (nextlimitvol - previouslimitvol) / (float) attack_sample_position; + } + + // (3) do the limiting + float *src = stereo_buffer; + + if (0.0 == deltavol) { // fixed volume + for (unsigned long i=0; i < framesize; i++) { + *src = *src * nextlimitvol; + src++; + *src = *src * nextlimitvol; + src++; + } + } + else { + for (unsigned long i=0; i < attack_sample_position; i++) { // attack phase + *src = *src * previouslimitvol; + src++; + *src = *src * previouslimitvol; + src++; + previouslimitvol += deltavol; + } + if (nextlimitvol < 1.0) { // plateau phase + for (unsigned long i = attack_sample_position; i < framesize; i++) { + *src = *src * nextlimitvol; + src++; + *src = *src * nextlimitvol; + src++; + } + } + } + + // (4) save our limit level for next time + limit_value = nextlimitvol; +} + +void KRAudioManager::mute(bool onNotOff) +{ + audioLimit_Mute(onNotOff); +} + +void KRAudioManager::renderLimiter() +{ + int output_offset = (m_output_accumulation_block_start) % (KRENGINE_REVERB_MAX_SAMPLES * KRENGINE_MAX_OUTPUT_CHANNELS); + float *output = m_output_accumulation + output_offset; + unsigned long numframes = KRENGINE_AUDIO_BLOCK_LENGTH; + audioLimit_Stereo(output, numframes); +} diff --git a/KREngine/kraken/KRAudioManager.h b/KREngine/kraken/KRAudioManager.h index c2bff56..a39357c 100644 --- a/KREngine/kraken/KRAudioManager.h +++ b/KREngine/kraken/KRAudioManager.h @@ -41,7 +41,7 @@ #include "KRAudioSource.h" const int KRENGINE_AUDIO_MAX_POOL_SIZE = 32; -const int KRENGINE_AUDIO_MAX_BUFFER_SIZE = 64*1024; +const int KRENGINE_AUDIO_MAX_BUFFER_SIZE = 64*1024; // this is the buffer for our decoded audio (not the source file data) const int KRENGINE_AUDIO_BUFFERS_PER_SOURCE = 3; const int KRENGINE_AUDIO_BLOCK_LOG2N = 7; // 2 ^ KRENGINE_AUDIO_BLOCK_LOG2N = KRENGINE_AUDIO_BLOCK_LENGTH @@ -133,6 +133,7 @@ public: KRAudioBuffer *getBuffer(KRAudioSample &audio_sample, int buffer_index); + void mute(bool onNotOff); void startFrame(float deltaTime); @@ -221,6 +222,7 @@ private: void renderHRTF(); void renderITD(); void renderReverbImpulseResponse(int impulse_response_offset, int frame_count_log2); + void renderLimiter(); std::vector m_hrtf_sample_locations; float *m_hrtf_data; From 62ca3305a64bd8fc02503c520d0ff4f6b2db4284 Mon Sep 17 00:00:00 2001 From: Peter Courtemanche Date: Tue, 14 Jan 2014 14:09:21 -0800 Subject: [PATCH 24/84] Made the audio mute function static (so it can be called from anywhere) --HG-- branch : nfb --- KREngine/kraken/KRAudioManager.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/KREngine/kraken/KRAudioManager.h b/KREngine/kraken/KRAudioManager.h index a39357c..550eaaf 100644 --- a/KREngine/kraken/KRAudioManager.h +++ b/KREngine/kraken/KRAudioManager.h @@ -133,7 +133,7 @@ public: KRAudioBuffer *getBuffer(KRAudioSample &audio_sample, int buffer_index); - void mute(bool onNotOff); + static void mute(bool onNotOff); void startFrame(float deltaTime); From edbae68dc5286805f58b1b2b376e9d967ff0fb17 Mon Sep 17 00:00:00 2001 From: Peter Courtemanche Date: Tue, 14 Jan 2014 14:41:54 -0800 Subject: [PATCH 25/84] Little bug fix for the mute/unmute code --HG-- branch : nfb --- KREngine/kraken/KRAudioManager.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/KREngine/kraken/KRAudioManager.cpp b/KREngine/kraken/KRAudioManager.cpp index 8634bba..72aa05b 100644 --- a/KREngine/kraken/KRAudioManager.cpp +++ b/KREngine/kraken/KRAudioManager.cpp @@ -1983,7 +1983,7 @@ void audioLimit_Stereo(float *stereo_buffer, unsigned long framesize) // (1) get the limiting parameters for the incoming audio data float previouslimitvol = limit_value; float peak; - unsigned long attack_sample_position; + unsigned long attack_sample_position = framesize; // (1a) check for a mute or unmute state then get the next limit volume float nextlimitvol = 0.0; From ca1af8dd3e67b774d3a7f3301cba2c30492c06e8 Mon Sep 17 00:00:00 2001 From: Peter Courtemanche Date: Tue, 14 Jan 2014 15:51:32 -0800 Subject: [PATCH 26/84] Changed the buffer constants to decrease the number of drop outs (still needs some work) --HG-- branch : nfb --- KREngine/kraken/KRAudioManager.h | 21 +++++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/KREngine/kraken/KRAudioManager.h b/KREngine/kraken/KRAudioManager.h index 550eaaf..132e662 100644 --- a/KREngine/kraken/KRAudioManager.h +++ b/KREngine/kraken/KRAudioManager.h @@ -40,18 +40,27 @@ #include "KRMat4.h" #include "KRAudioSource.h" -const int KRENGINE_AUDIO_MAX_POOL_SIZE = 32; -const int KRENGINE_AUDIO_MAX_BUFFER_SIZE = 64*1024; // this is the buffer for our decoded audio (not the source file data) +const int KRENGINE_AUDIO_MAX_POOL_SIZE = 40; //32; + // for Circa we play a maximum of 7 mono audio streams at once + cross fading with ambient + // so we could safely say a maximum of 10 streams, which would be 33 buffers + +const int KRENGINE_AUDIO_MAX_BUFFER_SIZE = 5120; // in bytes + // this is the buffer for our decoded audio (not the source file data) + // it should be greater then 1152 samples (the size of an mp3 frame in samples) + // so it should be greater then 2304 bytes and also a multiple of 128 samples (to make + // the data flow efficient) but it shouldn't be too large or it will cause + // the render loop to stall out decoding large chunks of mp3 data. + // 2560 bytes would be the smallest size for mono sources, and 5120 would be smallest for stereo. + const int KRENGINE_AUDIO_BUFFERS_PER_SOURCE = 3; const int KRENGINE_AUDIO_BLOCK_LOG2N = 7; // 2 ^ KRENGINE_AUDIO_BLOCK_LOG2N = KRENGINE_AUDIO_BLOCK_LENGTH - // 7 is 128, 8 -> 256, 9 -> 512, 10 -> 1024 (the size of the hardware (AUgraph) framebuffer) - // NOTE: the hrtf code use magic numbers everywhere and is hardcoded to 128 samples per frame + // 7 is 128 .. NOTE: the hrtf code uses magic numbers everywhere and is hardcoded to 128 samples per frame const int KRENGINE_AUDIO_BLOCK_LENGTH = 1 << KRENGINE_AUDIO_BLOCK_LOG2N; // Length of one block to process. Determines the latency of the audio system and sets size for FFT's used in HRTF convolution - // the AUGraph works in 1024 sample chunks. If we put a value of less then 1024 in here then we are making mutliple calls to our render functions without any - // improvement in latency and our audio render perfomance goes down significantly + // the AUGraph works in 1024 sample chunks. At 128 we are making 8 consecutive calls to the renderBlock method for each + // render initiated by the AUGraph. const int KRENGINE_REVERB_MAX_FFT_LOG2 = 15; const int KRENGINE_REVERB_WORKSPACE_SIZE = 1 << KRENGINE_REVERB_MAX_FFT_LOG2; From cae949c2596ce3a6db7aa7a01cdd1be3905b91e5 Mon Sep 17 00:00:00 2001 From: Peter Courtemanche Date: Tue, 14 Jan 2014 19:12:51 -0800 Subject: [PATCH 27/84] AudioManager - increased pool size to reduce dropouts. Change startFrame locks to non-blocking - improves FPS a bit and dropout rate goes down. --HG-- branch : nfb --- KREngine/kraken/KRAudioManager.cpp | 14 +++++++++++++- KREngine/kraken/KRAudioManager.h | 7 ++++--- 2 files changed, 17 insertions(+), 4 deletions(-) diff --git a/KREngine/kraken/KRAudioManager.cpp b/KREngine/kraken/KRAudioManager.cpp index 72aa05b..9f8449a 100644 --- a/KREngine/kraken/KRAudioManager.cpp +++ b/KREngine/kraken/KRAudioManager.cpp @@ -1491,7 +1491,19 @@ void KRAudioManager::setGlobalAmbientGain(float gain) void KRAudioManager::startFrame(float deltaTime) { - m_mutex.lock(); + static unsigned long trackCount = 0; + static unsigned long trackMissed = 0; + trackCount++; + if (trackCount > 200) { +// printf("Missed %ld out of 200 try_lock attempts on audio startFrame\n", trackMissed); + trackCount = 0; + trackMissed = 0; + } + + if (!m_mutex.try_lock()) { + trackMissed++; + return; // if we are rendering audio don't update audio state + } // NOTE: this misses anywhere from 0 to to 30 times out of 200 on the iPad2 // ----====---- Determine Ambient Zone Contributions ----====---- m_ambient_zone_weights.clear(); diff --git a/KREngine/kraken/KRAudioManager.h b/KREngine/kraken/KRAudioManager.h index 132e662..c19697c 100644 --- a/KREngine/kraken/KRAudioManager.h +++ b/KREngine/kraken/KRAudioManager.h @@ -40,9 +40,10 @@ #include "KRMat4.h" #include "KRAudioSource.h" -const int KRENGINE_AUDIO_MAX_POOL_SIZE = 40; //32; - // for Circa we play a maximum of 7 mono audio streams at once + cross fading with ambient - // so we could safely say a maximum of 10 streams, which would be 33 buffers +const int KRENGINE_AUDIO_MAX_POOL_SIZE = 60; //32; + // for Circa we play a maximum of 11 mono audio streams at once + cross fading with ambient + // so we could safely say a maximum of 12 or 13 streams, which would be 39 buffers + // do the WAV files for the reverb use the same buffer pool ??? const int KRENGINE_AUDIO_MAX_BUFFER_SIZE = 5120; // in bytes // this is the buffer for our decoded audio (not the source file data) From 05a218973ca0e0172e755ec4b04a33ca0832d478 Mon Sep 17 00:00:00 2001 From: Kearwood Gilbert Date: Tue, 14 Jan 2014 23:16:44 -0800 Subject: [PATCH 28/84] Removed Recast --HG-- branch : nfb --- KREngine/3rdparty/recast/include/Recast.h | 1135 ------------- .../3rdparty/recast/include/RecastAlloc.h | 124 -- .../3rdparty/recast/include/RecastAssert.h | 33 - KREngine/3rdparty/recast/source/Recast.cpp | 489 ------ .../3rdparty/recast/source/RecastAlloc.cpp | 88 - .../3rdparty/recast/source/RecastArea.cpp | 602 ------- .../3rdparty/recast/source/RecastContour.cpp | 851 ---------- .../3rdparty/recast/source/RecastFilter.cpp | 207 --- .../3rdparty/recast/source/RecastLayers.cpp | 620 ------- .../3rdparty/recast/source/RecastMesh.cpp | 1433 ----------------- .../recast/source/RecastMeshDetail.cpp | 1245 -------------- .../recast/source/RecastRasterization.cpp | 387 ----- .../3rdparty/recast/source/RecastRegion.cpp | 1337 --------------- KREngine/Kraken.xcodeproj/project.pbxproj | 102 -- 14 files changed, 8653 deletions(-) delete mode 100755 KREngine/3rdparty/recast/include/Recast.h delete mode 100755 KREngine/3rdparty/recast/include/RecastAlloc.h delete mode 100755 KREngine/3rdparty/recast/include/RecastAssert.h delete mode 100755 KREngine/3rdparty/recast/source/Recast.cpp delete mode 100755 KREngine/3rdparty/recast/source/RecastAlloc.cpp delete mode 100755 KREngine/3rdparty/recast/source/RecastArea.cpp delete mode 100755 KREngine/3rdparty/recast/source/RecastContour.cpp delete mode 100755 KREngine/3rdparty/recast/source/RecastFilter.cpp delete mode 100755 KREngine/3rdparty/recast/source/RecastLayers.cpp delete mode 100755 KREngine/3rdparty/recast/source/RecastMesh.cpp delete mode 100755 KREngine/3rdparty/recast/source/RecastMeshDetail.cpp delete mode 100755 KREngine/3rdparty/recast/source/RecastRasterization.cpp delete mode 100755 KREngine/3rdparty/recast/source/RecastRegion.cpp diff --git a/KREngine/3rdparty/recast/include/Recast.h b/KREngine/3rdparty/recast/include/Recast.h deleted file mode 100755 index 336837e..0000000 --- a/KREngine/3rdparty/recast/include/Recast.h +++ /dev/null @@ -1,1135 +0,0 @@ -// -// Copyright (c) 2009-2010 Mikko Mononen memon@inside.org -// -// This software is provided 'as-is', without any express or implied -// warranty. In no event will the authors be held liable for any damages -// arising from the use of this software. -// Permission is granted to anyone to use this software for any purpose, -// including commercial applications, and to alter it and redistribute it -// freely, subject to the following restrictions: -// 1. The origin of this software must not be misrepresented; you must not -// claim that you wrote the original software. If you use this software -// in a product, an acknowledgment in the product documentation would be -// appreciated but is not required. -// 2. Altered source versions must be plainly marked as such, and must not be -// misrepresented as being the original software. -// 3. This notice may not be removed or altered from any source distribution. -// - -#ifndef RECAST_H -#define RECAST_H - -/// The value of PI used by Recast. -static const float RC_PI = 3.14159265f; - -/// Recast log categories. -/// @see rcContext -enum rcLogCategory -{ - RC_LOG_PROGRESS = 1, ///< A progress log entry. - RC_LOG_WARNING, ///< A warning log entry. - RC_LOG_ERROR, ///< An error log entry. -}; - -/// Recast performance timer categories. -/// @see rcContext -enum rcTimerLabel -{ - /// The user defined total time of the build. - RC_TIMER_TOTAL, - /// A user defined build time. - RC_TIMER_TEMP, - /// The time to rasterize the triangles. (See: #rcRasterizeTriangle) - RC_TIMER_RASTERIZE_TRIANGLES, - /// The time to build the compact heightfield. (See: #rcBuildCompactHeightfield) - RC_TIMER_BUILD_COMPACTHEIGHTFIELD, - /// The total time to build the contours. (See: #rcBuildContours) - RC_TIMER_BUILD_CONTOURS, - /// The time to trace the boundaries of the contours. (See: #rcBuildContours) - RC_TIMER_BUILD_CONTOURS_TRACE, - /// The time to simplify the contours. (See: #rcBuildContours) - RC_TIMER_BUILD_CONTOURS_SIMPLIFY, - /// The time to filter ledge spans. (See: #rcFilterLedgeSpans) - RC_TIMER_FILTER_BORDER, - /// The time to filter low height spans. (See: #rcFilterWalkableLowHeightSpans) - RC_TIMER_FILTER_WALKABLE, - /// The time to apply the median filter. (See: #rcMedianFilterWalkableArea) - RC_TIMER_MEDIAN_AREA, - /// The time to filter low obstacles. (See: #rcFilterLowHangingWalkableObstacles) - RC_TIMER_FILTER_LOW_OBSTACLES, - /// The time to build the polygon mesh. (See: #rcBuildPolyMesh) - RC_TIMER_BUILD_POLYMESH, - /// The time to merge polygon meshes. (See: #rcMergePolyMeshes) - RC_TIMER_MERGE_POLYMESH, - /// The time to erode the walkable area. (See: #rcErodeWalkableArea) - RC_TIMER_ERODE_AREA, - /// The time to mark a box area. (See: #rcMarkBoxArea) - RC_TIMER_MARK_BOX_AREA, - /// The time to mark a cylinder area. (See: #rcMarkCylinderArea) - RC_TIMER_MARK_CYLINDER_AREA, - /// The time to mark a convex polygon area. (See: #rcMarkConvexPolyArea) - RC_TIMER_MARK_CONVEXPOLY_AREA, - /// The total time to build the distance field. (See: #rcBuildDistanceField) - RC_TIMER_BUILD_DISTANCEFIELD, - /// The time to build the distances of the distance field. (See: #rcBuildDistanceField) - RC_TIMER_BUILD_DISTANCEFIELD_DIST, - /// The time to blur the distance field. (See: #rcBuildDistanceField) - RC_TIMER_BUILD_DISTANCEFIELD_BLUR, - /// The total time to build the regions. (See: #rcBuildRegions, #rcBuildRegionsMonotone) - RC_TIMER_BUILD_REGIONS, - /// The total time to apply the watershed algorithm. (See: #rcBuildRegions) - RC_TIMER_BUILD_REGIONS_WATERSHED, - /// The time to expand regions while applying the watershed algorithm. (See: #rcBuildRegions) - RC_TIMER_BUILD_REGIONS_EXPAND, - /// The time to flood regions while applying the watershed algorithm. (See: #rcBuildRegions) - RC_TIMER_BUILD_REGIONS_FLOOD, - /// The time to filter out small regions. (See: #rcBuildRegions, #rcBuildRegionsMonotone) - RC_TIMER_BUILD_REGIONS_FILTER, - /// The time to build heightfield layers. (See: #rcBuildHeightfieldLayers) - RC_TIMER_BUILD_LAYERS, - /// The time to build the polygon mesh detail. (See: #rcBuildPolyMeshDetail) - RC_TIMER_BUILD_POLYMESHDETAIL, - /// The time to merge polygon mesh details. (See: #rcMergePolyMeshDetails) - RC_TIMER_MERGE_POLYMESHDETAIL, - /// The maximum number of timers. (Used for iterating timers.) - RC_MAX_TIMERS -}; - -/// Provides an interface for optional logging and performance tracking of the Recast -/// build process. -/// @ingroup recast -class rcContext -{ -public: - - /// Contructor. - /// @param[in] state TRUE if the logging and performance timers should be enabled. [Default: true] - inline rcContext(bool state = true) : m_logEnabled(state), m_timerEnabled(state) {} - virtual ~rcContext() {} - - /// Enables or disables logging. - /// @param[in] state TRUE if logging should be enabled. - inline void enableLog(bool state) { m_logEnabled = state; } - - /// Clears all log entries. - inline void resetLog() { if (m_logEnabled) doResetLog(); } - - /// Logs a message. - /// @param[in] category The category of the message. - /// @param[in] format The message. - void log(const rcLogCategory category, const char* format, ...); - - /// Enables or disables the performance timers. - /// @param[in] state TRUE if timers should be enabled. - inline void enableTimer(bool state) { m_timerEnabled = state; } - - /// Clears all peformance timers. (Resets all to unused.) - inline void resetTimers() { if (m_timerEnabled) doResetTimers(); } - - /// Starts the specified performance timer. - /// @param label The category of timer. - inline void startTimer(const rcTimerLabel label) { if (m_timerEnabled) doStartTimer(label); } - - /// Stops the specified performance timer. - /// @param label The category of the timer. - inline void stopTimer(const rcTimerLabel label) { if (m_timerEnabled) doStopTimer(label); } - - /// Returns the total accumulated time of the specified performance timer. - /// @param label The category of the timer. - /// @return The accumulated time of the timer, or -1 if timers are disabled or the timer has never been started. - inline int getAccumulatedTime(const rcTimerLabel label) const { return m_timerEnabled ? doGetAccumulatedTime(label) : -1; } - -protected: - - /// Clears all log entries. - virtual void doResetLog() {} - - /// Logs a message. - /// @param[in] category The category of the message. - /// @param[in] msg The formatted message. - /// @param[in] len The length of the formatted message. - virtual void doLog(const rcLogCategory /*category*/, const char* /*msg*/, const int /*len*/) {} - - /// Clears all timers. (Resets all to unused.) - virtual void doResetTimers() {} - - /// Starts the specified performance timer. - /// @param[in] label The category of timer. - virtual void doStartTimer(const rcTimerLabel /*label*/) {} - - /// Stops the specified performance timer. - /// @param[in] label The category of the timer. - virtual void doStopTimer(const rcTimerLabel /*label*/) {} - - /// Returns the total accumulated time of the specified performance timer. - /// @param[in] label The category of the timer. - /// @return The accumulated time of the timer, or -1 if timers are disabled or the timer has never been started. - virtual int doGetAccumulatedTime(const rcTimerLabel /*label*/) const { return -1; } - - /// True if logging is enabled. - bool m_logEnabled; - - /// True if the performance timers are enabled. - bool m_timerEnabled; -}; - -/// Specifies a configuration to use when performing Recast builds. -/// @ingroup recast -struct rcConfig -{ - /// The width of the field along the x-axis. [Limit: >= 0] [Units: vx] - int width; - - /// The height of the field along the z-axis. [Limit: >= 0] [Units: vx] - int height; - - /// The width/height size of tile's on the xz-plane. [Limit: >= 0] [Units: vx] - int tileSize; - - /// The size of the non-navigable border around the heightfield. [Limit: >=0] [Units: vx] - int borderSize; - - /// The xz-plane cell size to use for fields. [Limit: > 0] [Units: wu] - float cs; - - /// The y-axis cell size to use for fields. [Limit: > 0] [Units: wu] - float ch; - - /// The minimum bounds of the field's AABB. [(x, y, z)] [Units: wu] - float bmin[3]; - - /// The maximum bounds of the field's AABB. [(x, y, z)] [Units: wu] - float bmax[3]; - - /// The maximum slope that is considered walkable. [Limits: 0 <= value < 90] [Units: Degrees] - float walkableSlopeAngle; - - /// Minimum floor to 'ceiling' height that will still allow the floor area to - /// be considered walkable. [Limit: >= 3] [Units: vx] - int walkableHeight; - - /// Maximum ledge height that is considered to still be traversable. [Limit: >=0] [Units: vx] - int walkableClimb; - - /// The distance to erode/shrink the walkable area of the heightfield away from - /// obstructions. [Limit: >=0] [Units: vx] - int walkableRadius; - - /// The maximum allowed length for contour edges along the border of the mesh. [Limit: >=0] [Units: vx] - int maxEdgeLen; - - /// The maximum distance a simplfied contour's border edges should deviate - /// the original raw contour. [Limit: >=0] [Units: vx] - float maxSimplificationError; - - /// The minimum number of cells allowed to form isolated island areas. [Limit: >=0] [Units: vx] - int minRegionArea; - - /// Any regions with a span count smaller than this value will, if possible, - /// be merged with larger regions. [Limit: >=0] [Units: vx] - int mergeRegionArea; - - /// The maximum number of vertices allowed for polygons generated during the - /// contour to polygon conversion process. [Limit: >= 3] - int maxVertsPerPoly; - - /// Sets the sampling distance to use when generating the detail mesh. - /// (For height detail only.) [Limits: 0 or >= 0.9] [Units: wu] - float detailSampleDist; - - /// The maximum distance the detail mesh surface should deviate from heightfield - /// data. (For height detail only.) [Limit: >=0] [Units: wu] - float detailSampleMaxError; -}; - -/// Defines the number of bits allocated to rcSpan::smin and rcSpan::smax. -static const int RC_SPAN_HEIGHT_BITS = 13; -/// Defines the maximum value for rcSpan::smin and rcSpan::smax. -static const int RC_SPAN_MAX_HEIGHT = (1< void rcIgnoreUnused(const T&) { } - -/// Swaps the values of the two parameters. -/// @param[in,out] a Value A -/// @param[in,out] b Value B -template inline void rcSwap(T& a, T& b) { T t = a; a = b; b = t; } - -/// Returns the minimum of two values. -/// @param[in] a Value A -/// @param[in] b Value B -/// @return The minimum of the two values. -template inline T rcMin(T a, T b) { return a < b ? a : b; } - -/// Returns the maximum of two values. -/// @param[in] a Value A -/// @param[in] b Value B -/// @return The maximum of the two values. -template inline T rcMax(T a, T b) { return a > b ? a : b; } - -/// Returns the absolute value. -/// @param[in] a The value. -/// @return The absolute value of the specified value. -template inline T rcAbs(T a) { return a < 0 ? -a : a; } - -/// Returns the square of the value. -/// @param[in] a The value. -/// @return The square of the value. -template inline T rcSqr(T a) { return a*a; } - -/// Clamps the value to the specified range. -/// @param[in] v The value to clamp. -/// @param[in] mn The minimum permitted return value. -/// @param[in] mx The maximum permitted return value. -/// @return The value, clamped to the specified range. -template inline T rcClamp(T v, T mn, T mx) { return v < mn ? mn : (v > mx ? mx : v); } - -/// Returns the square root of the value. -/// @param[in] x The value. -/// @return The square root of the vlaue. -float rcSqrt(float x); - -/// @} -/// @name Vector helper functions. -/// @{ - -/// Derives the cross product of two vectors. (@p v1 x @p v2) -/// @param[out] dest The cross product. [(x, y, z)] -/// @param[in] v1 A Vector [(x, y, z)] -/// @param[in] v2 A vector [(x, y, z)] -inline void rcVcross(float* dest, const float* v1, const float* v2) -{ - dest[0] = v1[1]*v2[2] - v1[2]*v2[1]; - dest[1] = v1[2]*v2[0] - v1[0]*v2[2]; - dest[2] = v1[0]*v2[1] - v1[1]*v2[0]; -} - -/// Derives the dot product of two vectors. (@p v1 . @p v2) -/// @param[in] v1 A Vector [(x, y, z)] -/// @param[in] v2 A vector [(x, y, z)] -/// @return The dot product. -inline float rcVdot(const float* v1, const float* v2) -{ - return v1[0]*v2[0] + v1[1]*v2[1] + v1[2]*v2[2]; -} - -/// Performs a scaled vector addition. (@p v1 + (@p v2 * @p s)) -/// @param[out] dest The result vector. [(x, y, z)] -/// @param[in] v1 The base vector. [(x, y, z)] -/// @param[in] v2 The vector to scale and add to @p v1. [(x, y, z)] -/// @param[in] s The amount to scale @p v2 by before adding to @p v1. -inline void rcVmad(float* dest, const float* v1, const float* v2, const float s) -{ - dest[0] = v1[0]+v2[0]*s; - dest[1] = v1[1]+v2[1]*s; - dest[2] = v1[2]+v2[2]*s; -} - -/// Performs a vector addition. (@p v1 + @p v2) -/// @param[out] dest The result vector. [(x, y, z)] -/// @param[in] v1 The base vector. [(x, y, z)] -/// @param[in] v2 The vector to add to @p v1. [(x, y, z)] -inline void rcVadd(float* dest, const float* v1, const float* v2) -{ - dest[0] = v1[0]+v2[0]; - dest[1] = v1[1]+v2[1]; - dest[2] = v1[2]+v2[2]; -} - -/// Performs a vector subtraction. (@p v1 - @p v2) -/// @param[out] dest The result vector. [(x, y, z)] -/// @param[in] v1 The base vector. [(x, y, z)] -/// @param[in] v2 The vector to subtract from @p v1. [(x, y, z)] -inline void rcVsub(float* dest, const float* v1, const float* v2) -{ - dest[0] = v1[0]-v2[0]; - dest[1] = v1[1]-v2[1]; - dest[2] = v1[2]-v2[2]; -} - -/// Selects the minimum value of each element from the specified vectors. -/// @param[in,out] mn A vector. (Will be updated with the result.) [(x, y, z)] -/// @param[in] v A vector. [(x, y, z)] -inline void rcVmin(float* mn, const float* v) -{ - mn[0] = rcMin(mn[0], v[0]); - mn[1] = rcMin(mn[1], v[1]); - mn[2] = rcMin(mn[2], v[2]); -} - -/// Selects the maximum value of each element from the specified vectors. -/// @param[in,out] mx A vector. (Will be updated with the result.) [(x, y, z)] -/// @param[in] v A vector. [(x, y, z)] -inline void rcVmax(float* mx, const float* v) -{ - mx[0] = rcMax(mx[0], v[0]); - mx[1] = rcMax(mx[1], v[1]); - mx[2] = rcMax(mx[2], v[2]); -} - -/// Performs a vector copy. -/// @param[out] dest The result. [(x, y, z)] -/// @param[in] v The vector to copy. [(x, y, z)] -inline void rcVcopy(float* dest, const float* v) -{ - dest[0] = v[0]; - dest[1] = v[1]; - dest[2] = v[2]; -} - -/// Returns the distance between two points. -/// @param[in] v1 A point. [(x, y, z)] -/// @param[in] v2 A point. [(x, y, z)] -/// @return The distance between the two points. -inline float rcVdist(const float* v1, const float* v2) -{ - float dx = v2[0] - v1[0]; - float dy = v2[1] - v1[1]; - float dz = v2[2] - v1[2]; - return rcSqrt(dx*dx + dy*dy + dz*dz); -} - -/// Returns the square of the distance between two points. -/// @param[in] v1 A point. [(x, y, z)] -/// @param[in] v2 A point. [(x, y, z)] -/// @return The square of the distance between the two points. -inline float rcVdistSqr(const float* v1, const float* v2) -{ - float dx = v2[0] - v1[0]; - float dy = v2[1] - v1[1]; - float dz = v2[2] - v1[2]; - return dx*dx + dy*dy + dz*dz; -} - -/// Normalizes the vector. -/// @param[in,out] v The vector to normalize. [(x, y, z)] -inline void rcVnormalize(float* v) -{ - float d = 1.0f / rcSqrt(rcSqr(v[0]) + rcSqr(v[1]) + rcSqr(v[2])); - v[0] *= d; - v[1] *= d; - v[2] *= d; -} - -/// @} -/// @name Heightfield Functions -/// @see rcHeightfield -/// @{ - -/// Calculates the bounding box of an array of vertices. -/// @ingroup recast -/// @param[in] verts An array of vertices. [(x, y, z) * @p nv] -/// @param[in] nv The number of vertices in the @p verts array. -/// @param[out] bmin The minimum bounds of the AABB. [(x, y, z)] [Units: wu] -/// @param[out] bmax The maximum bounds of the AABB. [(x, y, z)] [Units: wu] -void rcCalcBounds(const float* verts, int nv, float* bmin, float* bmax); - -/// Calculates the grid size based on the bounding box and grid cell size. -/// @ingroup recast -/// @param[in] bmin The minimum bounds of the AABB. [(x, y, z)] [Units: wu] -/// @param[in] bmax The maximum bounds of the AABB. [(x, y, z)] [Units: wu] -/// @param[in] cs The xz-plane cell size. [Limit: > 0] [Units: wu] -/// @param[out] w The width along the x-axis. [Limit: >= 0] [Units: vx] -/// @param[out] h The height along the z-axis. [Limit: >= 0] [Units: vx] -void rcCalcGridSize(const float* bmin, const float* bmax, float cs, int* w, int* h); - -/// Initializes a new heightfield. -/// @ingroup recast -/// @param[in,out] ctx The build context to use during the operation. -/// @param[in,out] hf The allocated heightfield to initialize. -/// @param[in] width The width of the field along the x-axis. [Limit: >= 0] [Units: vx] -/// @param[in] height The height of the field along the z-axis. [Limit: >= 0] [Units: vx] -/// @param[in] bmin The minimum bounds of the field's AABB. [(x, y, z)] [Units: wu] -/// @param[in] bmax The maximum bounds of the field's AABB. [(x, y, z)] [Units: wu] -/// @param[in] cs The xz-plane cell size to use for the field. [Limit: > 0] [Units: wu] -/// @param[in] ch The y-axis cell size to use for field. [Limit: > 0] [Units: wu] -bool rcCreateHeightfield(rcContext* ctx, rcHeightfield& hf, int width, int height, - const float* bmin, const float* bmax, - float cs, float ch); - -/// Sets the area id of all triangles with a slope below the specified value -/// to #RC_WALKABLE_AREA. -/// @ingroup recast -/// @param[in,out] ctx The build context to use during the operation. -/// @param[in] walkableSlopeAngle The maximum slope that is considered walkable. -/// [Limits: 0 <= value < 90] [Units: Degrees] -/// @param[in] verts The vertices. [(x, y, z) * @p nv] -/// @param[in] nv The number of vertices. -/// @param[in] tris The triangle vertex indices. [(vertA, vertB, vertC) * @p nt] -/// @param[in] nt The number of triangles. -/// @param[out] areas The triangle area ids. [Length: >= @p nt] -void rcMarkWalkableTriangles(rcContext* ctx, const float walkableSlopeAngle, const float* verts, int nv, - const int* tris, int nt, unsigned char* areas); - -/// Sets the area id of all triangles with a slope greater than or equal to the specified value to #RC_NULL_AREA. -/// @ingroup recast -/// @param[in,out] ctx The build context to use during the operation. -/// @param[in] walkableSlopeAngle The maximum slope that is considered walkable. -/// [Limits: 0 <= value < 90] [Units: Degrees] -/// @param[in] verts The vertices. [(x, y, z) * @p nv] -/// @param[in] nv The number of vertices. -/// @param[in] tris The triangle vertex indices. [(vertA, vertB, vertC) * @p nt] -/// @param[in] nt The number of triangles. -/// @param[out] areas The triangle area ids. [Length: >= @p nt] -void rcClearUnwalkableTriangles(rcContext* ctx, const float walkableSlopeAngle, const float* verts, int nv, - const int* tris, int nt, unsigned char* areas); - -/// Adds a span to the specified heightfield. -/// @ingroup recast -/// @param[in,out] ctx The build context to use during the operation. -/// @param[in,out] hf An initialized heightfield. -/// @param[in] x The width index where the span is to be added. -/// [Limits: 0 <= value < rcHeightfield::width] -/// @param[in] y The height index where the span is to be added. -/// [Limits: 0 <= value < rcHeightfield::height] -/// @param[in] smin The minimum height of the span. [Limit: < @p smax] [Units: vx] -/// @param[in] smax The maximum height of the span. [Limit: <= #RC_SPAN_MAX_HEIGHT] [Units: vx] -/// @param[in] area The area id of the span. [Limit: <= #RC_WALKABLE_AREA) -/// @param[in] flagMergeThr The merge theshold. [Limit: >= 0] [Units: vx] -void rcAddSpan(rcContext* ctx, rcHeightfield& hf, const int x, const int y, - const unsigned short smin, const unsigned short smax, - const unsigned char area, const int flagMergeThr); - -/// Rasterizes a triangle into the specified heightfield. -/// @ingroup recast -/// @param[in,out] ctx The build context to use during the operation. -/// @param[in] v0 Triangle vertex 0 [(x, y, z)] -/// @param[in] v1 Triangle vertex 1 [(x, y, z)] -/// @param[in] v2 Triangle vertex 2 [(x, y, z)] -/// @param[in] area The area id of the triangle. [Limit: <= #RC_WALKABLE_AREA] -/// @param[in,out] solid An initialized heightfield. -/// @param[in] flagMergeThr The distance where the walkable flag is favored over the non-walkable flag. -/// [Limit: >= 0] [Units: vx] -void rcRasterizeTriangle(rcContext* ctx, const float* v0, const float* v1, const float* v2, - const unsigned char area, rcHeightfield& solid, - const int flagMergeThr = 1); - -/// Rasterizes an indexed triangle mesh into the specified heightfield. -/// @ingroup recast -/// @param[in,out] ctx The build context to use during the operation. -/// @param[in] verts The vertices. [(x, y, z) * @p nv] -/// @param[in] nv The number of vertices. -/// @param[in] tris The triangle indices. [(vertA, vertB, vertC) * @p nt] -/// @param[in] areas The area id's of the triangles. [Limit: <= #RC_WALKABLE_AREA] [Size: @p nt] -/// @param[in] nt The number of triangles. -/// @param[in,out] solid An initialized heightfield. -/// @param[in] flagMergeThr The distance where the walkable flag is favored over the non-walkable flag. -/// [Limit: >= 0] [Units: vx] -void rcRasterizeTriangles(rcContext* ctx, const float* verts, const int nv, - const int* tris, const unsigned char* areas, const int nt, - rcHeightfield& solid, const int flagMergeThr = 1); - -/// Rasterizes an indexed triangle mesh into the specified heightfield. -/// @ingroup recast -/// @param[in,out] ctx The build context to use during the operation. -/// @param[in] verts The vertices. [(x, y, z) * @p nv] -/// @param[in] nv The number of vertices. -/// @param[in] tris The triangle indices. [(vertA, vertB, vertC) * @p nt] -/// @param[in] areas The area id's of the triangles. [Limit: <= #RC_WALKABLE_AREA] [Size: @p nt] -/// @param[in] nt The number of triangles. -/// @param[in,out] solid An initialized heightfield. -/// @param[in] flagMergeThr The distance where the walkable flag is favored over the non-walkable flag. -/// [Limit: >= 0] [Units: vx] -void rcRasterizeTriangles(rcContext* ctx, const float* verts, const int nv, - const unsigned short* tris, const unsigned char* areas, const int nt, - rcHeightfield& solid, const int flagMergeThr = 1); - -/// Rasterizes triangles into the specified heightfield. -/// @ingroup recast -/// @param[in,out] ctx The build context to use during the operation. -/// @param[in] verts The triangle vertices. [(ax, ay, az, bx, by, bz, cx, by, cx) * @p nt] -/// @param[in] areas The area id's of the triangles. [Limit: <= #RC_WALKABLE_AREA] [Size: @p nt] -/// @param[in] nt The number of triangles. -/// @param[in,out] solid An initialized heightfield. -/// @param[in] flagMergeThr The distance where the walkable flag is favored over the non-walkable flag. -/// [Limit: >= 0] [Units: vx] -void rcRasterizeTriangles(rcContext* ctx, const float* verts, const unsigned char* areas, const int nt, - rcHeightfield& solid, const int flagMergeThr = 1); - -/// Marks non-walkable spans as walkable if their maximum is within @p walkableClimp of a walkable neihbor. -/// @ingroup recast -/// @param[in,out] ctx The build context to use during the operation. -/// @param[in] walkableClimb Maximum ledge height that is considered to still be traversable. -/// [Limit: >=0] [Units: vx] -/// @param[in,out] solid A fully built heightfield. (All spans have been added.) -void rcFilterLowHangingWalkableObstacles(rcContext* ctx, const int walkableClimb, rcHeightfield& solid); - -/// Marks spans that are ledges as not-walkable. -/// @ingroup recast -/// @param[in,out] ctx The build context to use during the operation. -/// @param[in] walkableHeight Minimum floor to 'ceiling' height that will still allow the floor area to -/// be considered walkable. [Limit: >= 3] [Units: vx] -/// @param[in] walkableClimb Maximum ledge height that is considered to still be traversable. -/// [Limit: >=0] [Units: vx] -/// @param[in,out] solid A fully built heightfield. (All spans have been added.) -void rcFilterLedgeSpans(rcContext* ctx, const int walkableHeight, - const int walkableClimb, rcHeightfield& solid); - -/// Marks walkable spans as not walkable if the clearence above the span is less than the specified height. -/// @ingroup recast -/// @param[in,out] ctx The build context to use during the operation. -/// @param[in] walkableHeight Minimum floor to 'ceiling' height that will still allow the floor area to -/// be considered walkable. [Limit: >= 3] [Units: vx] -/// @param[in,out] solid A fully built heightfield. (All spans have been added.) -void rcFilterWalkableLowHeightSpans(rcContext* ctx, int walkableHeight, rcHeightfield& solid); - -/// Returns the number of spans contained in the specified heightfield. -/// @ingroup recast -/// @param[in,out] ctx The build context to use during the operation. -/// @param[in] hf An initialized heightfield. -/// @returns The number of spans in the heightfield. -int rcGetHeightFieldSpanCount(rcContext* ctx, rcHeightfield& hf); - -/// @} -/// @name Compact Heightfield Functions -/// @see rcCompactHeightfield -/// @{ - -/// Builds a compact heightfield representing open space, from a heightfield representing solid space. -/// @ingroup recast -/// @param[in,out] ctx The build context to use during the operation. -/// @param[in] walkableHeight Minimum floor to 'ceiling' height that will still allow the floor area -/// to be considered walkable. [Limit: >= 3] [Units: vx] -/// @param[in] walkableClimb Maximum ledge height that is considered to still be traversable. -/// [Limit: >=0] [Units: vx] -/// @param[in] hf The heightfield to be compacted. -/// @param[out] chf The resulting compact heightfield. (Must be pre-allocated.) -/// @returns True if the operation completed successfully. -bool rcBuildCompactHeightfield(rcContext* ctx, const int walkableHeight, const int walkableClimb, - rcHeightfield& hf, rcCompactHeightfield& chf); - -/// Erodes the walkable area within the heightfield by the specified radius. -/// @ingroup recast -/// @param[in,out] ctx The build context to use during the operation. -/// @param[in] radius The radius of erosion. [Limits: 0 < value < 255] [Units: vx] -/// @param[in,out] chf The populated compact heightfield to erode. -/// @returns True if the operation completed successfully. -bool rcErodeWalkableArea(rcContext* ctx, int radius, rcCompactHeightfield& chf); - -/// Applies a median filter to walkable area types (based on area id), removing noise. -/// @ingroup recast -/// @param[in,out] ctx The build context to use during the operation. -/// @param[in,out] chf A populated compact heightfield. -/// @returns True if the operation completed successfully. -bool rcMedianFilterWalkableArea(rcContext* ctx, rcCompactHeightfield& chf); - -/// Applies an area id to all spans within the specified bounding box. (AABB) -/// @ingroup recast -/// @param[in,out] ctx The build context to use during the operation. -/// @param[in] bmin The minimum of the bounding box. [(x, y, z)] -/// @param[in] bmax The maximum of the bounding box. [(x, y, z)] -/// @param[in] areaId The area id to apply. [Limit: <= #RC_WALKABLE_AREA] -/// @param[in,out] chf A populated compact heightfield. -void rcMarkBoxArea(rcContext* ctx, const float* bmin, const float* bmax, unsigned char areaId, - rcCompactHeightfield& chf); - -/// Applies the area id to the all spans within the specified convex polygon. -/// @ingroup recast -/// @param[in,out] ctx The build context to use during the operation. -/// @param[in] verts The vertices of the polygon [Fomr: (x, y, z) * @p nverts] -/// @param[in] nverts The number of vertices in the polygon. -/// @param[in] hmin The height of the base of the polygon. -/// @param[in] hmax The height of the top of the polygon. -/// @param[in] areaId The area id to apply. [Limit: <= #RC_WALKABLE_AREA] -/// @param[in,out] chf A populated compact heightfield. -void rcMarkConvexPolyArea(rcContext* ctx, const float* verts, const int nverts, - const float hmin, const float hmax, unsigned char areaId, - rcCompactHeightfield& chf); - -/// Helper function to offset voncex polygons for rcMarkConvexPolyArea. -/// @ingroup recast -/// @param[in] verts The vertices of the polygon [Form: (x, y, z) * @p nverts] -/// @param[in] nverts The number of vertices in the polygon. -/// @param[out] outVerts The offset vertices (should hold up to 2 * @p nverts) [Form: (x, y, z) * return value] -/// @param[in] maxOutVerts The max number of vertices that can be stored to @p outVerts. -/// @returns Number of vertices in the offset polygon or 0 if too few vertices in @p outVerts. -int rcOffsetPoly(const float* verts, const int nverts, const float offset, - float* outVerts, const int maxOutVerts); - -/// Applies the area id to all spans within the specified cylinder. -/// @ingroup recast -/// @param[in,out] ctx The build context to use during the operation. -/// @param[in] pos The center of the base of the cylinder. [Form: (x, y, z)] -/// @param[in] r The radius of the cylinder. -/// @param[in] h The height of the cylinder. -/// @param[in] areaId The area id to apply. [Limit: <= #RC_WALKABLE_AREA] -/// @param[in,out] chf A populated compact heightfield. -void rcMarkCylinderArea(rcContext* ctx, const float* pos, - const float r, const float h, unsigned char areaId, - rcCompactHeightfield& chf); - -/// Builds the distance field for the specified compact heightfield. -/// @ingroup recast -/// @param[in,out] ctx The build context to use during the operation. -/// @param[in,out] chf A populated compact heightfield. -/// @returns True if the operation completed successfully. -bool rcBuildDistanceField(rcContext* ctx, rcCompactHeightfield& chf); - -/// Builds region data for the heightfield using watershed partitioning. -/// @ingroup recast -/// @param[in,out] ctx The build context to use during the operation. -/// @param[in,out] chf A populated compact heightfield. -/// @param[in] borderSize The size of the non-navigable border around the heightfield. -/// [Limit: >=0] [Units: vx] -/// @param[in] minRegionArea The minimum number of cells allowed to form isolated island areas. -/// [Limit: >=0] [Units: vx]. -/// @param[in] mergeRegionArea Any regions with a span count smaller than this value will, if possible, -/// be merged with larger regions. [Limit: >=0] [Units: vx] -/// @returns True if the operation completed successfully. -bool rcBuildRegions(rcContext* ctx, rcCompactHeightfield& chf, - const int borderSize, const int minRegionArea, const int mergeRegionArea); - -/// Builds region data for the heightfield using simple monotone partitioning. -/// @ingroup recast -/// @param[in,out] ctx The build context to use during the operation. -/// @param[in,out] chf A populated compact heightfield. -/// @param[in] borderSize The size of the non-navigable border around the heightfield. -/// [Limit: >=0] [Units: vx] -/// @param[in] minRegionArea The minimum number of cells allowed to form isolated island areas. -/// [Limit: >=0] [Units: vx]. -/// @param[in] mergeRegionArea Any regions with a span count smaller than this value will, if possible, -/// be merged with larger regions. [Limit: >=0] [Units: vx] -/// @returns True if the operation completed successfully. -bool rcBuildRegionsMonotone(rcContext* ctx, rcCompactHeightfield& chf, - const int borderSize, const int minRegionArea, const int mergeRegionArea); - - -/// Sets the neighbor connection data for the specified direction. -/// @param[in] s The span to update. -/// @param[in] dir The direction to set. [Limits: 0 <= value < 4] -/// @param[in] i The index of the neighbor span. -inline void rcSetCon(rcCompactSpan& s, int dir, int i) -{ - const unsigned int shift = (unsigned int)dir*6; - unsigned int con = s.con; - s.con = (con & ~(0x3f << shift)) | (((unsigned int)i & 0x3f) << shift); -} - -/// Gets neighbor connection data for the specified direction. -/// @param[in] s The span to check. -/// @param[in] dir The direction to check. [Limits: 0 <= value < 4] -/// @return The neighbor connection data for the specified direction, -/// or #RC_NOT_CONNECTED if there is no connection. -inline int rcGetCon(const rcCompactSpan& s, int dir) -{ - const unsigned int shift = (unsigned int)dir*6; - return (s.con >> shift) & 0x3f; -} - -/// Gets the standard width (x-axis) offset for the specified direction. -/// @param[in] dir The direction. [Limits: 0 <= value < 4] -/// @return The width offset to apply to the current cell position to move -/// in the direction. -inline int rcGetDirOffsetX(int dir) -{ - const int offset[4] = { -1, 0, 1, 0, }; - return offset[dir&0x03]; -} - -/// Gets the standard height (z-axis) offset for the specified direction. -/// @param[in] dir The direction. [Limits: 0 <= value < 4] -/// @return The height offset to apply to the current cell position to move -/// in the direction. -inline int rcGetDirOffsetY(int dir) -{ - const int offset[4] = { 0, 1, 0, -1 }; - return offset[dir&0x03]; -} - -/// @} -/// @name Layer, Contour, Polymesh, and Detail Mesh Functions -/// @see rcHeightfieldLayer, rcContourSet, rcPolyMesh, rcPolyMeshDetail -/// @{ - -/// Builds a layer set from the specified compact heightfield. -/// @ingroup recast -/// @param[in,out] ctx The build context to use during the operation. -/// @param[in] chf A fully built compact heightfield. -/// @param[in] borderSize The size of the non-navigable border around the heightfield. [Limit: >=0] -/// [Units: vx] -/// @param[in] walkableHeight Minimum floor to 'ceiling' height that will still allow the floor area -/// to be considered walkable. [Limit: >= 3] [Units: vx] -/// @param[out] lset The resulting layer set. (Must be pre-allocated.) -/// @returns True if the operation completed successfully. -bool rcBuildHeightfieldLayers(rcContext* ctx, rcCompactHeightfield& chf, - const int borderSize, const int walkableHeight, - rcHeightfieldLayerSet& lset); - -/// Builds a contour set from the region outlines in the provided compact heightfield. -/// @ingroup recast -/// @param[in,out] ctx The build context to use during the operation. -/// @param[in] chf A fully built compact heightfield. -/// @param[in] maxError The maximum distance a simplfied contour's border edges should deviate -/// the original raw contour. [Limit: >=0] [Units: wu] -/// @param[in] maxEdgeLen The maximum allowed length for contour edges along the border of the mesh. -/// [Limit: >=0] [Units: vx] -/// @param[out] cset The resulting contour set. (Must be pre-allocated.) -/// @param[in] buildFlags The build flags. (See: #rcBuildContoursFlags) -/// @returns True if the operation completed successfully. -bool rcBuildContours(rcContext* ctx, rcCompactHeightfield& chf, - const float maxError, const int maxEdgeLen, - rcContourSet& cset, const int flags = RC_CONTOUR_TESS_WALL_EDGES); - -/// Builds a polygon mesh from the provided contours. -/// @ingroup recast -/// @param[in,out] ctx The build context to use during the operation. -/// @param[in] cset A fully built contour set. -/// @param[in] nvp The maximum number of vertices allowed for polygons generated during the -/// contour to polygon conversion process. [Limit: >= 3] -/// @param[out] mesh The resulting polygon mesh. (Must be re-allocated.) -/// @returns True if the operation completed successfully. -bool rcBuildPolyMesh(rcContext* ctx, rcContourSet& cset, const int nvp, rcPolyMesh& mesh); - -/// Merges multiple polygon meshes into a single mesh. -/// @ingroup recast -/// @param[in,out] ctx The build context to use during the operation. -/// @param[in] meshes An array of polygon meshes to merge. [Size: @p nmeshes] -/// @param[in] nmeshes The number of polygon meshes in the meshes array. -/// @param[in] mesh The resulting polygon mesh. (Must be pre-allocated.) -/// @returns True if the operation completed successfully. -bool rcMergePolyMeshes(rcContext* ctx, rcPolyMesh** meshes, const int nmeshes, rcPolyMesh& mesh); - -/// Builds a detail mesh from the provided polygon mesh. -/// @ingroup recast -/// @param[in,out] ctx The build context to use during the operation. -/// @param[in] mesh A fully built polygon mesh. -/// @param[in] chf The compact heightfield used to build the polygon mesh. -/// @param[in] sampleDist Sets the distance to use when samping the heightfield. [Limit: >=0] [Units: wu] -/// @param[in] sampleMaxError The maximum distance the detail mesh surface should deviate from -/// heightfield data. [Limit: >=0] [Units: wu] -/// @param[out] dmesh The resulting detail mesh. (Must be pre-allocated.) -/// @returns True if the operation completed successfully. -bool rcBuildPolyMeshDetail(rcContext* ctx, const rcPolyMesh& mesh, const rcCompactHeightfield& chf, - const float sampleDist, const float sampleMaxError, - rcPolyMeshDetail& dmesh); - -/// Copies the poly mesh data from src to dst. -/// @ingroup recast -/// @param[in,out] ctx The build context to use during the operation. -/// @param[in] src The source mesh to copy from. -/// @param[out] dst The resulting detail mesh. (Must be pre-allocated, must be empty mesh.) -/// @returns True if the operation completed successfully. -bool rcCopyPolyMesh(rcContext* ctx, const rcPolyMesh& src, rcPolyMesh& dst); - -/// Merges multiple detail meshes into a single detail mesh. -/// @ingroup recast -/// @param[in,out] ctx The build context to use during the operation. -/// @param[in] meshes An array of detail meshes to merge. [Size: @p nmeshes] -/// @param[in] nmeshes The number of detail meshes in the meshes array. -/// @param[out] mesh The resulting detail mesh. (Must be pre-allocated.) -/// @returns True if the operation completed successfully. -bool rcMergePolyMeshDetails(rcContext* ctx, rcPolyMeshDetail** meshes, const int nmeshes, rcPolyMeshDetail& mesh); - -/// @} - -#endif // RECAST_H - -/////////////////////////////////////////////////////////////////////////// - -// Due to the large amount of detail documentation for this file, -// the content normally located at the end of the header file has been separated -// out to a file in /Docs/Extern. diff --git a/KREngine/3rdparty/recast/include/RecastAlloc.h b/KREngine/3rdparty/recast/include/RecastAlloc.h deleted file mode 100755 index 438be9e..0000000 --- a/KREngine/3rdparty/recast/include/RecastAlloc.h +++ /dev/null @@ -1,124 +0,0 @@ -// -// Copyright (c) 2009-2010 Mikko Mononen memon@inside.org -// -// This software is provided 'as-is', without any express or implied -// warranty. In no event will the authors be held liable for any damages -// arising from the use of this software. -// Permission is granted to anyone to use this software for any purpose, -// including commercial applications, and to alter it and redistribute it -// freely, subject to the following restrictions: -// 1. The origin of this software must not be misrepresented; you must not -// claim that you wrote the original software. If you use this software -// in a product, an acknowledgment in the product documentation would be -// appreciated but is not required. -// 2. Altered source versions must be plainly marked as such, and must not be -// misrepresented as being the original software. -// 3. This notice may not be removed or altered from any source distribution. -// - -#ifndef RECASTALLOC_H -#define RECASTALLOC_H - -/// Provides hint values to the memory allocator on how long the -/// memory is expected to be used. -enum rcAllocHint -{ - RC_ALLOC_PERM, ///< Memory will persist after a function call. - RC_ALLOC_TEMP ///< Memory used temporarily within a function. -}; - -/// A memory allocation function. -// @param[in] size The size, in bytes of memory, to allocate. -// @param[in] rcAllocHint A hint to the allocator on how long the memory is expected to be in use. -// @return A pointer to the beginning of the allocated memory block, or null if the allocation failed. -/// @see rcAllocSetCustom -typedef void* (rcAllocFunc)(int size, rcAllocHint hint); - -/// A memory deallocation function. -/// @param[in] ptr A pointer to a memory block previously allocated using #rcAllocFunc. -/// @see rcAllocSetCustom -typedef void (rcFreeFunc)(void* ptr); - -/// Sets the base custom allocation functions to be used by Recast. -/// @param[in] allocFunc The memory allocation function to be used by #rcAlloc -/// @param[in] freeFunc The memory de-allocation function to be used by #rcFree -void rcAllocSetCustom(rcAllocFunc *allocFunc, rcFreeFunc *freeFunc); - -/// Allocates a memory block. -/// @param[in] size The size, in bytes of memory, to allocate. -/// @param[in] hint A hint to the allocator on how long the memory is expected to be in use. -/// @return A pointer to the beginning of the allocated memory block, or null if the allocation failed. -/// @see rcFree -void* rcAlloc(int size, rcAllocHint hint); - -/// Deallocates a memory block. -/// @param[in] ptr A pointer to a memory block previously allocated using #rcAlloc. -/// @see rcAlloc -void rcFree(void* ptr); - - -/// A simple dynamic array of integers. -class rcIntArray -{ - int* m_data; - int m_size, m_cap; - inline rcIntArray(const rcIntArray&); - inline rcIntArray& operator=(const rcIntArray&); -public: - - /// Constructs an instance with an initial array size of zero. - inline rcIntArray() : m_data(0), m_size(0), m_cap(0) {} - - /// Constructs an instance initialized to the specified size. - /// @param[in] n The initial size of the integer array. - inline rcIntArray(int n) : m_data(0), m_size(0), m_cap(0) { resize(n); } - inline ~rcIntArray() { rcFree(m_data); } - - /// Specifies the new size of the integer array. - /// @param[in] n The new size of the integer array. - void resize(int n); - - /// Push the specified integer onto the end of the array and increases the size by one. - /// @param[in] item The new value. - inline void push(int item) { resize(m_size+1); m_data[m_size-1] = item; } - - /// Returns the value at the end of the array and reduces the size by one. - /// @return The value at the end of the array. - inline int pop() { if (m_size > 0) m_size--; return m_data[m_size]; } - - /// The value at the specified array index. - /// @warning Does not provide overflow protection. - /// @param[in] i The index of the value. - inline const int& operator[](int i) const { return m_data[i]; } - - /// The value at the specified array index. - /// @warning Does not provide overflow protection. - /// @param[in] i The index of the value. - inline int& operator[](int i) { return m_data[i]; } - - /// The current size of the integer array. - inline int size() const { return m_size; } -}; - -/// A simple helper class used to delete an array when it goes out of scope. -/// @note This class is rarely if ever used by the end user. -template class rcScopedDelete -{ - T* ptr; - inline T* operator=(T* p); -public: - - /// Constructs an instance with a null pointer. - inline rcScopedDelete() : ptr(0) {} - - /// Constructs an instance with the specified pointer. - /// @param[in] p An pointer to an allocated array. - inline rcScopedDelete(T* p) : ptr(p) {} - inline ~rcScopedDelete() { rcFree(ptr); } - - /// The root array pointer. - /// @return The root array pointer. - inline operator T*() { return ptr; } -}; - -#endif diff --git a/KREngine/3rdparty/recast/include/RecastAssert.h b/KREngine/3rdparty/recast/include/RecastAssert.h deleted file mode 100755 index 2aca0d9..0000000 --- a/KREngine/3rdparty/recast/include/RecastAssert.h +++ /dev/null @@ -1,33 +0,0 @@ -// -// Copyright (c) 2009-2010 Mikko Mononen memon@inside.org -// -// This software is provided 'as-is', without any express or implied -// warranty. In no event will the authors be held liable for any damages -// arising from the use of this software. -// Permission is granted to anyone to use this software for any purpose, -// including commercial applications, and to alter it and redistribute it -// freely, subject to the following restrictions: -// 1. The origin of this software must not be misrepresented; you must not -// claim that you wrote the original software. If you use this software -// in a product, an acknowledgment in the product documentation would be -// appreciated but is not required. -// 2. Altered source versions must be plainly marked as such, and must not be -// misrepresented as being the original software. -// 3. This notice may not be removed or altered from any source distribution. -// - -#ifndef RECASTASSERT_H -#define RECASTASSERT_H - -// Note: This header file's only purpose is to include define assert. -// Feel free to change the file and include your own implementation instead. - -#ifdef NDEBUG -// From http://cnicholson.net/2009/02/stupid-c-tricks-adventures-in-assert/ -# define rcAssert(x) do { (void)sizeof(x); } while((void)(__LINE__==-1),false) -#else -# include -# define rcAssert assert -#endif - -#endif // RECASTASSERT_H diff --git a/KREngine/3rdparty/recast/source/Recast.cpp b/KREngine/3rdparty/recast/source/Recast.cpp deleted file mode 100755 index b9d8603..0000000 --- a/KREngine/3rdparty/recast/source/Recast.cpp +++ /dev/null @@ -1,489 +0,0 @@ -// -// Copyright (c) 2009-2010 Mikko Mononen memon@inside.org -// -// This software is provided 'as-is', without any express or implied -// warranty. In no event will the authors be held liable for any damages -// arising from the use of this software. -// Permission is granted to anyone to use this software for any purpose, -// including commercial applications, and to alter it and redistribute it -// freely, subject to the following restrictions: -// 1. The origin of this software must not be misrepresented; you must not -// claim that you wrote the original software. If you use this software -// in a product, an acknowledgment in the product documentation would be -// appreciated but is not required. -// 2. Altered source versions must be plainly marked as such, and must not be -// misrepresented as being the original software. -// 3. This notice may not be removed or altered from any source distribution. -// - -#include -#define _USE_MATH_DEFINES -#include -#include -#include -#include -#include -#include "Recast.h" -#include "RecastAlloc.h" -#include "RecastAssert.h" - -float rcSqrt(float x) -{ - return sqrtf(x); -} - -/// @class rcContext -/// @par -/// -/// This class does not provide logging or timer functionality on its -/// own. Both must be provided by a concrete implementation -/// by overriding the protected member functions. Also, this class does not -/// provide an interface for extracting log messages. (Only adding them.) -/// So concrete implementations must provide one. -/// -/// If no logging or timers are required, just pass an instance of this -/// class through the Recast build process. -/// - -/// @par -/// -/// Example: -/// @code -/// // Where ctx is an instance of rcContext and filepath is a char array. -/// ctx->log(RC_LOG_ERROR, "buildTiledNavigation: Could not load '%s'", filepath); -/// @endcode -void rcContext::log(const rcLogCategory category, const char* format, ...) -{ - if (!m_logEnabled) - return; - static const int MSG_SIZE = 512; - char msg[MSG_SIZE]; - va_list ap; - va_start(ap, format); - int len = vsnprintf(msg, MSG_SIZE, format, ap); - if (len >= MSG_SIZE) - { - len = MSG_SIZE-1; - msg[MSG_SIZE-1] = '\0'; - } - va_end(ap); - doLog(category, msg, len); -} - -rcHeightfield* rcAllocHeightfield() -{ - rcHeightfield* hf = (rcHeightfield*)rcAlloc(sizeof(rcHeightfield), RC_ALLOC_PERM); - memset(hf, 0, sizeof(rcHeightfield)); - return hf; -} - -void rcFreeHeightField(rcHeightfield* hf) -{ - if (!hf) return; - // Delete span array. - rcFree(hf->spans); - // Delete span pools. - while (hf->pools) - { - rcSpanPool* next = hf->pools->next; - rcFree(hf->pools); - hf->pools = next; - } - rcFree(hf); -} - -rcCompactHeightfield* rcAllocCompactHeightfield() -{ - rcCompactHeightfield* chf = (rcCompactHeightfield*)rcAlloc(sizeof(rcCompactHeightfield), RC_ALLOC_PERM); - memset(chf, 0, sizeof(rcCompactHeightfield)); - return chf; -} - -void rcFreeCompactHeightfield(rcCompactHeightfield* chf) -{ - if (!chf) return; - rcFree(chf->cells); - rcFree(chf->spans); - rcFree(chf->dist); - rcFree(chf->areas); - rcFree(chf); -} - - -rcHeightfieldLayerSet* rcAllocHeightfieldLayerSet() -{ - rcHeightfieldLayerSet* lset = (rcHeightfieldLayerSet*)rcAlloc(sizeof(rcHeightfieldLayerSet), RC_ALLOC_PERM); - memset(lset, 0, sizeof(rcHeightfieldLayerSet)); - return lset; -} - -void rcFreeHeightfieldLayerSet(rcHeightfieldLayerSet* lset) -{ - if (!lset) return; - for (int i = 0; i < lset->nlayers; ++i) - { - rcFree(lset->layers[i].heights); - rcFree(lset->layers[i].areas); - rcFree(lset->layers[i].cons); - } - rcFree(lset->layers); - rcFree(lset); -} - - -rcContourSet* rcAllocContourSet() -{ - rcContourSet* cset = (rcContourSet*)rcAlloc(sizeof(rcContourSet), RC_ALLOC_PERM); - memset(cset, 0, sizeof(rcContourSet)); - return cset; -} - -void rcFreeContourSet(rcContourSet* cset) -{ - if (!cset) return; - for (int i = 0; i < cset->nconts; ++i) - { - rcFree(cset->conts[i].verts); - rcFree(cset->conts[i].rverts); - } - rcFree(cset->conts); - rcFree(cset); -} - -rcPolyMesh* rcAllocPolyMesh() -{ - rcPolyMesh* pmesh = (rcPolyMesh*)rcAlloc(sizeof(rcPolyMesh), RC_ALLOC_PERM); - memset(pmesh, 0, sizeof(rcPolyMesh)); - return pmesh; -} - -void rcFreePolyMesh(rcPolyMesh* pmesh) -{ - if (!pmesh) return; - rcFree(pmesh->verts); - rcFree(pmesh->polys); - rcFree(pmesh->regs); - rcFree(pmesh->flags); - rcFree(pmesh->areas); - rcFree(pmesh); -} - -rcPolyMeshDetail* rcAllocPolyMeshDetail() -{ - rcPolyMeshDetail* dmesh = (rcPolyMeshDetail*)rcAlloc(sizeof(rcPolyMeshDetail), RC_ALLOC_PERM); - memset(dmesh, 0, sizeof(rcPolyMeshDetail)); - return dmesh; -} - -void rcFreePolyMeshDetail(rcPolyMeshDetail* dmesh) -{ - if (!dmesh) return; - rcFree(dmesh->meshes); - rcFree(dmesh->verts); - rcFree(dmesh->tris); - rcFree(dmesh); -} - -void rcCalcBounds(const float* verts, int nv, float* bmin, float* bmax) -{ - // Calculate bounding box. - rcVcopy(bmin, verts); - rcVcopy(bmax, verts); - for (int i = 1; i < nv; ++i) - { - const float* v = &verts[i*3]; - rcVmin(bmin, v); - rcVmax(bmax, v); - } -} - -void rcCalcGridSize(const float* bmin, const float* bmax, float cs, int* w, int* h) -{ - *w = (int)((bmax[0] - bmin[0])/cs+0.5f); - *h = (int)((bmax[2] - bmin[2])/cs+0.5f); -} - -/// @par -/// -/// See the #rcConfig documentation for more information on the configuration parameters. -/// -/// @see rcAllocHeightfield, rcHeightfield -bool rcCreateHeightfield(rcContext* ctx, rcHeightfield& hf, int width, int height, - const float* bmin, const float* bmax, - float cs, float ch) -{ - rcIgnoreUnused(ctx); - - hf.width = width; - hf.height = height; - rcVcopy(hf.bmin, bmin); - rcVcopy(hf.bmax, bmax); - hf.cs = cs; - hf.ch = ch; - hf.spans = (rcSpan**)rcAlloc(sizeof(rcSpan*)*hf.width*hf.height, RC_ALLOC_PERM); - if (!hf.spans) - return false; - memset(hf.spans, 0, sizeof(rcSpan*)*hf.width*hf.height); - return true; -} - -static void calcTriNormal(const float* v0, const float* v1, const float* v2, float* norm) -{ - float e0[3], e1[3]; - rcVsub(e0, v1, v0); - rcVsub(e1, v2, v0); - rcVcross(norm, e0, e1); - rcVnormalize(norm); -} - -/// @par -/// -/// Only sets the aread id's for the walkable triangles. Does not alter the -/// area id's for unwalkable triangles. -/// -/// See the #rcConfig documentation for more information on the configuration parameters. -/// -/// @see rcHeightfield, rcClearUnwalkableTriangles, rcRasterizeTriangles -void rcMarkWalkableTriangles(rcContext* ctx, const float walkableSlopeAngle, - const float* verts, int /*nv*/, - const int* tris, int nt, - unsigned char* areas) -{ - rcIgnoreUnused(ctx); - - const float walkableThr = cosf(walkableSlopeAngle/180.0f*RC_PI); - - float norm[3]; - - for (int i = 0; i < nt; ++i) - { - const int* tri = &tris[i*3]; - calcTriNormal(&verts[tri[0]*3], &verts[tri[1]*3], &verts[tri[2]*3], norm); - // Check if the face is walkable. - if (norm[1] > walkableThr) - areas[i] = RC_WALKABLE_AREA; - } -} - -/// @par -/// -/// Only sets the aread id's for the unwalkable triangles. Does not alter the -/// area id's for walkable triangles. -/// -/// See the #rcConfig documentation for more information on the configuration parameters. -/// -/// @see rcHeightfield, rcClearUnwalkableTriangles, rcRasterizeTriangles -void rcClearUnwalkableTriangles(rcContext* ctx, const float walkableSlopeAngle, - const float* verts, int /*nv*/, - const int* tris, int nt, - unsigned char* areas) -{ - rcIgnoreUnused(ctx); - - const float walkableThr = cosf(walkableSlopeAngle/180.0f*RC_PI); - - float norm[3]; - - for (int i = 0; i < nt; ++i) - { - const int* tri = &tris[i*3]; - calcTriNormal(&verts[tri[0]*3], &verts[tri[1]*3], &verts[tri[2]*3], norm); - // Check if the face is walkable. - if (norm[1] <= walkableThr) - areas[i] = RC_NULL_AREA; - } -} - -int rcGetHeightFieldSpanCount(rcContext* ctx, rcHeightfield& hf) -{ - rcIgnoreUnused(ctx); - - const int w = hf.width; - const int h = hf.height; - int spanCount = 0; - for (int y = 0; y < h; ++y) - { - for (int x = 0; x < w; ++x) - { - for (rcSpan* s = hf.spans[x + y*w]; s; s = s->next) - { - if (s->area != RC_NULL_AREA) - spanCount++; - } - } - } - return spanCount; -} - -/// @par -/// -/// This is just the beginning of the process of fully building a compact heightfield. -/// Various filters may be applied applied, then the distance field and regions built. -/// E.g: #rcBuildDistanceField and #rcBuildRegions -/// -/// See the #rcConfig documentation for more information on the configuration parameters. -/// -/// @see rcAllocCompactHeightfield, rcHeightfield, rcCompactHeightfield, rcConfig -bool rcBuildCompactHeightfield(rcContext* ctx, const int walkableHeight, const int walkableClimb, - rcHeightfield& hf, rcCompactHeightfield& chf) -{ - rcAssert(ctx); - - ctx->startTimer(RC_TIMER_BUILD_COMPACTHEIGHTFIELD); - - const int w = hf.width; - const int h = hf.height; - const int spanCount = rcGetHeightFieldSpanCount(ctx, hf); - - // Fill in header. - chf.width = w; - chf.height = h; - chf.spanCount = spanCount; - chf.walkableHeight = walkableHeight; - chf.walkableClimb = walkableClimb; - chf.maxRegions = 0; - rcVcopy(chf.bmin, hf.bmin); - rcVcopy(chf.bmax, hf.bmax); - chf.bmax[1] += walkableHeight*hf.ch; - chf.cs = hf.cs; - chf.ch = hf.ch; - chf.cells = (rcCompactCell*)rcAlloc(sizeof(rcCompactCell)*w*h, RC_ALLOC_PERM); - if (!chf.cells) - { - ctx->log(RC_LOG_ERROR, "rcBuildCompactHeightfield: Out of memory 'chf.cells' (%d)", w*h); - return false; - } - memset(chf.cells, 0, sizeof(rcCompactCell)*w*h); - chf.spans = (rcCompactSpan*)rcAlloc(sizeof(rcCompactSpan)*spanCount, RC_ALLOC_PERM); - if (!chf.spans) - { - ctx->log(RC_LOG_ERROR, "rcBuildCompactHeightfield: Out of memory 'chf.spans' (%d)", spanCount); - return false; - } - memset(chf.spans, 0, sizeof(rcCompactSpan)*spanCount); - chf.areas = (unsigned char*)rcAlloc(sizeof(unsigned char)*spanCount, RC_ALLOC_PERM); - if (!chf.areas) - { - ctx->log(RC_LOG_ERROR, "rcBuildCompactHeightfield: Out of memory 'chf.areas' (%d)", spanCount); - return false; - } - memset(chf.areas, RC_NULL_AREA, sizeof(unsigned char)*spanCount); - - const int MAX_HEIGHT = 0xffff; - - // Fill in cells and spans. - int idx = 0; - for (int y = 0; y < h; ++y) - { - for (int x = 0; x < w; ++x) - { - const rcSpan* s = hf.spans[x + y*w]; - // If there are no spans at this cell, just leave the data to index=0, count=0. - if (!s) continue; - rcCompactCell& c = chf.cells[x+y*w]; - c.index = idx; - c.count = 0; - while (s) - { - if (s->area != RC_NULL_AREA) - { - const int bot = (int)s->smax; - const int top = s->next ? (int)s->next->smin : MAX_HEIGHT; - chf.spans[idx].y = (unsigned short)rcClamp(bot, 0, 0xffff); - chf.spans[idx].h = (unsigned char)rcClamp(top - bot, 0, 0xff); - chf.areas[idx] = s->area; - idx++; - c.count++; - } - s = s->next; - } - } - } - - // Find neighbour connections. - const int MAX_LAYERS = RC_NOT_CONNECTED-1; - int tooHighNeighbour = 0; - for (int y = 0; y < h; ++y) - { - for (int x = 0; x < w; ++x) - { - const rcCompactCell& c = chf.cells[x+y*w]; - for (int i = (int)c.index, ni = (int)(c.index+c.count); i < ni; ++i) - { - rcCompactSpan& s = chf.spans[i]; - - for (int dir = 0; dir < 4; ++dir) - { - rcSetCon(s, dir, RC_NOT_CONNECTED); - const int nx = x + rcGetDirOffsetX(dir); - const int ny = y + rcGetDirOffsetY(dir); - // First check that the neighbour cell is in bounds. - if (nx < 0 || ny < 0 || nx >= w || ny >= h) - continue; - - // Iterate over all neighbour spans and check if any of the is - // accessible from current cell. - const rcCompactCell& nc = chf.cells[nx+ny*w]; - for (int k = (int)nc.index, nk = (int)(nc.index+nc.count); k < nk; ++k) - { - const rcCompactSpan& ns = chf.spans[k]; - const int bot = rcMax(s.y, ns.y); - const int top = rcMin(s.y+s.h, ns.y+ns.h); - - // Check that the gap between the spans is walkable, - // and that the climb height between the gaps is not too high. - if ((top - bot) >= walkableHeight && rcAbs((int)ns.y - (int)s.y) <= walkableClimb) - { - // Mark direction as walkable. - const int lidx = k - (int)nc.index; - if (lidx < 0 || lidx > MAX_LAYERS) - { - tooHighNeighbour = rcMax(tooHighNeighbour, lidx); - continue; - } - rcSetCon(s, dir, lidx); - break; - } - } - - } - } - } - } - - if (tooHighNeighbour > MAX_LAYERS) - { - ctx->log(RC_LOG_ERROR, "rcBuildCompactHeightfield: Heightfield has too many layers %d (max: %d)", - tooHighNeighbour, MAX_LAYERS); - } - - ctx->stopTimer(RC_TIMER_BUILD_COMPACTHEIGHTFIELD); - - return true; -} - -/* -static int getHeightfieldMemoryUsage(const rcHeightfield& hf) -{ - int size = 0; - size += sizeof(hf); - size += hf.width * hf.height * sizeof(rcSpan*); - - rcSpanPool* pool = hf.pools; - while (pool) - { - size += (sizeof(rcSpanPool) - sizeof(rcSpan)) + sizeof(rcSpan)*RC_SPANS_PER_POOL; - pool = pool->next; - } - return size; -} - -static int getCompactHeightFieldMemoryusage(const rcCompactHeightfield& chf) -{ - int size = 0; - size += sizeof(rcCompactHeightfield); - size += sizeof(rcCompactSpan) * chf.spanCount; - size += sizeof(rcCompactCell) * chf.width * chf.height; - return size; -} -*/ \ No newline at end of file diff --git a/KREngine/3rdparty/recast/source/RecastAlloc.cpp b/KREngine/3rdparty/recast/source/RecastAlloc.cpp deleted file mode 100755 index b5ec151..0000000 --- a/KREngine/3rdparty/recast/source/RecastAlloc.cpp +++ /dev/null @@ -1,88 +0,0 @@ -// -// Copyright (c) 2009-2010 Mikko Mononen memon@inside.org -// -// This software is provided 'as-is', without any express or implied -// warranty. In no event will the authors be held liable for any damages -// arising from the use of this software. -// Permission is granted to anyone to use this software for any purpose, -// including commercial applications, and to alter it and redistribute it -// freely, subject to the following restrictions: -// 1. The origin of this software must not be misrepresented; you must not -// claim that you wrote the original software. If you use this software -// in a product, an acknowledgment in the product documentation would be -// appreciated but is not required. -// 2. Altered source versions must be plainly marked as such, and must not be -// misrepresented as being the original software. -// 3. This notice may not be removed or altered from any source distribution. -// - -#include -#include -#include "RecastAlloc.h" - -static void *rcAllocDefault(int size, rcAllocHint) -{ - return malloc(size); -} - -static void rcFreeDefault(void *ptr) -{ - free(ptr); -} - -static rcAllocFunc* sRecastAllocFunc = rcAllocDefault; -static rcFreeFunc* sRecastFreeFunc = rcFreeDefault; - -/// @see rcAlloc, rcFree -void rcAllocSetCustom(rcAllocFunc *allocFunc, rcFreeFunc *freeFunc) -{ - sRecastAllocFunc = allocFunc ? allocFunc : rcAllocDefault; - sRecastFreeFunc = freeFunc ? freeFunc : rcFreeDefault; -} - -/// @see rcAllocSetCustom -void* rcAlloc(int size, rcAllocHint hint) -{ - return sRecastAllocFunc(size, hint); -} - -/// @par -/// -/// @warning This function leaves the value of @p ptr unchanged. So it still -/// points to the same (now invalid) location, and not to null. -/// -/// @see rcAllocSetCustom -void rcFree(void* ptr) -{ - if (ptr) - sRecastFreeFunc(ptr); -} - -/// @class rcIntArray -/// -/// While it is possible to pre-allocate a specific array size during -/// construction or by using the #resize method, certain methods will -/// automatically resize the array as needed. -/// -/// @warning The array memory is not initialized to zero when the size is -/// manually set during construction or when using #resize. - -/// @par -/// -/// Using this method ensures the array is at least large enough to hold -/// the specified number of elements. This can improve performance by -/// avoiding auto-resizing during use. -void rcIntArray::resize(int n) -{ - if (n > m_cap) - { - if (!m_cap) m_cap = n; - while (m_cap < n) m_cap *= 2; - int* newData = (int*)rcAlloc(m_cap*sizeof(int), RC_ALLOC_TEMP); - if (m_size && newData) memcpy(newData, m_data, m_size*sizeof(int)); - rcFree(m_data); - m_data = newData; - } - m_size = n; -} - diff --git a/KREngine/3rdparty/recast/source/RecastArea.cpp b/KREngine/3rdparty/recast/source/RecastArea.cpp deleted file mode 100755 index 1a338cd..0000000 --- a/KREngine/3rdparty/recast/source/RecastArea.cpp +++ /dev/null @@ -1,602 +0,0 @@ -// -// Copyright (c) 2009-2010 Mikko Mononen memon@inside.org -// -// This software is provided 'as-is', without any express or implied -// warranty. In no event will the authors be held liable for any damages -// arising from the use of this software. -// Permission is granted to anyone to use this software for any purpose, -// including commercial applications, and to alter it and redistribute it -// freely, subject to the following restrictions: -// 1. The origin of this software must not be misrepresented; you must not -// claim that you wrote the original software. If you use this software -// in a product, an acknowledgment in the product documentation would be -// appreciated but is not required. -// 2. Altered source versions must be plainly marked as such, and must not be -// misrepresented as being the original software. -// 3. This notice may not be removed or altered from any source distribution. -// - -#include -#define _USE_MATH_DEFINES -#include -#include -#include -#include -#include "Recast.h" -#include "RecastAlloc.h" -#include "RecastAssert.h" - -/// @par -/// -/// Basically, any spans that are closer to a boundary or obstruction than the specified radius -/// are marked as unwalkable. -/// -/// This method is usually called immediately after the heightfield has been built. -/// -/// @see rcCompactHeightfield, rcBuildCompactHeightfield, rcConfig::walkableRadius -bool rcErodeWalkableArea(rcContext* ctx, int radius, rcCompactHeightfield& chf) -{ - rcAssert(ctx); - - const int w = chf.width; - const int h = chf.height; - - ctx->startTimer(RC_TIMER_ERODE_AREA); - - unsigned char* dist = (unsigned char*)rcAlloc(sizeof(unsigned char)*chf.spanCount, RC_ALLOC_TEMP); - if (!dist) - { - ctx->log(RC_LOG_ERROR, "erodeWalkableArea: Out of memory 'dist' (%d).", chf.spanCount); - return false; - } - - // Init distance. - memset(dist, 0xff, sizeof(unsigned char)*chf.spanCount); - - // Mark boundary cells. - for (int y = 0; y < h; ++y) - { - for (int x = 0; x < w; ++x) - { - const rcCompactCell& c = chf.cells[x+y*w]; - for (int i = (int)c.index, ni = (int)(c.index+c.count); i < ni; ++i) - { - if (chf.areas[i] == RC_NULL_AREA) - { - dist[i] = 0; - } - else - { - const rcCompactSpan& s = chf.spans[i]; - int nc = 0; - for (int dir = 0; dir < 4; ++dir) - { - if (rcGetCon(s, dir) != RC_NOT_CONNECTED) - { - const int nx = x + rcGetDirOffsetX(dir); - const int ny = y + rcGetDirOffsetY(dir); - const int nidx = (int)chf.cells[nx+ny*w].index + rcGetCon(s, dir); - if (chf.areas[nidx] != RC_NULL_AREA) - { - nc++; - } - } - } - // At least one missing neighbour. - if (nc != 4) - dist[i] = 0; - } - } - } - } - - unsigned char nd; - - // Pass 1 - for (int y = 0; y < h; ++y) - { - for (int x = 0; x < w; ++x) - { - const rcCompactCell& c = chf.cells[x+y*w]; - for (int i = (int)c.index, ni = (int)(c.index+c.count); i < ni; ++i) - { - const rcCompactSpan& s = chf.spans[i]; - - if (rcGetCon(s, 0) != RC_NOT_CONNECTED) - { - // (-1,0) - const int ax = x + rcGetDirOffsetX(0); - const int ay = y + rcGetDirOffsetY(0); - const int ai = (int)chf.cells[ax+ay*w].index + rcGetCon(s, 0); - const rcCompactSpan& as = chf.spans[ai]; - nd = (unsigned char)rcMin((int)dist[ai]+2, 255); - if (nd < dist[i]) - dist[i] = nd; - - // (-1,-1) - if (rcGetCon(as, 3) != RC_NOT_CONNECTED) - { - const int aax = ax + rcGetDirOffsetX(3); - const int aay = ay + rcGetDirOffsetY(3); - const int aai = (int)chf.cells[aax+aay*w].index + rcGetCon(as, 3); - nd = (unsigned char)rcMin((int)dist[aai]+3, 255); - if (nd < dist[i]) - dist[i] = nd; - } - } - if (rcGetCon(s, 3) != RC_NOT_CONNECTED) - { - // (0,-1) - const int ax = x + rcGetDirOffsetX(3); - const int ay = y + rcGetDirOffsetY(3); - const int ai = (int)chf.cells[ax+ay*w].index + rcGetCon(s, 3); - const rcCompactSpan& as = chf.spans[ai]; - nd = (unsigned char)rcMin((int)dist[ai]+2, 255); - if (nd < dist[i]) - dist[i] = nd; - - // (1,-1) - if (rcGetCon(as, 2) != RC_NOT_CONNECTED) - { - const int aax = ax + rcGetDirOffsetX(2); - const int aay = ay + rcGetDirOffsetY(2); - const int aai = (int)chf.cells[aax+aay*w].index + rcGetCon(as, 2); - nd = (unsigned char)rcMin((int)dist[aai]+3, 255); - if (nd < dist[i]) - dist[i] = nd; - } - } - } - } - } - - // Pass 2 - for (int y = h-1; y >= 0; --y) - { - for (int x = w-1; x >= 0; --x) - { - const rcCompactCell& c = chf.cells[x+y*w]; - for (int i = (int)c.index, ni = (int)(c.index+c.count); i < ni; ++i) - { - const rcCompactSpan& s = chf.spans[i]; - - if (rcGetCon(s, 2) != RC_NOT_CONNECTED) - { - // (1,0) - const int ax = x + rcGetDirOffsetX(2); - const int ay = y + rcGetDirOffsetY(2); - const int ai = (int)chf.cells[ax+ay*w].index + rcGetCon(s, 2); - const rcCompactSpan& as = chf.spans[ai]; - nd = (unsigned char)rcMin((int)dist[ai]+2, 255); - if (nd < dist[i]) - dist[i] = nd; - - // (1,1) - if (rcGetCon(as, 1) != RC_NOT_CONNECTED) - { - const int aax = ax + rcGetDirOffsetX(1); - const int aay = ay + rcGetDirOffsetY(1); - const int aai = (int)chf.cells[aax+aay*w].index + rcGetCon(as, 1); - nd = (unsigned char)rcMin((int)dist[aai]+3, 255); - if (nd < dist[i]) - dist[i] = nd; - } - } - if (rcGetCon(s, 1) != RC_NOT_CONNECTED) - { - // (0,1) - const int ax = x + rcGetDirOffsetX(1); - const int ay = y + rcGetDirOffsetY(1); - const int ai = (int)chf.cells[ax+ay*w].index + rcGetCon(s, 1); - const rcCompactSpan& as = chf.spans[ai]; - nd = (unsigned char)rcMin((int)dist[ai]+2, 255); - if (nd < dist[i]) - dist[i] = nd; - - // (-1,1) - if (rcGetCon(as, 0) != RC_NOT_CONNECTED) - { - const int aax = ax + rcGetDirOffsetX(0); - const int aay = ay + rcGetDirOffsetY(0); - const int aai = (int)chf.cells[aax+aay*w].index + rcGetCon(as, 0); - nd = (unsigned char)rcMin((int)dist[aai]+3, 255); - if (nd < dist[i]) - dist[i] = nd; - } - } - } - } - } - - const unsigned char thr = (unsigned char)(radius*2); - for (int i = 0; i < chf.spanCount; ++i) - if (dist[i] < thr) - chf.areas[i] = RC_NULL_AREA; - - rcFree(dist); - - ctx->stopTimer(RC_TIMER_ERODE_AREA); - - return true; -} - -static void insertSort(unsigned char* a, const int n) -{ - int i, j; - for (i = 1; i < n; i++) - { - const unsigned char value = a[i]; - for (j = i - 1; j >= 0 && a[j] > value; j--) - a[j+1] = a[j]; - a[j+1] = value; - } -} - -/// @par -/// -/// This filter is usually applied after applying area id's using functions -/// such as #rcMarkBoxArea, #rcMarkConvexPolyArea, and #rcMarkCylinderArea. -/// -/// @see rcCompactHeightfield -bool rcMedianFilterWalkableArea(rcContext* ctx, rcCompactHeightfield& chf) -{ - rcAssert(ctx); - - const int w = chf.width; - const int h = chf.height; - - ctx->startTimer(RC_TIMER_MEDIAN_AREA); - - unsigned char* areas = (unsigned char*)rcAlloc(sizeof(unsigned char)*chf.spanCount, RC_ALLOC_TEMP); - if (!areas) - { - ctx->log(RC_LOG_ERROR, "medianFilterWalkableArea: Out of memory 'areas' (%d).", chf.spanCount); - return false; - } - - // Init distance. - memset(areas, 0xff, sizeof(unsigned char)*chf.spanCount); - - for (int y = 0; y < h; ++y) - { - for (int x = 0; x < w; ++x) - { - const rcCompactCell& c = chf.cells[x+y*w]; - for (int i = (int)c.index, ni = (int)(c.index+c.count); i < ni; ++i) - { - const rcCompactSpan& s = chf.spans[i]; - if (chf.areas[i] == RC_NULL_AREA) - { - areas[i] = chf.areas[i]; - continue; - } - - unsigned char nei[9]; - for (int j = 0; j < 9; ++j) - nei[j] = chf.areas[i]; - - for (int dir = 0; dir < 4; ++dir) - { - if (rcGetCon(s, dir) != RC_NOT_CONNECTED) - { - const int ax = x + rcGetDirOffsetX(dir); - const int ay = y + rcGetDirOffsetY(dir); - const int ai = (int)chf.cells[ax+ay*w].index + rcGetCon(s, dir); - if (chf.areas[ai] != RC_NULL_AREA) - nei[dir*2+0] = chf.areas[ai]; - - const rcCompactSpan& as = chf.spans[ai]; - const int dir2 = (dir+1) & 0x3; - if (rcGetCon(as, dir2) != RC_NOT_CONNECTED) - { - const int ax2 = ax + rcGetDirOffsetX(dir2); - const int ay2 = ay + rcGetDirOffsetY(dir2); - const int ai2 = (int)chf.cells[ax2+ay2*w].index + rcGetCon(as, dir2); - if (chf.areas[ai2] != RC_NULL_AREA) - nei[dir*2+1] = chf.areas[ai2]; - } - } - } - insertSort(nei, 9); - areas[i] = nei[4]; - } - } - } - - memcpy(chf.areas, areas, sizeof(unsigned char)*chf.spanCount); - - rcFree(areas); - - ctx->stopTimer(RC_TIMER_MEDIAN_AREA); - - return true; -} - -/// @par -/// -/// The value of spacial parameters are in world units. -/// -/// @see rcCompactHeightfield, rcMedianFilterWalkableArea -void rcMarkBoxArea(rcContext* ctx, const float* bmin, const float* bmax, unsigned char areaId, - rcCompactHeightfield& chf) -{ - rcAssert(ctx); - - ctx->startTimer(RC_TIMER_MARK_BOX_AREA); - - int minx = (int)((bmin[0]-chf.bmin[0])/chf.cs); - int miny = (int)((bmin[1]-chf.bmin[1])/chf.ch); - int minz = (int)((bmin[2]-chf.bmin[2])/chf.cs); - int maxx = (int)((bmax[0]-chf.bmin[0])/chf.cs); - int maxy = (int)((bmax[1]-chf.bmin[1])/chf.ch); - int maxz = (int)((bmax[2]-chf.bmin[2])/chf.cs); - - if (maxx < 0) return; - if (minx >= chf.width) return; - if (maxz < 0) return; - if (minz >= chf.height) return; - - if (minx < 0) minx = 0; - if (maxx >= chf.width) maxx = chf.width-1; - if (minz < 0) minz = 0; - if (maxz >= chf.height) maxz = chf.height-1; - - for (int z = minz; z <= maxz; ++z) - { - for (int x = minx; x <= maxx; ++x) - { - const rcCompactCell& c = chf.cells[x+z*chf.width]; - for (int i = (int)c.index, ni = (int)(c.index+c.count); i < ni; ++i) - { - rcCompactSpan& s = chf.spans[i]; - if ((int)s.y >= miny && (int)s.y <= maxy) - { - if (chf.areas[i] != RC_NULL_AREA) - chf.areas[i] = areaId; - } - } - } - } - - ctx->stopTimer(RC_TIMER_MARK_BOX_AREA); - -} - - -static int pointInPoly(int nvert, const float* verts, const float* p) -{ - int i, j, c = 0; - for (i = 0, j = nvert-1; i < nvert; j = i++) - { - const float* vi = &verts[i*3]; - const float* vj = &verts[j*3]; - if (((vi[2] > p[2]) != (vj[2] > p[2])) && - (p[0] < (vj[0]-vi[0]) * (p[2]-vi[2]) / (vj[2]-vi[2]) + vi[0]) ) - c = !c; - } - return c; -} - -/// @par -/// -/// The value of spacial parameters are in world units. -/// -/// The y-values of the polygon vertices are ignored. So the polygon is effectively -/// projected onto the xz-plane at @p hmin, then extruded to @p hmax. -/// -/// @see rcCompactHeightfield, rcMedianFilterWalkableArea -void rcMarkConvexPolyArea(rcContext* ctx, const float* verts, const int nverts, - const float hmin, const float hmax, unsigned char areaId, - rcCompactHeightfield& chf) -{ - rcAssert(ctx); - - ctx->startTimer(RC_TIMER_MARK_CONVEXPOLY_AREA); - - float bmin[3], bmax[3]; - rcVcopy(bmin, verts); - rcVcopy(bmax, verts); - for (int i = 1; i < nverts; ++i) - { - rcVmin(bmin, &verts[i*3]); - rcVmax(bmax, &verts[i*3]); - } - bmin[1] = hmin; - bmax[1] = hmax; - - int minx = (int)((bmin[0]-chf.bmin[0])/chf.cs); - int miny = (int)((bmin[1]-chf.bmin[1])/chf.ch); - int minz = (int)((bmin[2]-chf.bmin[2])/chf.cs); - int maxx = (int)((bmax[0]-chf.bmin[0])/chf.cs); - int maxy = (int)((bmax[1]-chf.bmin[1])/chf.ch); - int maxz = (int)((bmax[2]-chf.bmin[2])/chf.cs); - - if (maxx < 0) return; - if (minx >= chf.width) return; - if (maxz < 0) return; - if (minz >= chf.height) return; - - if (minx < 0) minx = 0; - if (maxx >= chf.width) maxx = chf.width-1; - if (minz < 0) minz = 0; - if (maxz >= chf.height) maxz = chf.height-1; - - - // TODO: Optimize. - for (int z = minz; z <= maxz; ++z) - { - for (int x = minx; x <= maxx; ++x) - { - const rcCompactCell& c = chf.cells[x+z*chf.width]; - for (int i = (int)c.index, ni = (int)(c.index+c.count); i < ni; ++i) - { - rcCompactSpan& s = chf.spans[i]; - if (chf.areas[i] == RC_NULL_AREA) - continue; - if ((int)s.y >= miny && (int)s.y <= maxy) - { - float p[3]; - p[0] = chf.bmin[0] + (x+0.5f)*chf.cs; - p[1] = 0; - p[2] = chf.bmin[2] + (z+0.5f)*chf.cs; - - if (pointInPoly(nverts, verts, p)) - { - chf.areas[i] = areaId; - } - } - } - } - } - - ctx->stopTimer(RC_TIMER_MARK_CONVEXPOLY_AREA); -} - -int rcOffsetPoly(const float* verts, const int nverts, const float offset, - float* outVerts, const int maxOutVerts) -{ - const float MITER_LIMIT = 1.20f; - - int n = 0; - - for (int i = 0; i < nverts; i++) - { - const int a = (i+nverts-1) % nverts; - const int b = i; - const int c = (i+1) % nverts; - const float* va = &verts[a*3]; - const float* vb = &verts[b*3]; - const float* vc = &verts[c*3]; - float dx0 = vb[0] - va[0]; - float dy0 = vb[2] - va[2]; - float d0 = dx0*dx0 + dy0*dy0; - if (d0 > 1e-6f) - { - d0 = 1.0f/rcSqrt(d0); - dx0 *= d0; - dy0 *= d0; - } - float dx1 = vc[0] - vb[0]; - float dy1 = vc[2] - vb[2]; - float d1 = dx1*dx1 + dy1*dy1; - if (d1 > 1e-6f) - { - d1 = 1.0f/rcSqrt(d1); - dx1 *= d1; - dy1 *= d1; - } - const float dlx0 = -dy0; - const float dly0 = dx0; - const float dlx1 = -dy1; - const float dly1 = dx1; - float cross = dx1*dy0 - dx0*dy1; - float dmx = (dlx0 + dlx1) * 0.5f; - float dmy = (dly0 + dly1) * 0.5f; - float dmr2 = dmx*dmx + dmy*dmy; - bool bevel = dmr2 * MITER_LIMIT*MITER_LIMIT < 1.0f; - if (dmr2 > 1e-6f) - { - const float scale = 1.0f / dmr2; - dmx *= scale; - dmy *= scale; - } - - if (bevel && cross < 0.0f) - { - if (n+2 >= maxOutVerts) - return 0; - float d = (1.0f - (dx0*dx1 + dy0*dy1))*0.5f; - outVerts[n*3+0] = vb[0] + (-dlx0+dx0*d)*offset; - outVerts[n*3+1] = vb[1]; - outVerts[n*3+2] = vb[2] + (-dly0+dy0*d)*offset; - n++; - outVerts[n*3+0] = vb[0] + (-dlx1-dx1*d)*offset; - outVerts[n*3+1] = vb[1]; - outVerts[n*3+2] = vb[2] + (-dly1-dy1*d)*offset; - n++; - } - else - { - if (n+1 >= maxOutVerts) - return 0; - outVerts[n*3+0] = vb[0] - dmx*offset; - outVerts[n*3+1] = vb[1]; - outVerts[n*3+2] = vb[2] - dmy*offset; - n++; - } - } - - return n; -} - - -/// @par -/// -/// The value of spacial parameters are in world units. -/// -/// @see rcCompactHeightfield, rcMedianFilterWalkableArea -void rcMarkCylinderArea(rcContext* ctx, const float* pos, - const float r, const float h, unsigned char areaId, - rcCompactHeightfield& chf) -{ - rcAssert(ctx); - - ctx->startTimer(RC_TIMER_MARK_CYLINDER_AREA); - - float bmin[3], bmax[3]; - bmin[0] = pos[0] - r; - bmin[1] = pos[1]; - bmin[2] = pos[2] - r; - bmax[0] = pos[0] + r; - bmax[1] = pos[1] + h; - bmax[2] = pos[2] + r; - const float r2 = r*r; - - int minx = (int)((bmin[0]-chf.bmin[0])/chf.cs); - int miny = (int)((bmin[1]-chf.bmin[1])/chf.ch); - int minz = (int)((bmin[2]-chf.bmin[2])/chf.cs); - int maxx = (int)((bmax[0]-chf.bmin[0])/chf.cs); - int maxy = (int)((bmax[1]-chf.bmin[1])/chf.ch); - int maxz = (int)((bmax[2]-chf.bmin[2])/chf.cs); - - if (maxx < 0) return; - if (minx >= chf.width) return; - if (maxz < 0) return; - if (minz >= chf.height) return; - - if (minx < 0) minx = 0; - if (maxx >= chf.width) maxx = chf.width-1; - if (minz < 0) minz = 0; - if (maxz >= chf.height) maxz = chf.height-1; - - - for (int z = minz; z <= maxz; ++z) - { - for (int x = minx; x <= maxx; ++x) - { - const rcCompactCell& c = chf.cells[x+z*chf.width]; - for (int i = (int)c.index, ni = (int)(c.index+c.count); i < ni; ++i) - { - rcCompactSpan& s = chf.spans[i]; - - if (chf.areas[i] == RC_NULL_AREA) - continue; - - if ((int)s.y >= miny && (int)s.y <= maxy) - { - const float sx = chf.bmin[0] + (x+0.5f)*chf.cs; - const float sz = chf.bmin[2] + (z+0.5f)*chf.cs; - const float dx = sx - pos[0]; - const float dz = sz - pos[2]; - - if (dx*dx + dz*dz < r2) - { - chf.areas[i] = areaId; - } - } - } - } - } - - ctx->stopTimer(RC_TIMER_MARK_CYLINDER_AREA); -} diff --git a/KREngine/3rdparty/recast/source/RecastContour.cpp b/KREngine/3rdparty/recast/source/RecastContour.cpp deleted file mode 100755 index 5c324bc..0000000 --- a/KREngine/3rdparty/recast/source/RecastContour.cpp +++ /dev/null @@ -1,851 +0,0 @@ -// -// Copyright (c) 2009-2010 Mikko Mononen memon@inside.org -// -// This software is provided 'as-is', without any express or implied -// warranty. In no event will the authors be held liable for any damages -// arising from the use of this software. -// Permission is granted to anyone to use this software for any purpose, -// including commercial applications, and to alter it and redistribute it -// freely, subject to the following restrictions: -// 1. The origin of this software must not be misrepresented; you must not -// claim that you wrote the original software. If you use this software -// in a product, an acknowledgment in the product documentation would be -// appreciated but is not required. -// 2. Altered source versions must be plainly marked as such, and must not be -// misrepresented as being the original software. -// 3. This notice may not be removed or altered from any source distribution. -// - -#define _USE_MATH_DEFINES -#include -#include -#include -#include "Recast.h" -#include "RecastAlloc.h" -#include "RecastAssert.h" - - -static int getCornerHeight(int x, int y, int i, int dir, - const rcCompactHeightfield& chf, - bool& isBorderVertex) -{ - const rcCompactSpan& s = chf.spans[i]; - int ch = (int)s.y; - int dirp = (dir+1) & 0x3; - - unsigned int regs[4] = {0,0,0,0}; - - // Combine region and area codes in order to prevent - // border vertices which are in between two areas to be removed. - regs[0] = chf.spans[i].reg | (chf.areas[i] << 16); - - if (rcGetCon(s, dir) != RC_NOT_CONNECTED) - { - const int ax = x + rcGetDirOffsetX(dir); - const int ay = y + rcGetDirOffsetY(dir); - const int ai = (int)chf.cells[ax+ay*chf.width].index + rcGetCon(s, dir); - const rcCompactSpan& as = chf.spans[ai]; - ch = rcMax(ch, (int)as.y); - regs[1] = chf.spans[ai].reg | (chf.areas[ai] << 16); - if (rcGetCon(as, dirp) != RC_NOT_CONNECTED) - { - const int ax2 = ax + rcGetDirOffsetX(dirp); - const int ay2 = ay + rcGetDirOffsetY(dirp); - const int ai2 = (int)chf.cells[ax2+ay2*chf.width].index + rcGetCon(as, dirp); - const rcCompactSpan& as2 = chf.spans[ai2]; - ch = rcMax(ch, (int)as2.y); - regs[2] = chf.spans[ai2].reg | (chf.areas[ai2] << 16); - } - } - if (rcGetCon(s, dirp) != RC_NOT_CONNECTED) - { - const int ax = x + rcGetDirOffsetX(dirp); - const int ay = y + rcGetDirOffsetY(dirp); - const int ai = (int)chf.cells[ax+ay*chf.width].index + rcGetCon(s, dirp); - const rcCompactSpan& as = chf.spans[ai]; - ch = rcMax(ch, (int)as.y); - regs[3] = chf.spans[ai].reg | (chf.areas[ai] << 16); - if (rcGetCon(as, dir) != RC_NOT_CONNECTED) - { - const int ax2 = ax + rcGetDirOffsetX(dir); - const int ay2 = ay + rcGetDirOffsetY(dir); - const int ai2 = (int)chf.cells[ax2+ay2*chf.width].index + rcGetCon(as, dir); - const rcCompactSpan& as2 = chf.spans[ai2]; - ch = rcMax(ch, (int)as2.y); - regs[2] = chf.spans[ai2].reg | (chf.areas[ai2] << 16); - } - } - - // Check if the vertex is special edge vertex, these vertices will be removed later. - for (int j = 0; j < 4; ++j) - { - const int a = j; - const int b = (j+1) & 0x3; - const int c = (j+2) & 0x3; - const int d = (j+3) & 0x3; - - // The vertex is a border vertex there are two same exterior cells in a row, - // followed by two interior cells and none of the regions are out of bounds. - const bool twoSameExts = (regs[a] & regs[b] & RC_BORDER_REG) != 0 && regs[a] == regs[b]; - const bool twoInts = ((regs[c] | regs[d]) & RC_BORDER_REG) == 0; - const bool intsSameArea = (regs[c]>>16) == (regs[d]>>16); - const bool noZeros = regs[a] != 0 && regs[b] != 0 && regs[c] != 0 && regs[d] != 0; - if (twoSameExts && twoInts && intsSameArea && noZeros) - { - isBorderVertex = true; - break; - } - } - - return ch; -} - -static void walkContour(int x, int y, int i, - rcCompactHeightfield& chf, - unsigned char* flags, rcIntArray& points) -{ - // Choose the first non-connected edge - unsigned char dir = 0; - while ((flags[i] & (1 << dir)) == 0) - dir++; - - unsigned char startDir = dir; - int starti = i; - - const unsigned char area = chf.areas[i]; - - int iter = 0; - while (++iter < 40000) - { - if (flags[i] & (1 << dir)) - { - // Choose the edge corner - bool isBorderVertex = false; - bool isAreaBorder = false; - int px = x; - int py = getCornerHeight(x, y, i, dir, chf, isBorderVertex); - int pz = y; - switch(dir) - { - case 0: pz++; break; - case 1: px++; pz++; break; - case 2: px++; break; - } - int r = 0; - const rcCompactSpan& s = chf.spans[i]; - if (rcGetCon(s, dir) != RC_NOT_CONNECTED) - { - const int ax = x + rcGetDirOffsetX(dir); - const int ay = y + rcGetDirOffsetY(dir); - const int ai = (int)chf.cells[ax+ay*chf.width].index + rcGetCon(s, dir); - r = (int)chf.spans[ai].reg; - if (area != chf.areas[ai]) - isAreaBorder = true; - } - if (isBorderVertex) - r |= RC_BORDER_VERTEX; - if (isAreaBorder) - r |= RC_AREA_BORDER; - points.push(px); - points.push(py); - points.push(pz); - points.push(r); - - flags[i] &= ~(1 << dir); // Remove visited edges - dir = (dir+1) & 0x3; // Rotate CW - } - else - { - int ni = -1; - const int nx = x + rcGetDirOffsetX(dir); - const int ny = y + rcGetDirOffsetY(dir); - const rcCompactSpan& s = chf.spans[i]; - if (rcGetCon(s, dir) != RC_NOT_CONNECTED) - { - const rcCompactCell& nc = chf.cells[nx+ny*chf.width]; - ni = (int)nc.index + rcGetCon(s, dir); - } - if (ni == -1) - { - // Should not happen. - return; - } - x = nx; - y = ny; - i = ni; - dir = (dir+3) & 0x3; // Rotate CCW - } - - if (starti == i && startDir == dir) - { - break; - } - } -} - -static float distancePtSeg(const int x, const int z, - const int px, const int pz, - const int qx, const int qz) -{ -/* float pqx = (float)(qx - px); - float pqy = (float)(qy - py); - float pqz = (float)(qz - pz); - float dx = (float)(x - px); - float dy = (float)(y - py); - float dz = (float)(z - pz); - float d = pqx*pqx + pqy*pqy + pqz*pqz; - float t = pqx*dx + pqy*dy + pqz*dz; - if (d > 0) - t /= d; - if (t < 0) - t = 0; - else if (t > 1) - t = 1; - - dx = px + t*pqx - x; - dy = py + t*pqy - y; - dz = pz + t*pqz - z; - - return dx*dx + dy*dy + dz*dz;*/ - - float pqx = (float)(qx - px); - float pqz = (float)(qz - pz); - float dx = (float)(x - px); - float dz = (float)(z - pz); - float d = pqx*pqx + pqz*pqz; - float t = pqx*dx + pqz*dz; - if (d > 0) - t /= d; - if (t < 0) - t = 0; - else if (t > 1) - t = 1; - - dx = px + t*pqx - x; - dz = pz + t*pqz - z; - - return dx*dx + dz*dz; -} - -static void simplifyContour(rcIntArray& points, rcIntArray& simplified, - const float maxError, const int maxEdgeLen, const int buildFlags) -{ - // Add initial points. - bool hasConnections = false; - for (int i = 0; i < points.size(); i += 4) - { - if ((points[i+3] & RC_CONTOUR_REG_MASK) != 0) - { - hasConnections = true; - break; - } - } - - if (hasConnections) - { - // The contour has some portals to other regions. - // Add a new point to every location where the region changes. - for (int i = 0, ni = points.size()/4; i < ni; ++i) - { - int ii = (i+1) % ni; - const bool differentRegs = (points[i*4+3] & RC_CONTOUR_REG_MASK) != (points[ii*4+3] & RC_CONTOUR_REG_MASK); - const bool areaBorders = (points[i*4+3] & RC_AREA_BORDER) != (points[ii*4+3] & RC_AREA_BORDER); - if (differentRegs || areaBorders) - { - simplified.push(points[i*4+0]); - simplified.push(points[i*4+1]); - simplified.push(points[i*4+2]); - simplified.push(i); - } - } - } - - if (simplified.size() == 0) - { - // If there is no connections at all, - // create some initial points for the simplification process. - // Find lower-left and upper-right vertices of the contour. - int llx = points[0]; - int lly = points[1]; - int llz = points[2]; - int lli = 0; - int urx = points[0]; - int ury = points[1]; - int urz = points[2]; - int uri = 0; - for (int i = 0; i < points.size(); i += 4) - { - int x = points[i+0]; - int y = points[i+1]; - int z = points[i+2]; - if (x < llx || (x == llx && z < llz)) - { - llx = x; - lly = y; - llz = z; - lli = i/4; - } - if (x > urx || (x == urx && z > urz)) - { - urx = x; - ury = y; - urz = z; - uri = i/4; - } - } - simplified.push(llx); - simplified.push(lly); - simplified.push(llz); - simplified.push(lli); - - simplified.push(urx); - simplified.push(ury); - simplified.push(urz); - simplified.push(uri); - } - - // Add points until all raw points are within - // error tolerance to the simplified shape. - const int pn = points.size()/4; - for (int i = 0; i < simplified.size()/4; ) - { - int ii = (i+1) % (simplified.size()/4); - - const int ax = simplified[i*4+0]; - const int az = simplified[i*4+2]; - const int ai = simplified[i*4+3]; - - const int bx = simplified[ii*4+0]; - const int bz = simplified[ii*4+2]; - const int bi = simplified[ii*4+3]; - - // Find maximum deviation from the segment. - float maxd = 0; - int maxi = -1; - int ci, cinc, endi; - - // Traverse the segment in lexilogical order so that the - // max deviation is calculated similarly when traversing - // opposite segments. - if (bx > ax || (bx == ax && bz > az)) - { - cinc = 1; - ci = (ai+cinc) % pn; - endi = bi; - } - else - { - cinc = pn-1; - ci = (bi+cinc) % pn; - endi = ai; - } - - // Tessellate only outer edges or edges between areas. - if ((points[ci*4+3] & RC_CONTOUR_REG_MASK) == 0 || - (points[ci*4+3] & RC_AREA_BORDER)) - { - while (ci != endi) - { - float d = distancePtSeg(points[ci*4+0], points[ci*4+2], ax, az, bx, bz); - if (d > maxd) - { - maxd = d; - maxi = ci; - } - ci = (ci+cinc) % pn; - } - } - - - // If the max deviation is larger than accepted error, - // add new point, else continue to next segment. - if (maxi != -1 && maxd > (maxError*maxError)) - { - // Add space for the new point. - simplified.resize(simplified.size()+4); - const int n = simplified.size()/4; - for (int j = n-1; j > i; --j) - { - simplified[j*4+0] = simplified[(j-1)*4+0]; - simplified[j*4+1] = simplified[(j-1)*4+1]; - simplified[j*4+2] = simplified[(j-1)*4+2]; - simplified[j*4+3] = simplified[(j-1)*4+3]; - } - // Add the point. - simplified[(i+1)*4+0] = points[maxi*4+0]; - simplified[(i+1)*4+1] = points[maxi*4+1]; - simplified[(i+1)*4+2] = points[maxi*4+2]; - simplified[(i+1)*4+3] = maxi; - } - else - { - ++i; - } - } - - // Split too long edges. - if (maxEdgeLen > 0 && (buildFlags & (RC_CONTOUR_TESS_WALL_EDGES|RC_CONTOUR_TESS_AREA_EDGES)) != 0) - { - for (int i = 0; i < simplified.size()/4; ) - { - const int ii = (i+1) % (simplified.size()/4); - - const int ax = simplified[i*4+0]; - const int az = simplified[i*4+2]; - const int ai = simplified[i*4+3]; - - const int bx = simplified[ii*4+0]; - const int bz = simplified[ii*4+2]; - const int bi = simplified[ii*4+3]; - - // Find maximum deviation from the segment. - int maxi = -1; - int ci = (ai+1) % pn; - - // Tessellate only outer edges or edges between areas. - bool tess = false; - // Wall edges. - if ((buildFlags & RC_CONTOUR_TESS_WALL_EDGES) && (points[ci*4+3] & RC_CONTOUR_REG_MASK) == 0) - tess = true; - // Edges between areas. - if ((buildFlags & RC_CONTOUR_TESS_AREA_EDGES) && (points[ci*4+3] & RC_AREA_BORDER)) - tess = true; - - if (tess) - { - int dx = bx - ax; - int dz = bz - az; - if (dx*dx + dz*dz > maxEdgeLen*maxEdgeLen) - { - // Round based on the segments in lexilogical order so that the - // max tesselation is consistent regardles in which direction - // segments are traversed. - const int n = bi < ai ? (bi+pn - ai) : (bi - ai); - if (n > 1) - { - if (bx > ax || (bx == ax && bz > az)) - maxi = (ai + n/2) % pn; - else - maxi = (ai + (n+1)/2) % pn; - } - } - } - - // If the max deviation is larger than accepted error, - // add new point, else continue to next segment. - if (maxi != -1) - { - // Add space for the new point. - simplified.resize(simplified.size()+4); - const int n = simplified.size()/4; - for (int j = n-1; j > i; --j) - { - simplified[j*4+0] = simplified[(j-1)*4+0]; - simplified[j*4+1] = simplified[(j-1)*4+1]; - simplified[j*4+2] = simplified[(j-1)*4+2]; - simplified[j*4+3] = simplified[(j-1)*4+3]; - } - // Add the point. - simplified[(i+1)*4+0] = points[maxi*4+0]; - simplified[(i+1)*4+1] = points[maxi*4+1]; - simplified[(i+1)*4+2] = points[maxi*4+2]; - simplified[(i+1)*4+3] = maxi; - } - else - { - ++i; - } - } - } - - for (int i = 0; i < simplified.size()/4; ++i) - { - // The edge vertex flag is take from the current raw point, - // and the neighbour region is take from the next raw point. - const int ai = (simplified[i*4+3]+1) % pn; - const int bi = simplified[i*4+3]; - simplified[i*4+3] = (points[ai*4+3] & (RC_CONTOUR_REG_MASK|RC_AREA_BORDER)) | (points[bi*4+3] & RC_BORDER_VERTEX); - } - -} - -static void removeDegenerateSegments(rcIntArray& simplified) -{ - // Remove adjacent vertices which are equal on xz-plane, - // or else the triangulator will get confused. - for (int i = 0; i < simplified.size()/4; ++i) - { - int ni = i+1; - if (ni >= (simplified.size()/4)) - ni = 0; - - if (simplified[i*4+0] == simplified[ni*4+0] && - simplified[i*4+2] == simplified[ni*4+2]) - { - // Degenerate segment, remove. - for (int j = i; j < simplified.size()/4-1; ++j) - { - simplified[j*4+0] = simplified[(j+1)*4+0]; - simplified[j*4+1] = simplified[(j+1)*4+1]; - simplified[j*4+2] = simplified[(j+1)*4+2]; - simplified[j*4+3] = simplified[(j+1)*4+3]; - } - simplified.resize(simplified.size()-4); - } - } -} - -static int calcAreaOfPolygon2D(const int* verts, const int nverts) -{ - int area = 0; - for (int i = 0, j = nverts-1; i < nverts; j=i++) - { - const int* vi = &verts[i*4]; - const int* vj = &verts[j*4]; - area += vi[0] * vj[2] - vj[0] * vi[2]; - } - return (area+1) / 2; -} - -inline bool ileft(const int* a, const int* b, const int* c) -{ - return (b[0] - a[0]) * (c[2] - a[2]) - (c[0] - a[0]) * (b[2] - a[2]) <= 0; -} - -static void getClosestIndices(const int* vertsa, const int nvertsa, - const int* vertsb, const int nvertsb, - int& ia, int& ib) -{ - int closestDist = 0xfffffff; - ia = -1, ib = -1; - for (int i = 0; i < nvertsa; ++i) - { - const int in = (i+1) % nvertsa; - const int ip = (i+nvertsa-1) % nvertsa; - const int* va = &vertsa[i*4]; - const int* van = &vertsa[in*4]; - const int* vap = &vertsa[ip*4]; - - for (int j = 0; j < nvertsb; ++j) - { - const int* vb = &vertsb[j*4]; - // vb must be "infront" of va. - if (ileft(vap,va,vb) && ileft(va,van,vb)) - { - const int dx = vb[0] - va[0]; - const int dz = vb[2] - va[2]; - const int d = dx*dx + dz*dz; - if (d < closestDist) - { - ia = i; - ib = j; - closestDist = d; - } - } - } - } -} - -static bool mergeContours(rcContour& ca, rcContour& cb, int ia, int ib) -{ - const int maxVerts = ca.nverts + cb.nverts + 2; - int* verts = (int*)rcAlloc(sizeof(int)*maxVerts*4, RC_ALLOC_PERM); - if (!verts) - return false; - - int nv = 0; - - // Copy contour A. - for (int i = 0; i <= ca.nverts; ++i) - { - int* dst = &verts[nv*4]; - const int* src = &ca.verts[((ia+i)%ca.nverts)*4]; - dst[0] = src[0]; - dst[1] = src[1]; - dst[2] = src[2]; - dst[3] = src[3]; - nv++; - } - - // Copy contour B - for (int i = 0; i <= cb.nverts; ++i) - { - int* dst = &verts[nv*4]; - const int* src = &cb.verts[((ib+i)%cb.nverts)*4]; - dst[0] = src[0]; - dst[1] = src[1]; - dst[2] = src[2]; - dst[3] = src[3]; - nv++; - } - - rcFree(ca.verts); - ca.verts = verts; - ca.nverts = nv; - - rcFree(cb.verts); - cb.verts = 0; - cb.nverts = 0; - - return true; -} - -/// @par -/// -/// The raw contours will match the region outlines exactly. The @p maxError and @p maxEdgeLen -/// parameters control how closely the simplified contours will match the raw contours. -/// -/// Simplified contours are generated such that the vertices for portals between areas match up. -/// (They are considered mandatory vertices.) -/// -/// Setting @p maxEdgeLength to zero will disabled the edge length feature. -/// -/// See the #rcConfig documentation for more information on the configuration parameters. -/// -/// @see rcAllocContourSet, rcCompactHeightfield, rcContourSet, rcConfig -bool rcBuildContours(rcContext* ctx, rcCompactHeightfield& chf, - const float maxError, const int maxEdgeLen, - rcContourSet& cset, const int buildFlags) -{ - rcAssert(ctx); - - const int w = chf.width; - const int h = chf.height; - const int borderSize = chf.borderSize; - - ctx->startTimer(RC_TIMER_BUILD_CONTOURS); - - rcVcopy(cset.bmin, chf.bmin); - rcVcopy(cset.bmax, chf.bmax); - if (borderSize > 0) - { - // If the heightfield was build with bordersize, remove the offset. - const float pad = borderSize*chf.cs; - cset.bmin[0] += pad; - cset.bmin[2] += pad; - cset.bmax[0] -= pad; - cset.bmax[2] -= pad; - } - cset.cs = chf.cs; - cset.ch = chf.ch; - cset.width = chf.width - chf.borderSize*2; - cset.height = chf.height - chf.borderSize*2; - cset.borderSize = chf.borderSize; - - int maxContours = rcMax((int)chf.maxRegions, 8); - cset.conts = (rcContour*)rcAlloc(sizeof(rcContour)*maxContours, RC_ALLOC_PERM); - if (!cset.conts) - return false; - cset.nconts = 0; - - rcScopedDelete flags = (unsigned char*)rcAlloc(sizeof(unsigned char)*chf.spanCount, RC_ALLOC_TEMP); - if (!flags) - { - ctx->log(RC_LOG_ERROR, "rcBuildContours: Out of memory 'flags' (%d).", chf.spanCount); - return false; - } - - ctx->startTimer(RC_TIMER_BUILD_CONTOURS_TRACE); - - // Mark boundaries. - for (int y = 0; y < h; ++y) - { - for (int x = 0; x < w; ++x) - { - const rcCompactCell& c = chf.cells[x+y*w]; - for (int i = (int)c.index, ni = (int)(c.index+c.count); i < ni; ++i) - { - unsigned char res = 0; - const rcCompactSpan& s = chf.spans[i]; - if (!chf.spans[i].reg || (chf.spans[i].reg & RC_BORDER_REG)) - { - flags[i] = 0; - continue; - } - for (int dir = 0; dir < 4; ++dir) - { - unsigned short r = 0; - if (rcGetCon(s, dir) != RC_NOT_CONNECTED) - { - const int ax = x + rcGetDirOffsetX(dir); - const int ay = y + rcGetDirOffsetY(dir); - const int ai = (int)chf.cells[ax+ay*w].index + rcGetCon(s, dir); - r = chf.spans[ai].reg; - } - if (r == chf.spans[i].reg) - res |= (1 << dir); - } - flags[i] = res ^ 0xf; // Inverse, mark non connected edges. - } - } - } - - ctx->stopTimer(RC_TIMER_BUILD_CONTOURS_TRACE); - - rcIntArray verts(256); - rcIntArray simplified(64); - - for (int y = 0; y < h; ++y) - { - for (int x = 0; x < w; ++x) - { - const rcCompactCell& c = chf.cells[x+y*w]; - for (int i = (int)c.index, ni = (int)(c.index+c.count); i < ni; ++i) - { - if (flags[i] == 0 || flags[i] == 0xf) - { - flags[i] = 0; - continue; - } - const unsigned short reg = chf.spans[i].reg; - if (!reg || (reg & RC_BORDER_REG)) - continue; - const unsigned char area = chf.areas[i]; - - verts.resize(0); - simplified.resize(0); - - ctx->startTimer(RC_TIMER_BUILD_CONTOURS_TRACE); - walkContour(x, y, i, chf, flags, verts); - ctx->stopTimer(RC_TIMER_BUILD_CONTOURS_TRACE); - - ctx->startTimer(RC_TIMER_BUILD_CONTOURS_SIMPLIFY); - simplifyContour(verts, simplified, maxError, maxEdgeLen, buildFlags); - removeDegenerateSegments(simplified); - ctx->stopTimer(RC_TIMER_BUILD_CONTOURS_SIMPLIFY); - - - // Store region->contour remap info. - // Create contour. - if (simplified.size()/4 >= 3) - { - if (cset.nconts >= maxContours) - { - // Allocate more contours. - // This can happen when there are tiny holes in the heightfield. - const int oldMax = maxContours; - maxContours *= 2; - rcContour* newConts = (rcContour*)rcAlloc(sizeof(rcContour)*maxContours, RC_ALLOC_PERM); - for (int j = 0; j < cset.nconts; ++j) - { - newConts[j] = cset.conts[j]; - // Reset source pointers to prevent data deletion. - cset.conts[j].verts = 0; - cset.conts[j].rverts = 0; - } - rcFree(cset.conts); - cset.conts = newConts; - - ctx->log(RC_LOG_WARNING, "rcBuildContours: Expanding max contours from %d to %d.", oldMax, maxContours); - } - - rcContour* cont = &cset.conts[cset.nconts++]; - - cont->nverts = simplified.size()/4; - cont->verts = (int*)rcAlloc(sizeof(int)*cont->nverts*4, RC_ALLOC_PERM); - if (!cont->verts) - { - ctx->log(RC_LOG_ERROR, "rcBuildContours: Out of memory 'verts' (%d).", cont->nverts); - return false; - } - memcpy(cont->verts, &simplified[0], sizeof(int)*cont->nverts*4); - if (borderSize > 0) - { - // If the heightfield was build with bordersize, remove the offset. - for (int j = 0; j < cont->nverts; ++j) - { - int* v = &cont->verts[j*4]; - v[0] -= borderSize; - v[2] -= borderSize; - } - } - - cont->nrverts = verts.size()/4; - cont->rverts = (int*)rcAlloc(sizeof(int)*cont->nrverts*4, RC_ALLOC_PERM); - if (!cont->rverts) - { - ctx->log(RC_LOG_ERROR, "rcBuildContours: Out of memory 'rverts' (%d).", cont->nrverts); - return false; - } - memcpy(cont->rverts, &verts[0], sizeof(int)*cont->nrverts*4); - if (borderSize > 0) - { - // If the heightfield was build with bordersize, remove the offset. - for (int j = 0; j < cont->nrverts; ++j) - { - int* v = &cont->rverts[j*4]; - v[0] -= borderSize; - v[2] -= borderSize; - } - } - -/* cont->cx = cont->cy = cont->cz = 0; - for (int i = 0; i < cont->nverts; ++i) - { - cont->cx += cont->verts[i*4+0]; - cont->cy += cont->verts[i*4+1]; - cont->cz += cont->verts[i*4+2]; - } - cont->cx /= cont->nverts; - cont->cy /= cont->nverts; - cont->cz /= cont->nverts;*/ - - cont->reg = reg; - cont->area = area; - } - } - } - } - - // Check and merge droppings. - // Sometimes the previous algorithms can fail and create several contours - // per area. This pass will try to merge the holes into the main region. - for (int i = 0; i < cset.nconts; ++i) - { - rcContour& cont = cset.conts[i]; - // Check if the contour is would backwards. - if (calcAreaOfPolygon2D(cont.verts, cont.nverts) < 0) - { - // Find another contour which has the same region ID. - int mergeIdx = -1; - for (int j = 0; j < cset.nconts; ++j) - { - if (i == j) continue; - if (cset.conts[j].nverts && cset.conts[j].reg == cont.reg) - { - // Make sure the polygon is correctly oriented. - if (calcAreaOfPolygon2D(cset.conts[j].verts, cset.conts[j].nverts)) - { - mergeIdx = j; - break; - } - } - } - if (mergeIdx == -1) - { - ctx->log(RC_LOG_WARNING, "rcBuildContours: Could not find merge target for bad contour %d.", i); - } - else - { - rcContour& mcont = cset.conts[mergeIdx]; - // Merge by closest points. - int ia = 0, ib = 0; - getClosestIndices(mcont.verts, mcont.nverts, cont.verts, cont.nverts, ia, ib); - if (ia == -1 || ib == -1) - { - ctx->log(RC_LOG_WARNING, "rcBuildContours: Failed to find merge points for %d and %d.", i, mergeIdx); - continue; - } - if (!mergeContours(mcont, cont, ia, ib)) - { - ctx->log(RC_LOG_WARNING, "rcBuildContours: Failed to merge contours %d and %d.", i, mergeIdx); - continue; - } - } - } - } - - ctx->stopTimer(RC_TIMER_BUILD_CONTOURS); - - return true; -} diff --git a/KREngine/3rdparty/recast/source/RecastFilter.cpp b/KREngine/3rdparty/recast/source/RecastFilter.cpp deleted file mode 100755 index bf985c3..0000000 --- a/KREngine/3rdparty/recast/source/RecastFilter.cpp +++ /dev/null @@ -1,207 +0,0 @@ -// -// Copyright (c) 2009-2010 Mikko Mononen memon@inside.org -// -// This software is provided 'as-is', without any express or implied -// warranty. In no event will the authors be held liable for any damages -// arising from the use of this software. -// Permission is granted to anyone to use this software for any purpose, -// including commercial applications, and to alter it and redistribute it -// freely, subject to the following restrictions: -// 1. The origin of this software must not be misrepresented; you must not -// claim that you wrote the original software. If you use this software -// in a product, an acknowledgment in the product documentation would be -// appreciated but is not required. -// 2. Altered source versions must be plainly marked as such, and must not be -// misrepresented as being the original software. -// 3. This notice may not be removed or altered from any source distribution. -// - -#define _USE_MATH_DEFINES -#include -#include -#include "Recast.h" -#include "RecastAssert.h" - -/// @par -/// -/// Allows the formation of walkable regions that will flow over low lying -/// objects such as curbs, and up structures such as stairways. -/// -/// Two neighboring spans are walkable if: rcAbs(currentSpan.smax - neighborSpan.smax) < waklableClimb -/// -/// @warning Will override the effect of #rcFilterLedgeSpans. So if both filters are used, call -/// #rcFilterLedgeSpans after calling this filter. -/// -/// @see rcHeightfield, rcConfig -void rcFilterLowHangingWalkableObstacles(rcContext* ctx, const int walkableClimb, rcHeightfield& solid) -{ - rcAssert(ctx); - - ctx->startTimer(RC_TIMER_FILTER_LOW_OBSTACLES); - - const int w = solid.width; - const int h = solid.height; - - for (int y = 0; y < h; ++y) - { - for (int x = 0; x < w; ++x) - { - rcSpan* ps = 0; - bool previousWalkable = false; - unsigned char previousArea = RC_NULL_AREA; - - for (rcSpan* s = solid.spans[x + y*w]; s; ps = s, s = s->next) - { - const bool walkable = s->area != RC_NULL_AREA; - // If current span is not walkable, but there is walkable - // span just below it, mark the span above it walkable too. - if (!walkable && previousWalkable) - { - if (rcAbs((int)s->smax - (int)ps->smax) <= walkableClimb) - s->area = previousArea; - } - // Copy walkable flag so that it cannot propagate - // past multiple non-walkable objects. - previousWalkable = walkable; - previousArea = s->area; - } - } - } - - ctx->stopTimer(RC_TIMER_FILTER_LOW_OBSTACLES); -} - -/// @par -/// -/// A ledge is a span with one or more neighbors whose maximum is further away than @p walkableClimb -/// from the current span's maximum. -/// This method removes the impact of the overestimation of conservative voxelization -/// so the resulting mesh will not have regions hanging in the air over ledges. -/// -/// A span is a ledge if: rcAbs(currentSpan.smax - neighborSpan.smax) > walkableClimb -/// -/// @see rcHeightfield, rcConfig -void rcFilterLedgeSpans(rcContext* ctx, const int walkableHeight, const int walkableClimb, - rcHeightfield& solid) -{ - rcAssert(ctx); - - ctx->startTimer(RC_TIMER_FILTER_BORDER); - - const int w = solid.width; - const int h = solid.height; - const int MAX_HEIGHT = 0xffff; - - // Mark border spans. - for (int y = 0; y < h; ++y) - { - for (int x = 0; x < w; ++x) - { - for (rcSpan* s = solid.spans[x + y*w]; s; s = s->next) - { - // Skip non walkable spans. - if (s->area == RC_NULL_AREA) - continue; - - const int bot = (int)(s->smax); - const int top = s->next ? (int)(s->next->smin) : MAX_HEIGHT; - - // Find neighbours minimum height. - int minh = MAX_HEIGHT; - - // Min and max height of accessible neighbours. - int asmin = s->smax; - int asmax = s->smax; - - for (int dir = 0; dir < 4; ++dir) - { - int dx = x + rcGetDirOffsetX(dir); - int dy = y + rcGetDirOffsetY(dir); - // Skip neighbours which are out of bounds. - if (dx < 0 || dy < 0 || dx >= w || dy >= h) - { - minh = rcMin(minh, -walkableClimb - bot); - continue; - } - - // From minus infinity to the first span. - rcSpan* ns = solid.spans[dx + dy*w]; - int nbot = -walkableClimb; - int ntop = ns ? (int)ns->smin : MAX_HEIGHT; - // Skip neightbour if the gap between the spans is too small. - if (rcMin(top,ntop) - rcMax(bot,nbot) > walkableHeight) - minh = rcMin(minh, nbot - bot); - - // Rest of the spans. - for (ns = solid.spans[dx + dy*w]; ns; ns = ns->next) - { - nbot = (int)ns->smax; - ntop = ns->next ? (int)ns->next->smin : MAX_HEIGHT; - // Skip neightbour if the gap between the spans is too small. - if (rcMin(top,ntop) - rcMax(bot,nbot) > walkableHeight) - { - minh = rcMin(minh, nbot - bot); - - // Find min/max accessible neighbour height. - if (rcAbs(nbot - bot) <= walkableClimb) - { - if (nbot < asmin) asmin = nbot; - if (nbot > asmax) asmax = nbot; - } - - } - } - } - - // The current span is close to a ledge if the drop to any - // neighbour span is less than the walkableClimb. - if (minh < -walkableClimb) - s->area = RC_NULL_AREA; - - // If the difference between all neighbours is too large, - // we are at steep slope, mark the span as ledge. - if ((asmax - asmin) > walkableClimb) - { - s->area = RC_NULL_AREA; - } - } - } - } - - ctx->stopTimer(RC_TIMER_FILTER_BORDER); -} - -/// @par -/// -/// For this filter, the clearance above the span is the distance from the span's -/// maximum to the next higher span's minimum. (Same grid column.) -/// -/// @see rcHeightfield, rcConfig -void rcFilterWalkableLowHeightSpans(rcContext* ctx, int walkableHeight, rcHeightfield& solid) -{ - rcAssert(ctx); - - ctx->startTimer(RC_TIMER_FILTER_WALKABLE); - - const int w = solid.width; - const int h = solid.height; - const int MAX_HEIGHT = 0xffff; - - // Remove walkable flag from spans which do not have enough - // space above them for the agent to stand there. - for (int y = 0; y < h; ++y) - { - for (int x = 0; x < w; ++x) - { - for (rcSpan* s = solid.spans[x + y*w]; s; s = s->next) - { - const int bot = (int)(s->smax); - const int top = s->next ? (int)(s->next->smin) : MAX_HEIGHT; - if ((top - bot) <= walkableHeight) - s->area = RC_NULL_AREA; - } - } - } - - ctx->stopTimer(RC_TIMER_FILTER_WALKABLE); -} diff --git a/KREngine/3rdparty/recast/source/RecastLayers.cpp b/KREngine/3rdparty/recast/source/RecastLayers.cpp deleted file mode 100755 index 204f72e..0000000 --- a/KREngine/3rdparty/recast/source/RecastLayers.cpp +++ /dev/null @@ -1,620 +0,0 @@ -// -// Copyright (c) 2009-2010 Mikko Mononen memon@inside.org -// -// This software is provided 'as-is', without any express or implied -// warranty. In no event will the authors be held liable for any damages -// arising from the use of this software. -// Permission is granted to anyone to use this software for any purpose, -// including commercial applications, and to alter it and redistribute it -// freely, subject to the following restrictions: -// 1. The origin of this software must not be misrepresented; you must not -// claim that you wrote the original software. If you use this software -// in a product, an acknowledgment in the product documentation would be -// appreciated but is not required. -// 2. Altered source versions must be plainly marked as such, and must not be -// misrepresented as being the original software. -// 3. This notice may not be removed or altered from any source distribution. -// - -#include -#define _USE_MATH_DEFINES -#include -#include -#include -#include -#include "Recast.h" -#include "RecastAlloc.h" -#include "RecastAssert.h" - - -static const int RC_MAX_LAYERS = RC_NOT_CONNECTED; -static const int RC_MAX_NEIS = 16; - -struct rcLayerRegion -{ - unsigned char layers[RC_MAX_LAYERS]; - unsigned char neis[RC_MAX_NEIS]; - unsigned short ymin, ymax; - unsigned char layerId; // Layer ID - unsigned char nlayers; // Layer count - unsigned char nneis; // Neighbour count - unsigned char base; // Flag indicating if the region is hte base of merged regions. -}; - - -static void addUnique(unsigned char* a, unsigned char& an, unsigned char v) -{ - const int n = (int)an; - for (int i = 0; i < n; ++i) - if (a[i] == v) - return; - a[an] = v; - an++; -} - -static bool contains(const unsigned char* a, const unsigned char an, const unsigned char v) -{ - const int n = (int)an; - for (int i = 0; i < n; ++i) - if (a[i] == v) - return true; - return false; -} - -inline bool overlapRange(const unsigned short amin, const unsigned short amax, - const unsigned short bmin, const unsigned short bmax) -{ - return (amin > bmax || amax < bmin) ? false : true; -} - - - -struct rcLayerSweepSpan -{ - unsigned short ns; // number samples - unsigned char id; // region id - unsigned char nei; // neighbour id -}; - -/// @par -/// -/// See the #rcConfig documentation for more information on the configuration parameters. -/// -/// @see rcAllocHeightfieldLayerSet, rcCompactHeightfield, rcHeightfieldLayerSet, rcConfig -bool rcBuildHeightfieldLayers(rcContext* ctx, rcCompactHeightfield& chf, - const int borderSize, const int walkableHeight, - rcHeightfieldLayerSet& lset) -{ - rcAssert(ctx); - - ctx->startTimer(RC_TIMER_BUILD_LAYERS); - - const int w = chf.width; - const int h = chf.height; - - rcScopedDelete srcReg = (unsigned char*)rcAlloc(sizeof(unsigned char)*chf.spanCount, RC_ALLOC_TEMP); - if (!srcReg) - { - ctx->log(RC_LOG_ERROR, "rcBuildHeightfieldLayers: Out of memory 'srcReg' (%d).", chf.spanCount); - return false; - } - memset(srcReg,0xff,sizeof(unsigned char)*chf.spanCount); - - const int nsweeps = chf.width; - rcScopedDelete sweeps = (rcLayerSweepSpan*)rcAlloc(sizeof(rcLayerSweepSpan)*nsweeps, RC_ALLOC_TEMP); - if (!sweeps) - { - ctx->log(RC_LOG_ERROR, "rcBuildHeightfieldLayers: Out of memory 'sweeps' (%d).", nsweeps); - return false; - } - - - // Partition walkable area into monotone regions. - int prevCount[256]; - unsigned char regId = 0; - - for (int y = borderSize; y < h-borderSize; ++y) - { - memset(prevCount,0,sizeof(int)*regId); - unsigned char sweepId = 0; - - for (int x = borderSize; x < w-borderSize; ++x) - { - const rcCompactCell& c = chf.cells[x+y*w]; - - for (int i = (int)c.index, ni = (int)(c.index+c.count); i < ni; ++i) - { - const rcCompactSpan& s = chf.spans[i]; - if (chf.areas[i] == RC_NULL_AREA) continue; - - unsigned char sid = 0xff; - - // -x - if (rcGetCon(s, 0) != RC_NOT_CONNECTED) - { - const int ax = x + rcGetDirOffsetX(0); - const int ay = y + rcGetDirOffsetY(0); - const int ai = (int)chf.cells[ax+ay*w].index + rcGetCon(s, 0); - if (chf.areas[ai] != RC_NULL_AREA && srcReg[ai] != 0xff) - sid = srcReg[ai]; - } - - if (sid == 0xff) - { - sid = sweepId++; - sweeps[sid].nei = 0xff; - sweeps[sid].ns = 0; - } - - // -y - if (rcGetCon(s,3) != RC_NOT_CONNECTED) - { - const int ax = x + rcGetDirOffsetX(3); - const int ay = y + rcGetDirOffsetY(3); - const int ai = (int)chf.cells[ax+ay*w].index + rcGetCon(s, 3); - const unsigned char nr = srcReg[ai]; - if (nr != 0xff) - { - // Set neighbour when first valid neighbour is encoutered. - if (sweeps[sid].ns == 0) - sweeps[sid].nei = nr; - - if (sweeps[sid].nei == nr) - { - // Update existing neighbour - sweeps[sid].ns++; - prevCount[nr]++; - } - else - { - // This is hit if there is nore than one neighbour. - // Invalidate the neighbour. - sweeps[sid].nei = 0xff; - } - } - } - - srcReg[i] = sid; - } - } - - // Create unique ID. - for (int i = 0; i < sweepId; ++i) - { - // If the neighbour is set and there is only one continuous connection to it, - // the sweep will be merged with the previous one, else new region is created. - if (sweeps[i].nei != 0xff && prevCount[sweeps[i].nei] == (int)sweeps[i].ns) - { - sweeps[i].id = sweeps[i].nei; - } - else - { - if (regId == 255) - { - ctx->log(RC_LOG_ERROR, "rcBuildHeightfieldLayers: Region ID overflow."); - return false; - } - sweeps[i].id = regId++; - } - } - - // Remap local sweep ids to region ids. - for (int x = borderSize; x < w-borderSize; ++x) - { - const rcCompactCell& c = chf.cells[x+y*w]; - for (int i = (int)c.index, ni = (int)(c.index+c.count); i < ni; ++i) - { - if (srcReg[i] != 0xff) - srcReg[i] = sweeps[srcReg[i]].id; - } - } - } - - // Allocate and init layer regions. - const int nregs = (int)regId; - rcScopedDelete regs = (rcLayerRegion*)rcAlloc(sizeof(rcLayerRegion)*nregs, RC_ALLOC_TEMP); - if (!regs) - { - ctx->log(RC_LOG_ERROR, "rcBuildHeightfieldLayers: Out of memory 'regs' (%d).", nregs); - return false; - } - memset(regs, 0, sizeof(rcLayerRegion)*nregs); - for (int i = 0; i < nregs; ++i) - { - regs[i].layerId = 0xff; - regs[i].ymin = 0xffff; - regs[i].ymax = 0; - } - - // Find region neighbours and overlapping regions. - for (int y = 0; y < h; ++y) - { - for (int x = 0; x < w; ++x) - { - const rcCompactCell& c = chf.cells[x+y*w]; - - unsigned char lregs[RC_MAX_LAYERS]; - int nlregs = 0; - - for (int i = (int)c.index, ni = (int)(c.index+c.count); i < ni; ++i) - { - const rcCompactSpan& s = chf.spans[i]; - const unsigned char ri = srcReg[i]; - if (ri == 0xff) continue; - - regs[ri].ymin = rcMin(regs[ri].ymin, s.y); - regs[ri].ymax = rcMax(regs[ri].ymax, s.y); - - // Collect all region layers. - if (nlregs < RC_MAX_LAYERS) - lregs[nlregs++] = ri; - - // Update neighbours - for (int dir = 0; dir < 4; ++dir) - { - if (rcGetCon(s, dir) != RC_NOT_CONNECTED) - { - const int ax = x + rcGetDirOffsetX(dir); - const int ay = y + rcGetDirOffsetY(dir); - const int ai = (int)chf.cells[ax+ay*w].index + rcGetCon(s, dir); - const unsigned char rai = srcReg[ai]; - if (rai != 0xff && rai != ri) - addUnique(regs[ri].neis, regs[ri].nneis, rai); - } - } - - } - - // Update overlapping regions. - for (int i = 0; i < nlregs-1; ++i) - { - for (int j = i+1; j < nlregs; ++j) - { - if (lregs[i] != lregs[j]) - { - rcLayerRegion& ri = regs[lregs[i]]; - rcLayerRegion& rj = regs[lregs[j]]; - addUnique(ri.layers, ri.nlayers, lregs[j]); - addUnique(rj.layers, rj.nlayers, lregs[i]); - } - } - } - - } - } - - // Create 2D layers from regions. - unsigned char layerId = 0; - - static const int MAX_STACK = 64; - unsigned char stack[MAX_STACK]; - int nstack = 0; - - for (int i = 0; i < nregs; ++i) - { - rcLayerRegion& root = regs[i]; - // Skip alreadu visited. - if (root.layerId != 0xff) - continue; - - // Start search. - root.layerId = layerId; - root.base = 1; - - nstack = 0; - stack[nstack++] = (unsigned char)i; - - while (nstack) - { - // Pop front - rcLayerRegion& reg = regs[stack[0]]; - nstack--; - for (int j = 0; j < nstack; ++j) - stack[j] = stack[j+1]; - - const int nneis = (int)reg.nneis; - for (int j = 0; j < nneis; ++j) - { - const unsigned char nei = reg.neis[j]; - rcLayerRegion& regn = regs[nei]; - // Skip already visited. - if (regn.layerId != 0xff) - continue; - // Skip if the neighbour is overlapping root region. - if (contains(root.layers, root.nlayers, nei)) - continue; - // Skip if the height range would become too large. - const int ymin = rcMin(root.ymin, regn.ymin); - const int ymax = rcMax(root.ymax, regn.ymax); - if ((ymax - ymin) >= 255) - continue; - - if (nstack < MAX_STACK) - { - // Deepen - stack[nstack++] = (unsigned char)nei; - - // Mark layer id - regn.layerId = layerId; - // Merge current layers to root. - for (int k = 0; k < regn.nlayers; ++k) - addUnique(root.layers, root.nlayers, regn.layers[k]); - root.ymin = rcMin(root.ymin, regn.ymin); - root.ymax = rcMax(root.ymax, regn.ymax); - } - } - } - - layerId++; - } - - // Merge non-overlapping regions that are close in height. - const unsigned short mergeHeight = (unsigned short)walkableHeight * 4; - - for (int i = 0; i < nregs; ++i) - { - rcLayerRegion& ri = regs[i]; - if (!ri.base) continue; - - unsigned char newId = ri.layerId; - - for (;;) - { - unsigned char oldId = 0xff; - - for (int j = 0; j < nregs; ++j) - { - if (i == j) continue; - rcLayerRegion& rj = regs[j]; - if (!rj.base) continue; - - // Skip if teh regions are not close to each other. - if (!overlapRange(ri.ymin,ri.ymax+mergeHeight, rj.ymin,rj.ymax+mergeHeight)) - continue; - // Skip if the height range would become too large. - const int ymin = rcMin(ri.ymin, rj.ymin); - const int ymax = rcMax(ri.ymax, rj.ymax); - if ((ymax - ymin) >= 255) - continue; - - // Make sure that there is no overlap when mergin 'ri' and 'rj'. - bool overlap = false; - // Iterate over all regions which have the same layerId as 'rj' - for (int k = 0; k < nregs; ++k) - { - if (regs[k].layerId != rj.layerId) - continue; - // Check if region 'k' is overlapping region 'ri' - // Index to 'regs' is the same as region id. - if (contains(ri.layers,ri.nlayers, (unsigned char)k)) - { - overlap = true; - break; - } - } - // Cannot merge of regions overlap. - if (overlap) - continue; - - // Can merge i and j. - oldId = rj.layerId; - break; - } - - // Could not find anything to merge with, stop. - if (oldId == 0xff) - break; - - // Merge - for (int j = 0; j < nregs; ++j) - { - rcLayerRegion& rj = regs[j]; - if (rj.layerId == oldId) - { - rj.base = 0; - // Remap layerIds. - rj.layerId = newId; - // Add overlaid layers from 'rj' to 'ri'. - for (int k = 0; k < rj.nlayers; ++k) - addUnique(ri.layers, ri.nlayers, rj.layers[k]); - // Update heigh bounds. - ri.ymin = rcMin(ri.ymin, rj.ymin); - ri.ymax = rcMax(ri.ymax, rj.ymax); - } - } - } - } - - // Compact layerIds - unsigned char remap[256]; - memset(remap, 0, 256); - - // Find number of unique layers. - layerId = 0; - for (int i = 0; i < nregs; ++i) - remap[regs[i].layerId] = 1; - for (int i = 0; i < 256; ++i) - { - if (remap[i]) - remap[i] = layerId++; - else - remap[i] = 0xff; - } - // Remap ids. - for (int i = 0; i < nregs; ++i) - regs[i].layerId = remap[regs[i].layerId]; - - // No layers, return empty. - if (layerId == 0) - { - ctx->stopTimer(RC_TIMER_BUILD_LAYERS); - return true; - } - - // Create layers. - rcAssert(lset.layers == 0); - - const int lw = w - borderSize*2; - const int lh = h - borderSize*2; - - // Build contracted bbox for layers. - float bmin[3], bmax[3]; - rcVcopy(bmin, chf.bmin); - rcVcopy(bmax, chf.bmax); - bmin[0] += borderSize*chf.cs; - bmin[2] += borderSize*chf.cs; - bmax[0] -= borderSize*chf.cs; - bmax[2] -= borderSize*chf.cs; - - lset.nlayers = (int)layerId; - - lset.layers = (rcHeightfieldLayer*)rcAlloc(sizeof(rcHeightfieldLayer)*lset.nlayers, RC_ALLOC_PERM); - if (!lset.layers) - { - ctx->log(RC_LOG_ERROR, "rcBuildHeightfieldLayers: Out of memory 'layers' (%d).", lset.nlayers); - return false; - } - memset(lset.layers, 0, sizeof(rcHeightfieldLayer)*lset.nlayers); - - - // Store layers. - for (int i = 0; i < lset.nlayers; ++i) - { - unsigned char curId = (unsigned char)i; - - // Allocate memory for the current layer. - rcHeightfieldLayer* layer = &lset.layers[i]; - memset(layer, 0, sizeof(rcHeightfieldLayer)); - - const int gridSize = sizeof(unsigned char)*lw*lh; - - layer->heights = (unsigned char*)rcAlloc(gridSize, RC_ALLOC_PERM); - if (!layer->heights) - { - ctx->log(RC_LOG_ERROR, "rcBuildHeightfieldLayers: Out of memory 'heights' (%d).", gridSize); - return false; - } - memset(layer->heights, 0xff, gridSize); - - layer->areas = (unsigned char*)rcAlloc(gridSize, RC_ALLOC_PERM); - if (!layer->areas) - { - ctx->log(RC_LOG_ERROR, "rcBuildHeightfieldLayers: Out of memory 'areas' (%d).", gridSize); - return false; - } - memset(layer->areas, 0, gridSize); - - layer->cons = (unsigned char*)rcAlloc(gridSize, RC_ALLOC_PERM); - if (!layer->cons) - { - ctx->log(RC_LOG_ERROR, "rcBuildHeightfieldLayers: Out of memory 'cons' (%d).", gridSize); - return false; - } - memset(layer->cons, 0, gridSize); - - // Find layer height bounds. - int hmin = 0, hmax = 0; - for (int j = 0; j < nregs; ++j) - { - if (regs[j].base && regs[j].layerId == curId) - { - hmin = (int)regs[j].ymin; - hmax = (int)regs[j].ymax; - } - } - - layer->width = lw; - layer->height = lh; - layer->cs = chf.cs; - layer->ch = chf.ch; - - // Adjust the bbox to fit the heighfield. - rcVcopy(layer->bmin, bmin); - rcVcopy(layer->bmax, bmax); - layer->bmin[1] = bmin[1] + hmin*chf.ch; - layer->bmax[1] = bmin[1] + hmax*chf.ch; - layer->hmin = hmin; - layer->hmax = hmax; - - // Update usable data region. - layer->minx = layer->width; - layer->maxx = 0; - layer->miny = layer->height; - layer->maxy = 0; - - // Copy height and area from compact heighfield. - for (int y = 0; y < lh; ++y) - { - for (int x = 0; x < lw; ++x) - { - const int cx = borderSize+x; - const int cy = borderSize+y; - const rcCompactCell& c = chf.cells[cx+cy*w]; - for (int j = (int)c.index, nj = (int)(c.index+c.count); j < nj; ++j) - { - const rcCompactSpan& s = chf.spans[j]; - // Skip unassigned regions. - if (srcReg[j] == 0xff) - continue; - // Skip of does nto belong to current layer. - unsigned char lid = regs[srcReg[j]].layerId; - if (lid != curId) - continue; - - // Update data bounds. - layer->minx = rcMin(layer->minx, x); - layer->maxx = rcMax(layer->maxx, x); - layer->miny = rcMin(layer->miny, y); - layer->maxy = rcMax(layer->maxy, y); - - // Store height and area type. - const int idx = x+y*lw; - layer->heights[idx] = (unsigned char)(s.y - hmin); - layer->areas[idx] = chf.areas[j]; - - // Check connection. - unsigned char portal = 0; - unsigned char con = 0; - for (int dir = 0; dir < 4; ++dir) - { - if (rcGetCon(s, dir) != RC_NOT_CONNECTED) - { - const int ax = cx + rcGetDirOffsetX(dir); - const int ay = cy + rcGetDirOffsetY(dir); - const int ai = (int)chf.cells[ax+ay*w].index + rcGetCon(s, dir); - unsigned char alid = srcReg[ai] != 0xff ? regs[srcReg[ai]].layerId : 0xff; - // Portal mask - if (chf.areas[ai] != RC_NULL_AREA && lid != alid) - { - portal |= (unsigned char)(1< hmin) - layer->heights[idx] = rcMax(layer->heights[idx], (unsigned char)(as.y - hmin)); - } - // Valid connection mask - if (chf.areas[ai] != RC_NULL_AREA && lid == alid) - { - const int nx = ax - borderSize; - const int ny = ay - borderSize; - if (nx >= 0 && ny >= 0 && nx < lw && ny < lh) - con |= (unsigned char)(1<cons[idx] = (portal << 4) | con; - } - } - } - - if (layer->minx > layer->maxx) - layer->minx = layer->maxx = 0; - if (layer->miny > layer->maxy) - layer->miny = layer->maxy = 0; - } - - ctx->stopTimer(RC_TIMER_BUILD_LAYERS); - - return true; -} diff --git a/KREngine/3rdparty/recast/source/RecastMesh.cpp b/KREngine/3rdparty/recast/source/RecastMesh.cpp deleted file mode 100755 index 534a72e..0000000 --- a/KREngine/3rdparty/recast/source/RecastMesh.cpp +++ /dev/null @@ -1,1433 +0,0 @@ -// -// Copyright (c) 2009-2010 Mikko Mononen memon@inside.org -// -// This software is provided 'as-is', without any express or implied -// warranty. In no event will the authors be held liable for any damages -// arising from the use of this software. -// Permission is granted to anyone to use this software for any purpose, -// including commercial applications, and to alter it and redistribute it -// freely, subject to the following restrictions: -// 1. The origin of this software must not be misrepresented; you must not -// claim that you wrote the original software. If you use this software -// in a product, an acknowledgment in the product documentation would be -// appreciated but is not required. -// 2. Altered source versions must be plainly marked as such, and must not be -// misrepresented as being the original software. -// 3. This notice may not be removed or altered from any source distribution. -// - -#define _USE_MATH_DEFINES -#include -#include -#include -#include "Recast.h" -#include "RecastAlloc.h" -#include "RecastAssert.h" - -struct rcEdge -{ - unsigned short vert[2]; - unsigned short polyEdge[2]; - unsigned short poly[2]; -}; - -static bool buildMeshAdjacency(unsigned short* polys, const int npolys, - const int nverts, const int vertsPerPoly) -{ - // Based on code by Eric Lengyel from: - // http://www.terathon.com/code/edges.php - - int maxEdgeCount = npolys*vertsPerPoly; - unsigned short* firstEdge = (unsigned short*)rcAlloc(sizeof(unsigned short)*(nverts + maxEdgeCount), RC_ALLOC_TEMP); - if (!firstEdge) - return false; - unsigned short* nextEdge = firstEdge + nverts; - int edgeCount = 0; - - rcEdge* edges = (rcEdge*)rcAlloc(sizeof(rcEdge)*maxEdgeCount, RC_ALLOC_TEMP); - if (!edges) - { - rcFree(firstEdge); - return false; - } - - for (int i = 0; i < nverts; i++) - firstEdge[i] = RC_MESH_NULL_IDX; - - for (int i = 0; i < npolys; ++i) - { - unsigned short* t = &polys[i*vertsPerPoly*2]; - for (int j = 0; j < vertsPerPoly; ++j) - { - if (t[j] == RC_MESH_NULL_IDX) break; - unsigned short v0 = t[j]; - unsigned short v1 = (j+1 >= vertsPerPoly || t[j+1] == RC_MESH_NULL_IDX) ? t[0] : t[j+1]; - if (v0 < v1) - { - rcEdge& edge = edges[edgeCount]; - edge.vert[0] = v0; - edge.vert[1] = v1; - edge.poly[0] = (unsigned short)i; - edge.polyEdge[0] = (unsigned short)j; - edge.poly[1] = (unsigned short)i; - edge.polyEdge[1] = 0; - // Insert edge - nextEdge[edgeCount] = firstEdge[v0]; - firstEdge[v0] = (unsigned short)edgeCount; - edgeCount++; - } - } - } - - for (int i = 0; i < npolys; ++i) - { - unsigned short* t = &polys[i*vertsPerPoly*2]; - for (int j = 0; j < vertsPerPoly; ++j) - { - if (t[j] == RC_MESH_NULL_IDX) break; - unsigned short v0 = t[j]; - unsigned short v1 = (j+1 >= vertsPerPoly || t[j+1] == RC_MESH_NULL_IDX) ? t[0] : t[j+1]; - if (v0 > v1) - { - for (unsigned short e = firstEdge[v1]; e != RC_MESH_NULL_IDX; e = nextEdge[e]) - { - rcEdge& edge = edges[e]; - if (edge.vert[1] == v0 && edge.poly[0] == edge.poly[1]) - { - edge.poly[1] = (unsigned short)i; - edge.polyEdge[1] = (unsigned short)j; - break; - } - } - } - } - } - - // Store adjacency - for (int i = 0; i < edgeCount; ++i) - { - const rcEdge& e = edges[i]; - if (e.poly[0] != e.poly[1]) - { - unsigned short* p0 = &polys[e.poly[0]*vertsPerPoly*2]; - unsigned short* p1 = &polys[e.poly[1]*vertsPerPoly*2]; - p0[vertsPerPoly + e.polyEdge[0]] = e.poly[1]; - p1[vertsPerPoly + e.polyEdge[1]] = e.poly[0]; - } - } - - rcFree(firstEdge); - rcFree(edges); - - return true; -} - - -static const int VERTEX_BUCKET_COUNT = (1<<12); - -inline int computeVertexHash(int x, int y, int z) -{ - const unsigned int h1 = 0x8da6b343; // Large multiplicative constants; - const unsigned int h2 = 0xd8163841; // here arbitrarily chosen primes - const unsigned int h3 = 0xcb1ab31f; - unsigned int n = h1 * x + h2 * y + h3 * z; - return (int)(n & (VERTEX_BUCKET_COUNT-1)); -} - -static unsigned short addVertex(unsigned short x, unsigned short y, unsigned short z, - unsigned short* verts, int* firstVert, int* nextVert, int& nv) -{ - int bucket = computeVertexHash(x, 0, z); - int i = firstVert[bucket]; - - while (i != -1) - { - const unsigned short* v = &verts[i*3]; - if (v[0] == x && (rcAbs(v[1] - y) <= 2) && v[2] == z) - return (unsigned short)i; - i = nextVert[i]; // next - } - - // Could not find, create new. - i = nv; nv++; - unsigned short* v = &verts[i*3]; - v[0] = x; - v[1] = y; - v[2] = z; - nextVert[i] = firstVert[bucket]; - firstVert[bucket] = i; - - return (unsigned short)i; -} - -inline int prev(int i, int n) { return i-1 >= 0 ? i-1 : n-1; } -inline int next(int i, int n) { return i+1 < n ? i+1 : 0; } - -inline int area2(const int* a, const int* b, const int* c) -{ - return (b[0] - a[0]) * (c[2] - a[2]) - (c[0] - a[0]) * (b[2] - a[2]); -} - -// Exclusive or: true iff exactly one argument is true. -// The arguments are negated to ensure that they are 0/1 -// values. Then the bitwise Xor operator may apply. -// (This idea is due to Michael Baldwin.) -inline bool xorb(bool x, bool y) -{ - return !x ^ !y; -} - -// Returns true iff c is strictly to the left of the directed -// line through a to b. -inline bool left(const int* a, const int* b, const int* c) -{ - return area2(a, b, c) < 0; -} - -inline bool leftOn(const int* a, const int* b, const int* c) -{ - return area2(a, b, c) <= 0; -} - -inline bool collinear(const int* a, const int* b, const int* c) -{ - return area2(a, b, c) == 0; -} - -// Returns true iff ab properly intersects cd: they share -// a point interior to both segments. The properness of the -// intersection is ensured by using strict leftness. -static bool intersectProp(const int* a, const int* b, const int* c, const int* d) -{ - // Eliminate improper cases. - if (collinear(a,b,c) || collinear(a,b,d) || - collinear(c,d,a) || collinear(c,d,b)) - return false; - - return xorb(left(a,b,c), left(a,b,d)) && xorb(left(c,d,a), left(c,d,b)); -} - -// Returns T iff (a,b,c) are collinear and point c lies -// on the closed segement ab. -static bool between(const int* a, const int* b, const int* c) -{ - if (!collinear(a, b, c)) - return false; - // If ab not vertical, check betweenness on x; else on y. - if (a[0] != b[0]) - return ((a[0] <= c[0]) && (c[0] <= b[0])) || ((a[0] >= c[0]) && (c[0] >= b[0])); - else - return ((a[2] <= c[2]) && (c[2] <= b[2])) || ((a[2] >= c[2]) && (c[2] >= b[2])); -} - -// Returns true iff segments ab and cd intersect, properly or improperly. -static bool intersect(const int* a, const int* b, const int* c, const int* d) -{ - if (intersectProp(a, b, c, d)) - return true; - else if (between(a, b, c) || between(a, b, d) || - between(c, d, a) || between(c, d, b)) - return true; - else - return false; -} - -static bool vequal(const int* a, const int* b) -{ - return a[0] == b[0] && a[2] == b[2]; -} - -// Returns T iff (v_i, v_j) is a proper internal *or* external -// diagonal of P, *ignoring edges incident to v_i and v_j*. -static bool diagonalie(int i, int j, int n, const int* verts, int* indices) -{ - const int* d0 = &verts[(indices[i] & 0x0fffffff) * 4]; - const int* d1 = &verts[(indices[j] & 0x0fffffff) * 4]; - - // For each edge (k,k+1) of P - for (int k = 0; k < n; k++) - { - int k1 = next(k, n); - // Skip edges incident to i or j - if (!((k == i) || (k1 == i) || (k == j) || (k1 == j))) - { - const int* p0 = &verts[(indices[k] & 0x0fffffff) * 4]; - const int* p1 = &verts[(indices[k1] & 0x0fffffff) * 4]; - - if (vequal(d0, p0) || vequal(d1, p0) || vequal(d0, p1) || vequal(d1, p1)) - continue; - - if (intersect(d0, d1, p0, p1)) - return false; - } - } - return true; -} - -// Returns true iff the diagonal (i,j) is strictly internal to the -// polygon P in the neighborhood of the i endpoint. -static bool inCone(int i, int j, int n, const int* verts, int* indices) -{ - const int* pi = &verts[(indices[i] & 0x0fffffff) * 4]; - const int* pj = &verts[(indices[j] & 0x0fffffff) * 4]; - const int* pi1 = &verts[(indices[next(i, n)] & 0x0fffffff) * 4]; - const int* pin1 = &verts[(indices[prev(i, n)] & 0x0fffffff) * 4]; - - // If P[i] is a convex vertex [ i+1 left or on (i-1,i) ]. - if (leftOn(pin1, pi, pi1)) - return left(pi, pj, pin1) && left(pj, pi, pi1); - // Assume (i-1,i,i+1) not collinear. - // else P[i] is reflex. - return !(leftOn(pi, pj, pi1) && leftOn(pj, pi, pin1)); -} - -// Returns T iff (v_i, v_j) is a proper internal -// diagonal of P. -static bool diagonal(int i, int j, int n, const int* verts, int* indices) -{ - return inCone(i, j, n, verts, indices) && diagonalie(i, j, n, verts, indices); -} - -static int triangulate(int n, const int* verts, int* indices, int* tris) -{ - int ntris = 0; - int* dst = tris; - - // The last bit of the index is used to indicate if the vertex can be removed. - for (int i = 0; i < n; i++) - { - int i1 = next(i, n); - int i2 = next(i1, n); - if (diagonal(i, i2, n, verts, indices)) - indices[i1] |= 0x80000000; - } - - while (n > 3) - { - int minLen = -1; - int mini = -1; - for (int i = 0; i < n; i++) - { - int i1 = next(i, n); - if (indices[i1] & 0x80000000) - { - const int* p0 = &verts[(indices[i] & 0x0fffffff) * 4]; - const int* p2 = &verts[(indices[next(i1, n)] & 0x0fffffff) * 4]; - - int dx = p2[0] - p0[0]; - int dy = p2[2] - p0[2]; - int len = dx*dx + dy*dy; - - if (minLen < 0 || len < minLen) - { - minLen = len; - mini = i; - } - } - } - - if (mini == -1) - { - // Should not happen. -/* printf("mini == -1 ntris=%d n=%d\n", ntris, n); - for (int i = 0; i < n; i++) - { - printf("%d ", indices[i] & 0x0fffffff); - } - printf("\n");*/ - return -ntris; - } - - int i = mini; - int i1 = next(i, n); - int i2 = next(i1, n); - - *dst++ = indices[i] & 0x0fffffff; - *dst++ = indices[i1] & 0x0fffffff; - *dst++ = indices[i2] & 0x0fffffff; - ntris++; - - // Removes P[i1] by copying P[i+1]...P[n-1] left one index. - n--; - for (int k = i1; k < n; k++) - indices[k] = indices[k+1]; - - if (i1 >= n) i1 = 0; - i = prev(i1,n); - // Update diagonal flags. - if (diagonal(prev(i, n), i1, n, verts, indices)) - indices[i] |= 0x80000000; - else - indices[i] &= 0x0fffffff; - - if (diagonal(i, next(i1, n), n, verts, indices)) - indices[i1] |= 0x80000000; - else - indices[i1] &= 0x0fffffff; - } - - // Append the remaining triangle. - *dst++ = indices[0] & 0x0fffffff; - *dst++ = indices[1] & 0x0fffffff; - *dst++ = indices[2] & 0x0fffffff; - ntris++; - - return ntris; -} - -static int countPolyVerts(const unsigned short* p, const int nvp) -{ - for (int i = 0; i < nvp; ++i) - if (p[i] == RC_MESH_NULL_IDX) - return i; - return nvp; -} - -inline bool uleft(const unsigned short* a, const unsigned short* b, const unsigned short* c) -{ - return ((int)b[0] - (int)a[0]) * ((int)c[2] - (int)a[2]) - - ((int)c[0] - (int)a[0]) * ((int)b[2] - (int)a[2]) < 0; -} - -static int getPolyMergeValue(unsigned short* pa, unsigned short* pb, - const unsigned short* verts, int& ea, int& eb, - const int nvp) -{ - const int na = countPolyVerts(pa, nvp); - const int nb = countPolyVerts(pb, nvp); - - // If the merged polygon would be too big, do not merge. - if (na+nb-2 > nvp) - return -1; - - // Check if the polygons share an edge. - ea = -1; - eb = -1; - - for (int i = 0; i < na; ++i) - { - unsigned short va0 = pa[i]; - unsigned short va1 = pa[(i+1) % na]; - if (va0 > va1) - rcSwap(va0, va1); - for (int j = 0; j < nb; ++j) - { - unsigned short vb0 = pb[j]; - unsigned short vb1 = pb[(j+1) % nb]; - if (vb0 > vb1) - rcSwap(vb0, vb1); - if (va0 == vb0 && va1 == vb1) - { - ea = i; - eb = j; - break; - } - } - } - - // No common edge, cannot merge. - if (ea == -1 || eb == -1) - return -1; - - // Check to see if the merged polygon would be convex. - unsigned short va, vb, vc; - - va = pa[(ea+na-1) % na]; - vb = pa[ea]; - vc = pb[(eb+2) % nb]; - if (!uleft(&verts[va*3], &verts[vb*3], &verts[vc*3])) - return -1; - - va = pb[(eb+nb-1) % nb]; - vb = pb[eb]; - vc = pa[(ea+2) % na]; - if (!uleft(&verts[va*3], &verts[vb*3], &verts[vc*3])) - return -1; - - va = pa[ea]; - vb = pa[(ea+1)%na]; - - int dx = (int)verts[va*3+0] - (int)verts[vb*3+0]; - int dy = (int)verts[va*3+2] - (int)verts[vb*3+2]; - - return dx*dx + dy*dy; -} - -static void mergePolys(unsigned short* pa, unsigned short* pb, int ea, int eb, - unsigned short* tmp, const int nvp) -{ - const int na = countPolyVerts(pa, nvp); - const int nb = countPolyVerts(pb, nvp); - - // Merge polygons. - memset(tmp, 0xff, sizeof(unsigned short)*nvp); - int n = 0; - // Add pa - for (int i = 0; i < na-1; ++i) - tmp[n++] = pa[(ea+1+i) % na]; - // Add pb - for (int i = 0; i < nb-1; ++i) - tmp[n++] = pb[(eb+1+i) % nb]; - - memcpy(pa, tmp, sizeof(unsigned short)*nvp); -} - - -static void pushFront(int v, int* arr, int& an) -{ - an++; - for (int i = an-1; i > 0; --i) arr[i] = arr[i-1]; - arr[0] = v; -} - -static void pushBack(int v, int* arr, int& an) -{ - arr[an] = v; - an++; -} - -static bool canRemoveVertex(rcContext* ctx, rcPolyMesh& mesh, const unsigned short rem) -{ - const int nvp = mesh.nvp; - - // Count number of polygons to remove. - int numRemovedVerts = 0; - int numTouchedVerts = 0; - int numRemainingEdges = 0; - for (int i = 0; i < mesh.npolys; ++i) - { - unsigned short* p = &mesh.polys[i*nvp*2]; - const int nv = countPolyVerts(p, nvp); - int numRemoved = 0; - int numVerts = 0; - for (int j = 0; j < nv; ++j) - { - if (p[j] == rem) - { - numTouchedVerts++; - numRemoved++; - } - numVerts++; - } - if (numRemoved) - { - numRemovedVerts += numRemoved; - numRemainingEdges += numVerts-(numRemoved+1); - } - } - - // There would be too few edges remaining to create a polygon. - // This can happen for example when a tip of a triangle is marked - // as deletion, but there are no other polys that share the vertex. - // In this case, the vertex should not be removed. - if (numRemainingEdges <= 2) - return false; - - // Find edges which share the removed vertex. - const int maxEdges = numTouchedVerts*2; - int nedges = 0; - rcScopedDelete edges = (int*)rcAlloc(sizeof(int)*maxEdges*3, RC_ALLOC_TEMP); - if (!edges) - { - ctx->log(RC_LOG_WARNING, "canRemoveVertex: Out of memory 'edges' (%d).", maxEdges*3); - return false; - } - - for (int i = 0; i < mesh.npolys; ++i) - { - unsigned short* p = &mesh.polys[i*nvp*2]; - const int nv = countPolyVerts(p, nvp); - - // Collect edges which touches the removed vertex. - for (int j = 0, k = nv-1; j < nv; k = j++) - { - if (p[j] == rem || p[k] == rem) - { - // Arrange edge so that a=rem. - int a = p[j], b = p[k]; - if (b == rem) - rcSwap(a,b); - - // Check if the edge exists - bool exists = false; - for (int m = 0; m < nedges; ++m) - { - int* e = &edges[m*3]; - if (e[1] == b) - { - // Exists, increment vertex share count. - e[2]++; - exists = true; - } - } - // Add new edge. - if (!exists) - { - int* e = &edges[nedges*3]; - e[0] = a; - e[1] = b; - e[2] = 1; - nedges++; - } - } - } - } - - // There should be no more than 2 open edges. - // This catches the case that two non-adjacent polygons - // share the removed vertex. In that case, do not remove the vertex. - int numOpenEdges = 0; - for (int i = 0; i < nedges; ++i) - { - if (edges[i*3+2] < 2) - numOpenEdges++; - } - if (numOpenEdges > 2) - return false; - - return true; -} - -static bool removeVertex(rcContext* ctx, rcPolyMesh& mesh, const unsigned short rem, const int maxTris) -{ - const int nvp = mesh.nvp; - - // Count number of polygons to remove. - int numRemovedVerts = 0; - for (int i = 0; i < mesh.npolys; ++i) - { - unsigned short* p = &mesh.polys[i*nvp*2]; - const int nv = countPolyVerts(p, nvp); - for (int j = 0; j < nv; ++j) - { - if (p[j] == rem) - numRemovedVerts++; - } - } - - int nedges = 0; - rcScopedDelete edges = (int*)rcAlloc(sizeof(int)*numRemovedVerts*nvp*4, RC_ALLOC_TEMP); - if (!edges) - { - ctx->log(RC_LOG_WARNING, "removeVertex: Out of memory 'edges' (%d).", numRemovedVerts*nvp*4); - return false; - } - - int nhole = 0; - rcScopedDelete hole = (int*)rcAlloc(sizeof(int)*numRemovedVerts*nvp, RC_ALLOC_TEMP); - if (!hole) - { - ctx->log(RC_LOG_WARNING, "removeVertex: Out of memory 'hole' (%d).", numRemovedVerts*nvp); - return false; - } - - int nhreg = 0; - rcScopedDelete hreg = (int*)rcAlloc(sizeof(int)*numRemovedVerts*nvp, RC_ALLOC_TEMP); - if (!hreg) - { - ctx->log(RC_LOG_WARNING, "removeVertex: Out of memory 'hreg' (%d).", numRemovedVerts*nvp); - return false; - } - - int nharea = 0; - rcScopedDelete harea = (int*)rcAlloc(sizeof(int)*numRemovedVerts*nvp, RC_ALLOC_TEMP); - if (!harea) - { - ctx->log(RC_LOG_WARNING, "removeVertex: Out of memory 'harea' (%d).", numRemovedVerts*nvp); - return false; - } - - for (int i = 0; i < mesh.npolys; ++i) - { - unsigned short* p = &mesh.polys[i*nvp*2]; - const int nv = countPolyVerts(p, nvp); - bool hasRem = false; - for (int j = 0; j < nv; ++j) - if (p[j] == rem) hasRem = true; - if (hasRem) - { - // Collect edges which does not touch the removed vertex. - for (int j = 0, k = nv-1; j < nv; k = j++) - { - if (p[j] != rem && p[k] != rem) - { - int* e = &edges[nedges*4]; - e[0] = p[k]; - e[1] = p[j]; - e[2] = mesh.regs[i]; - e[3] = mesh.areas[i]; - nedges++; - } - } - // Remove the polygon. - unsigned short* p2 = &mesh.polys[(mesh.npolys-1)*nvp*2]; - if (p != p2) - memcpy(p,p2,sizeof(unsigned short)*nvp); - memset(p+nvp,0xff,sizeof(unsigned short)*nvp); - mesh.regs[i] = mesh.regs[mesh.npolys-1]; - mesh.areas[i] = mesh.areas[mesh.npolys-1]; - mesh.npolys--; - --i; - } - } - - // Remove vertex. - for (int i = (int)rem; i < mesh.nverts; ++i) - { - mesh.verts[i*3+0] = mesh.verts[(i+1)*3+0]; - mesh.verts[i*3+1] = mesh.verts[(i+1)*3+1]; - mesh.verts[i*3+2] = mesh.verts[(i+1)*3+2]; - } - mesh.nverts--; - - // Adjust indices to match the removed vertex layout. - for (int i = 0; i < mesh.npolys; ++i) - { - unsigned short* p = &mesh.polys[i*nvp*2]; - const int nv = countPolyVerts(p, nvp); - for (int j = 0; j < nv; ++j) - if (p[j] > rem) p[j]--; - } - for (int i = 0; i < nedges; ++i) - { - if (edges[i*4+0] > rem) edges[i*4+0]--; - if (edges[i*4+1] > rem) edges[i*4+1]--; - } - - if (nedges == 0) - return true; - - // Start with one vertex, keep appending connected - // segments to the start and end of the hole. - pushBack(edges[0], hole, nhole); - pushBack(edges[2], hreg, nhreg); - pushBack(edges[3], harea, nharea); - - while (nedges) - { - bool match = false; - - for (int i = 0; i < nedges; ++i) - { - const int ea = edges[i*4+0]; - const int eb = edges[i*4+1]; - const int r = edges[i*4+2]; - const int a = edges[i*4+3]; - bool add = false; - if (hole[0] == eb) - { - // The segment matches the beginning of the hole boundary. - pushFront(ea, hole, nhole); - pushFront(r, hreg, nhreg); - pushFront(a, harea, nharea); - add = true; - } - else if (hole[nhole-1] == ea) - { - // The segment matches the end of the hole boundary. - pushBack(eb, hole, nhole); - pushBack(r, hreg, nhreg); - pushBack(a, harea, nharea); - add = true; - } - if (add) - { - // The edge segment was added, remove it. - edges[i*4+0] = edges[(nedges-1)*4+0]; - edges[i*4+1] = edges[(nedges-1)*4+1]; - edges[i*4+2] = edges[(nedges-1)*4+2]; - edges[i*4+3] = edges[(nedges-1)*4+3]; - --nedges; - match = true; - --i; - } - } - - if (!match) - break; - } - - rcScopedDelete tris = (int*)rcAlloc(sizeof(int)*nhole*3, RC_ALLOC_TEMP); - if (!tris) - { - ctx->log(RC_LOG_WARNING, "removeVertex: Out of memory 'tris' (%d).", nhole*3); - return false; - } - - rcScopedDelete tverts = (int*)rcAlloc(sizeof(int)*nhole*4, RC_ALLOC_TEMP); - if (!tverts) - { - ctx->log(RC_LOG_WARNING, "removeVertex: Out of memory 'tverts' (%d).", nhole*4); - return false; - } - - rcScopedDelete thole = (int*)rcAlloc(sizeof(int)*nhole, RC_ALLOC_TEMP); - if (!tverts) - { - ctx->log(RC_LOG_WARNING, "removeVertex: Out of memory 'thole' (%d).", nhole); - return false; - } - - // Generate temp vertex array for triangulation. - for (int i = 0; i < nhole; ++i) - { - const int pi = hole[i]; - tverts[i*4+0] = mesh.verts[pi*3+0]; - tverts[i*4+1] = mesh.verts[pi*3+1]; - tverts[i*4+2] = mesh.verts[pi*3+2]; - tverts[i*4+3] = 0; - thole[i] = i; - } - - // Triangulate the hole. - int ntris = triangulate(nhole, &tverts[0], &thole[0], tris); - if (ntris < 0) - { - ntris = -ntris; - ctx->log(RC_LOG_WARNING, "removeVertex: triangulate() returned bad results."); - } - - // Merge the hole triangles back to polygons. - rcScopedDelete polys = (unsigned short*)rcAlloc(sizeof(unsigned short)*(ntris+1)*nvp, RC_ALLOC_TEMP); - if (!polys) - { - ctx->log(RC_LOG_ERROR, "removeVertex: Out of memory 'polys' (%d).", (ntris+1)*nvp); - return false; - } - rcScopedDelete pregs = (unsigned short*)rcAlloc(sizeof(unsigned short)*ntris, RC_ALLOC_TEMP); - if (!pregs) - { - ctx->log(RC_LOG_ERROR, "removeVertex: Out of memory 'pregs' (%d).", ntris); - return false; - } - rcScopedDelete pareas = (unsigned char*)rcAlloc(sizeof(unsigned char)*ntris, RC_ALLOC_TEMP); - if (!pregs) - { - ctx->log(RC_LOG_ERROR, "removeVertex: Out of memory 'pareas' (%d).", ntris); - return false; - } - - unsigned short* tmpPoly = &polys[ntris*nvp]; - - // Build initial polygons. - int npolys = 0; - memset(polys, 0xff, ntris*nvp*sizeof(unsigned short)); - for (int j = 0; j < ntris; ++j) - { - int* t = &tris[j*3]; - if (t[0] != t[1] && t[0] != t[2] && t[1] != t[2]) - { - polys[npolys*nvp+0] = (unsigned short)hole[t[0]]; - polys[npolys*nvp+1] = (unsigned short)hole[t[1]]; - polys[npolys*nvp+2] = (unsigned short)hole[t[2]]; - pregs[npolys] = (unsigned short)hreg[t[0]]; - pareas[npolys] = (unsigned char)harea[t[0]]; - npolys++; - } - } - if (!npolys) - return true; - - // Merge polygons. - if (nvp > 3) - { - for (;;) - { - // Find best polygons to merge. - int bestMergeVal = 0; - int bestPa = 0, bestPb = 0, bestEa = 0, bestEb = 0; - - for (int j = 0; j < npolys-1; ++j) - { - unsigned short* pj = &polys[j*nvp]; - for (int k = j+1; k < npolys; ++k) - { - unsigned short* pk = &polys[k*nvp]; - int ea, eb; - int v = getPolyMergeValue(pj, pk, mesh.verts, ea, eb, nvp); - if (v > bestMergeVal) - { - bestMergeVal = v; - bestPa = j; - bestPb = k; - bestEa = ea; - bestEb = eb; - } - } - } - - if (bestMergeVal > 0) - { - // Found best, merge. - unsigned short* pa = &polys[bestPa*nvp]; - unsigned short* pb = &polys[bestPb*nvp]; - mergePolys(pa, pb, bestEa, bestEb, tmpPoly, nvp); - unsigned short* last = &polys[(npolys-1)*nvp]; - if (pb != last) - memcpy(pb, last, sizeof(unsigned short)*nvp); - pregs[bestPb] = pregs[npolys-1]; - pareas[bestPb] = pareas[npolys-1]; - npolys--; - } - else - { - // Could not merge any polygons, stop. - break; - } - } - } - - // Store polygons. - for (int i = 0; i < npolys; ++i) - { - if (mesh.npolys >= maxTris) break; - unsigned short* p = &mesh.polys[mesh.npolys*nvp*2]; - memset(p,0xff,sizeof(unsigned short)*nvp*2); - for (int j = 0; j < nvp; ++j) - p[j] = polys[i*nvp+j]; - mesh.regs[mesh.npolys] = pregs[i]; - mesh.areas[mesh.npolys] = pareas[i]; - mesh.npolys++; - if (mesh.npolys > maxTris) - { - ctx->log(RC_LOG_ERROR, "removeVertex: Too many polygons %d (max:%d).", mesh.npolys, maxTris); - return false; - } - } - - return true; -} - -/// @par -/// -/// @note If the mesh data is to be used to construct a Detour navigation mesh, then the upper -/// limit must be retricted to <= #DT_VERTS_PER_POLYGON. -/// -/// @see rcAllocPolyMesh, rcContourSet, rcPolyMesh, rcConfig -bool rcBuildPolyMesh(rcContext* ctx, rcContourSet& cset, const int nvp, rcPolyMesh& mesh) -{ - rcAssert(ctx); - - ctx->startTimer(RC_TIMER_BUILD_POLYMESH); - - rcVcopy(mesh.bmin, cset.bmin); - rcVcopy(mesh.bmax, cset.bmax); - mesh.cs = cset.cs; - mesh.ch = cset.ch; - mesh.borderSize = cset.borderSize; - - int maxVertices = 0; - int maxTris = 0; - int maxVertsPerCont = 0; - for (int i = 0; i < cset.nconts; ++i) - { - // Skip null contours. - if (cset.conts[i].nverts < 3) continue; - maxVertices += cset.conts[i].nverts; - maxTris += cset.conts[i].nverts - 2; - maxVertsPerCont = rcMax(maxVertsPerCont, cset.conts[i].nverts); - } - - if (maxVertices >= 0xfffe) - { - ctx->log(RC_LOG_ERROR, "rcBuildPolyMesh: Too many vertices %d.", maxVertices); - return false; - } - - rcScopedDelete vflags = (unsigned char*)rcAlloc(sizeof(unsigned char)*maxVertices, RC_ALLOC_TEMP); - if (!vflags) - { - ctx->log(RC_LOG_ERROR, "rcBuildPolyMesh: Out of memory 'vflags' (%d).", maxVertices); - return false; - } - memset(vflags, 0, maxVertices); - - mesh.verts = (unsigned short*)rcAlloc(sizeof(unsigned short)*maxVertices*3, RC_ALLOC_PERM); - if (!mesh.verts) - { - ctx->log(RC_LOG_ERROR, "rcBuildPolyMesh: Out of memory 'mesh.verts' (%d).", maxVertices); - return false; - } - mesh.polys = (unsigned short*)rcAlloc(sizeof(unsigned short)*maxTris*nvp*2, RC_ALLOC_PERM); - if (!mesh.polys) - { - ctx->log(RC_LOG_ERROR, "rcBuildPolyMesh: Out of memory 'mesh.polys' (%d).", maxTris*nvp*2); - return false; - } - mesh.regs = (unsigned short*)rcAlloc(sizeof(unsigned short)*maxTris, RC_ALLOC_PERM); - if (!mesh.regs) - { - ctx->log(RC_LOG_ERROR, "rcBuildPolyMesh: Out of memory 'mesh.regs' (%d).", maxTris); - return false; - } - mesh.areas = (unsigned char*)rcAlloc(sizeof(unsigned char)*maxTris, RC_ALLOC_PERM); - if (!mesh.areas) - { - ctx->log(RC_LOG_ERROR, "rcBuildPolyMesh: Out of memory 'mesh.areas' (%d).", maxTris); - return false; - } - - mesh.nverts = 0; - mesh.npolys = 0; - mesh.nvp = nvp; - mesh.maxpolys = maxTris; - - memset(mesh.verts, 0, sizeof(unsigned short)*maxVertices*3); - memset(mesh.polys, 0xff, sizeof(unsigned short)*maxTris*nvp*2); - memset(mesh.regs, 0, sizeof(unsigned short)*maxTris); - memset(mesh.areas, 0, sizeof(unsigned char)*maxTris); - - rcScopedDelete nextVert = (int*)rcAlloc(sizeof(int)*maxVertices, RC_ALLOC_TEMP); - if (!nextVert) - { - ctx->log(RC_LOG_ERROR, "rcBuildPolyMesh: Out of memory 'nextVert' (%d).", maxVertices); - return false; - } - memset(nextVert, 0, sizeof(int)*maxVertices); - - rcScopedDelete firstVert = (int*)rcAlloc(sizeof(int)*VERTEX_BUCKET_COUNT, RC_ALLOC_TEMP); - if (!firstVert) - { - ctx->log(RC_LOG_ERROR, "rcBuildPolyMesh: Out of memory 'firstVert' (%d).", VERTEX_BUCKET_COUNT); - return false; - } - for (int i = 0; i < VERTEX_BUCKET_COUNT; ++i) - firstVert[i] = -1; - - rcScopedDelete indices = (int*)rcAlloc(sizeof(int)*maxVertsPerCont, RC_ALLOC_TEMP); - if (!indices) - { - ctx->log(RC_LOG_ERROR, "rcBuildPolyMesh: Out of memory 'indices' (%d).", maxVertsPerCont); - return false; - } - rcScopedDelete tris = (int*)rcAlloc(sizeof(int)*maxVertsPerCont*3, RC_ALLOC_TEMP); - if (!tris) - { - ctx->log(RC_LOG_ERROR, "rcBuildPolyMesh: Out of memory 'tris' (%d).", maxVertsPerCont*3); - return false; - } - rcScopedDelete polys = (unsigned short*)rcAlloc(sizeof(unsigned short)*(maxVertsPerCont+1)*nvp, RC_ALLOC_TEMP); - if (!polys) - { - ctx->log(RC_LOG_ERROR, "rcBuildPolyMesh: Out of memory 'polys' (%d).", maxVertsPerCont*nvp); - return false; - } - unsigned short* tmpPoly = &polys[maxVertsPerCont*nvp]; - - for (int i = 0; i < cset.nconts; ++i) - { - rcContour& cont = cset.conts[i]; - - // Skip null contours. - if (cont.nverts < 3) - continue; - - // Triangulate contour - for (int j = 0; j < cont.nverts; ++j) - indices[j] = j; - - int ntris = triangulate(cont.nverts, cont.verts, &indices[0], &tris[0]); - if (ntris <= 0) - { - // Bad triangulation, should not happen. -/* printf("\tconst float bmin[3] = {%ff,%ff,%ff};\n", cset.bmin[0], cset.bmin[1], cset.bmin[2]); - printf("\tconst float cs = %ff;\n", cset.cs); - printf("\tconst float ch = %ff;\n", cset.ch); - printf("\tconst int verts[] = {\n"); - for (int k = 0; k < cont.nverts; ++k) - { - const int* v = &cont.verts[k*4]; - printf("\t\t%d,%d,%d,%d,\n", v[0], v[1], v[2], v[3]); - } - printf("\t};\n\tconst int nverts = sizeof(verts)/(sizeof(int)*4);\n");*/ - ctx->log(RC_LOG_WARNING, "rcBuildPolyMesh: Bad triangulation Contour %d.", i); - ntris = -ntris; - } - - // Add and merge vertices. - for (int j = 0; j < cont.nverts; ++j) - { - const int* v = &cont.verts[j*4]; - indices[j] = addVertex((unsigned short)v[0], (unsigned short)v[1], (unsigned short)v[2], - mesh.verts, firstVert, nextVert, mesh.nverts); - if (v[3] & RC_BORDER_VERTEX) - { - // This vertex should be removed. - vflags[indices[j]] = 1; - } - } - - // Build initial polygons. - int npolys = 0; - memset(polys, 0xff, maxVertsPerCont*nvp*sizeof(unsigned short)); - for (int j = 0; j < ntris; ++j) - { - int* t = &tris[j*3]; - if (t[0] != t[1] && t[0] != t[2] && t[1] != t[2]) - { - polys[npolys*nvp+0] = (unsigned short)indices[t[0]]; - polys[npolys*nvp+1] = (unsigned short)indices[t[1]]; - polys[npolys*nvp+2] = (unsigned short)indices[t[2]]; - npolys++; - } - } - if (!npolys) - continue; - - // Merge polygons. - if (nvp > 3) - { - for(;;) - { - // Find best polygons to merge. - int bestMergeVal = 0; - int bestPa = 0, bestPb = 0, bestEa = 0, bestEb = 0; - - for (int j = 0; j < npolys-1; ++j) - { - unsigned short* pj = &polys[j*nvp]; - for (int k = j+1; k < npolys; ++k) - { - unsigned short* pk = &polys[k*nvp]; - int ea, eb; - int v = getPolyMergeValue(pj, pk, mesh.verts, ea, eb, nvp); - if (v > bestMergeVal) - { - bestMergeVal = v; - bestPa = j; - bestPb = k; - bestEa = ea; - bestEb = eb; - } - } - } - - if (bestMergeVal > 0) - { - // Found best, merge. - unsigned short* pa = &polys[bestPa*nvp]; - unsigned short* pb = &polys[bestPb*nvp]; - mergePolys(pa, pb, bestEa, bestEb, tmpPoly, nvp); - unsigned short* lastPoly = &polys[(npolys-1)*nvp]; - if (pb != lastPoly) - memcpy(pb, lastPoly, sizeof(unsigned short)*nvp); - npolys--; - } - else - { - // Could not merge any polygons, stop. - break; - } - } - } - - // Store polygons. - for (int j = 0; j < npolys; ++j) - { - unsigned short* p = &mesh.polys[mesh.npolys*nvp*2]; - unsigned short* q = &polys[j*nvp]; - for (int k = 0; k < nvp; ++k) - p[k] = q[k]; - mesh.regs[mesh.npolys] = cont.reg; - mesh.areas[mesh.npolys] = cont.area; - mesh.npolys++; - if (mesh.npolys > maxTris) - { - ctx->log(RC_LOG_ERROR, "rcBuildPolyMesh: Too many polygons %d (max:%d).", mesh.npolys, maxTris); - return false; - } - } - } - - - // Remove edge vertices. - for (int i = 0; i < mesh.nverts; ++i) - { - if (vflags[i]) - { - if (!canRemoveVertex(ctx, mesh, (unsigned short)i)) - continue; - if (!removeVertex(ctx, mesh, (unsigned short)i, maxTris)) - { - // Failed to remove vertex - ctx->log(RC_LOG_ERROR, "rcBuildPolyMesh: Failed to remove edge vertex %d.", i); - return false; - } - // Remove vertex - // Note: mesh.nverts is already decremented inside removeVertex()! - // Fixup vertex flags - for (int j = i; j < mesh.nverts; ++j) - vflags[j] = vflags[j+1]; - --i; - } - } - - // Calculate adjacency. - if (!buildMeshAdjacency(mesh.polys, mesh.npolys, mesh.nverts, nvp)) - { - ctx->log(RC_LOG_ERROR, "rcBuildPolyMesh: Adjacency failed."); - return false; - } - - // Find portal edges - if (mesh.borderSize > 0) - { - const int w = cset.width; - const int h = cset.height; - for (int i = 0; i < mesh.npolys; ++i) - { - unsigned short* p = &mesh.polys[i*2*nvp]; - for (int j = 0; j < nvp; ++j) - { - if (p[j] == RC_MESH_NULL_IDX) break; - // Skip connected edges. - if (p[nvp+j] != RC_MESH_NULL_IDX) - continue; - int nj = j+1; - if (nj >= nvp || p[nj] == RC_MESH_NULL_IDX) nj = 0; - const unsigned short* va = &mesh.verts[p[j]*3]; - const unsigned short* vb = &mesh.verts[p[nj]*3]; - - if ((int)va[0] == 0 && (int)vb[0] == 0) - p[nvp+j] = 0x8000 | 0; - else if ((int)va[2] == h && (int)vb[2] == h) - p[nvp+j] = 0x8000 | 1; - else if ((int)va[0] == w && (int)vb[0] == w) - p[nvp+j] = 0x8000 | 2; - else if ((int)va[2] == 0 && (int)vb[2] == 0) - p[nvp+j] = 0x8000 | 3; - } - } - } - - // Just allocate the mesh flags array. The user is resposible to fill it. - mesh.flags = (unsigned short*)rcAlloc(sizeof(unsigned short)*mesh.npolys, RC_ALLOC_PERM); - if (!mesh.flags) - { - ctx->log(RC_LOG_ERROR, "rcBuildPolyMesh: Out of memory 'mesh.flags' (%d).", mesh.npolys); - return false; - } - memset(mesh.flags, 0, sizeof(unsigned short) * mesh.npolys); - - if (mesh.nverts > 0xffff) - { - ctx->log(RC_LOG_ERROR, "rcBuildPolyMesh: The resulting mesh has too many vertices %d (max %d). Data can be corrupted.", mesh.nverts, 0xffff); - } - if (mesh.npolys > 0xffff) - { - ctx->log(RC_LOG_ERROR, "rcBuildPolyMesh: The resulting mesh has too many polygons %d (max %d). Data can be corrupted.", mesh.npolys, 0xffff); - } - - ctx->stopTimer(RC_TIMER_BUILD_POLYMESH); - - return true; -} - -/// @see rcAllocPolyMesh, rcPolyMesh -bool rcMergePolyMeshes(rcContext* ctx, rcPolyMesh** meshes, const int nmeshes, rcPolyMesh& mesh) -{ - rcAssert(ctx); - - if (!nmeshes || !meshes) - return true; - - ctx->startTimer(RC_TIMER_MERGE_POLYMESH); - - mesh.nvp = meshes[0]->nvp; - mesh.cs = meshes[0]->cs; - mesh.ch = meshes[0]->ch; - rcVcopy(mesh.bmin, meshes[0]->bmin); - rcVcopy(mesh.bmax, meshes[0]->bmax); - - int maxVerts = 0; - int maxPolys = 0; - int maxVertsPerMesh = 0; - for (int i = 0; i < nmeshes; ++i) - { - rcVmin(mesh.bmin, meshes[i]->bmin); - rcVmax(mesh.bmax, meshes[i]->bmax); - maxVertsPerMesh = rcMax(maxVertsPerMesh, meshes[i]->nverts); - maxVerts += meshes[i]->nverts; - maxPolys += meshes[i]->npolys; - } - - mesh.nverts = 0; - mesh.verts = (unsigned short*)rcAlloc(sizeof(unsigned short)*maxVerts*3, RC_ALLOC_PERM); - if (!mesh.verts) - { - ctx->log(RC_LOG_ERROR, "rcMergePolyMeshes: Out of memory 'mesh.verts' (%d).", maxVerts*3); - return false; - } - - mesh.npolys = 0; - mesh.polys = (unsigned short*)rcAlloc(sizeof(unsigned short)*maxPolys*2*mesh.nvp, RC_ALLOC_PERM); - if (!mesh.polys) - { - ctx->log(RC_LOG_ERROR, "rcMergePolyMeshes: Out of memory 'mesh.polys' (%d).", maxPolys*2*mesh.nvp); - return false; - } - memset(mesh.polys, 0xff, sizeof(unsigned short)*maxPolys*2*mesh.nvp); - - mesh.regs = (unsigned short*)rcAlloc(sizeof(unsigned short)*maxPolys, RC_ALLOC_PERM); - if (!mesh.regs) - { - ctx->log(RC_LOG_ERROR, "rcMergePolyMeshes: Out of memory 'mesh.regs' (%d).", maxPolys); - return false; - } - memset(mesh.regs, 0, sizeof(unsigned short)*maxPolys); - - mesh.areas = (unsigned char*)rcAlloc(sizeof(unsigned char)*maxPolys, RC_ALLOC_PERM); - if (!mesh.areas) - { - ctx->log(RC_LOG_ERROR, "rcMergePolyMeshes: Out of memory 'mesh.areas' (%d).", maxPolys); - return false; - } - memset(mesh.areas, 0, sizeof(unsigned char)*maxPolys); - - mesh.flags = (unsigned short*)rcAlloc(sizeof(unsigned short)*maxPolys, RC_ALLOC_PERM); - if (!mesh.flags) - { - ctx->log(RC_LOG_ERROR, "rcMergePolyMeshes: Out of memory 'mesh.flags' (%d).", maxPolys); - return false; - } - memset(mesh.flags, 0, sizeof(unsigned short)*maxPolys); - - rcScopedDelete nextVert = (int*)rcAlloc(sizeof(int)*maxVerts, RC_ALLOC_TEMP); - if (!nextVert) - { - ctx->log(RC_LOG_ERROR, "rcMergePolyMeshes: Out of memory 'nextVert' (%d).", maxVerts); - return false; - } - memset(nextVert, 0, sizeof(int)*maxVerts); - - rcScopedDelete firstVert = (int*)rcAlloc(sizeof(int)*VERTEX_BUCKET_COUNT, RC_ALLOC_TEMP); - if (!firstVert) - { - ctx->log(RC_LOG_ERROR, "rcMergePolyMeshes: Out of memory 'firstVert' (%d).", VERTEX_BUCKET_COUNT); - return false; - } - for (int i = 0; i < VERTEX_BUCKET_COUNT; ++i) - firstVert[i] = -1; - - rcScopedDelete vremap = (unsigned short*)rcAlloc(sizeof(unsigned short)*maxVertsPerMesh, RC_ALLOC_PERM); - if (!vremap) - { - ctx->log(RC_LOG_ERROR, "rcMergePolyMeshes: Out of memory 'vremap' (%d).", maxVertsPerMesh); - return false; - } - memset(vremap, 0, sizeof(unsigned short)*maxVertsPerMesh); - - for (int i = 0; i < nmeshes; ++i) - { - const rcPolyMesh* pmesh = meshes[i]; - - const unsigned short ox = (unsigned short)floorf((pmesh->bmin[0]-mesh.bmin[0])/mesh.cs+0.5f); - const unsigned short oz = (unsigned short)floorf((pmesh->bmin[2]-mesh.bmin[2])/mesh.cs+0.5f); - - for (int j = 0; j < pmesh->nverts; ++j) - { - unsigned short* v = &pmesh->verts[j*3]; - vremap[j] = addVertex(v[0]+ox, v[1], v[2]+oz, - mesh.verts, firstVert, nextVert, mesh.nverts); - } - - for (int j = 0; j < pmesh->npolys; ++j) - { - unsigned short* tgt = &mesh.polys[mesh.npolys*2*mesh.nvp]; - unsigned short* src = &pmesh->polys[j*2*mesh.nvp]; - mesh.regs[mesh.npolys] = pmesh->regs[j]; - mesh.areas[mesh.npolys] = pmesh->areas[j]; - mesh.flags[mesh.npolys] = pmesh->flags[j]; - mesh.npolys++; - for (int k = 0; k < mesh.nvp; ++k) - { - if (src[k] == RC_MESH_NULL_IDX) break; - tgt[k] = vremap[src[k]]; - } - } - } - - // Calculate adjacency. - if (!buildMeshAdjacency(mesh.polys, mesh.npolys, mesh.nverts, mesh.nvp)) - { - ctx->log(RC_LOG_ERROR, "rcMergePolyMeshes: Adjacency failed."); - return false; - } - - if (mesh.nverts > 0xffff) - { - ctx->log(RC_LOG_ERROR, "rcMergePolyMeshes: The resulting mesh has too many vertices %d (max %d). Data can be corrupted.", mesh.nverts, 0xffff); - } - if (mesh.npolys > 0xffff) - { - ctx->log(RC_LOG_ERROR, "rcMergePolyMeshes: The resulting mesh has too many polygons %d (max %d). Data can be corrupted.", mesh.npolys, 0xffff); - } - - ctx->stopTimer(RC_TIMER_MERGE_POLYMESH); - - return true; -} - -bool rcCopyPolyMesh(rcContext* ctx, const rcPolyMesh& src, rcPolyMesh& dst) -{ - rcAssert(ctx); - - // Destination must be empty. - rcAssert(dst.verts == 0); - rcAssert(dst.polys == 0); - rcAssert(dst.regs == 0); - rcAssert(dst.areas == 0); - rcAssert(dst.flags == 0); - - dst.nverts = src.nverts; - dst.npolys = src.npolys; - dst.maxpolys = src.npolys; - dst.nvp = src.nvp; - rcVcopy(dst.bmin, src.bmin); - rcVcopy(dst.bmax, src.bmax); - dst.cs = src.cs; - dst.ch = src.ch; - dst.borderSize = src.borderSize; - - dst.verts = (unsigned short*)rcAlloc(sizeof(unsigned short)*src.nverts*3, RC_ALLOC_PERM); - if (!dst.verts) - { - ctx->log(RC_LOG_ERROR, "rcCopyPolyMesh: Out of memory 'dst.verts' (%d).", src.nverts*3); - return false; - } - memcpy(dst.verts, src.verts, sizeof(unsigned short)*src.nverts*3); - - dst.polys = (unsigned short*)rcAlloc(sizeof(unsigned short)*src.npolys*2*src.nvp, RC_ALLOC_PERM); - if (!dst.polys) - { - ctx->log(RC_LOG_ERROR, "rcCopyPolyMesh: Out of memory 'dst.polys' (%d).", src.npolys*2*src.nvp); - return false; - } - memcpy(dst.polys, src.polys, sizeof(unsigned short)*src.npolys*2*src.nvp); - - dst.regs = (unsigned short*)rcAlloc(sizeof(unsigned short)*src.npolys, RC_ALLOC_PERM); - if (!dst.regs) - { - ctx->log(RC_LOG_ERROR, "rcCopyPolyMesh: Out of memory 'dst.regs' (%d).", src.npolys); - return false; - } - memcpy(dst.regs, src.regs, sizeof(unsigned short)*src.npolys); - - dst.areas = (unsigned char*)rcAlloc(sizeof(unsigned char)*src.npolys, RC_ALLOC_PERM); - if (!dst.areas) - { - ctx->log(RC_LOG_ERROR, "rcCopyPolyMesh: Out of memory 'dst.areas' (%d).", src.npolys); - return false; - } - memcpy(dst.areas, src.areas, sizeof(unsigned char)*src.npolys); - - dst.flags = (unsigned short*)rcAlloc(sizeof(unsigned short)*src.npolys, RC_ALLOC_PERM); - if (!dst.flags) - { - ctx->log(RC_LOG_ERROR, "rcCopyPolyMesh: Out of memory 'dst.flags' (%d).", src.npolys); - return false; - } - memcpy(dst.flags, src.flags, sizeof(unsigned char)*src.npolys); - - return true; -} diff --git a/KREngine/3rdparty/recast/source/RecastMeshDetail.cpp b/KREngine/3rdparty/recast/source/RecastMeshDetail.cpp deleted file mode 100755 index 77438fd..0000000 --- a/KREngine/3rdparty/recast/source/RecastMeshDetail.cpp +++ /dev/null @@ -1,1245 +0,0 @@ -// -// Copyright (c) 2009-2010 Mikko Mononen memon@inside.org -// -// This software is provided 'as-is', without any express or implied -// warranty. In no event will the authors be held liable for any damages -// arising from the use of this software. -// Permission is granted to anyone to use this software for any purpose, -// including commercial applications, and to alter it and redistribute it -// freely, subject to the following restrictions: -// 1. The origin of this software must not be misrepresented; you must not -// claim that you wrote the original software. If you use this software -// in a product, an acknowledgment in the product documentation would be -// appreciated but is not required. -// 2. Altered source versions must be plainly marked as such, and must not be -// misrepresented as being the original software. -// 3. This notice may not be removed or altered from any source distribution. -// - -#include -#define _USE_MATH_DEFINES -#include -#include -#include -#include -#include "Recast.h" -#include "RecastAlloc.h" -#include "RecastAssert.h" - - -static const unsigned RC_UNSET_HEIGHT = 0xffff; - -struct rcHeightPatch -{ - inline rcHeightPatch() : data(0), xmin(0), ymin(0), width(0), height(0) {} - inline ~rcHeightPatch() { rcFree(data); } - unsigned short* data; - int xmin, ymin, width, height; -}; - - -inline float vdot2(const float* a, const float* b) -{ - return a[0]*b[0] + a[2]*b[2]; -} - -inline float vdistSq2(const float* p, const float* q) -{ - const float dx = q[0] - p[0]; - const float dy = q[2] - p[2]; - return dx*dx + dy*dy; -} - -inline float vdist2(const float* p, const float* q) -{ - return sqrtf(vdistSq2(p,q)); -} - -inline float vcross2(const float* p1, const float* p2, const float* p3) -{ - const float u1 = p2[0] - p1[0]; - const float v1 = p2[2] - p1[2]; - const float u2 = p3[0] - p1[0]; - const float v2 = p3[2] - p1[2]; - return u1 * v2 - v1 * u2; -} - -static bool circumCircle(const float* p1, const float* p2, const float* p3, - float* c, float& r) -{ - static const float EPS = 1e-6f; - - const float cp = vcross2(p1, p2, p3); - if (fabsf(cp) > EPS) - { - const float p1Sq = vdot2(p1,p1); - const float p2Sq = vdot2(p2,p2); - const float p3Sq = vdot2(p3,p3); - c[0] = (p1Sq*(p2[2]-p3[2]) + p2Sq*(p3[2]-p1[2]) + p3Sq*(p1[2]-p2[2])) / (2*cp); - c[2] = (p1Sq*(p3[0]-p2[0]) + p2Sq*(p1[0]-p3[0]) + p3Sq*(p2[0]-p1[0])) / (2*cp); - r = vdist2(c, p1); - return true; - } - - c[0] = p1[0]; - c[2] = p1[2]; - r = 0; - return false; -} - -static float distPtTri(const float* p, const float* a, const float* b, const float* c) -{ - float v0[3], v1[3], v2[3]; - rcVsub(v0, c,a); - rcVsub(v1, b,a); - rcVsub(v2, p,a); - - const float dot00 = vdot2(v0, v0); - const float dot01 = vdot2(v0, v1); - const float dot02 = vdot2(v0, v2); - const float dot11 = vdot2(v1, v1); - const float dot12 = vdot2(v1, v2); - - // Compute barycentric coordinates - const float invDenom = 1.0f / (dot00 * dot11 - dot01 * dot01); - const float u = (dot11 * dot02 - dot01 * dot12) * invDenom; - float v = (dot00 * dot12 - dot01 * dot02) * invDenom; - - // If point lies inside the triangle, return interpolated y-coord. - static const float EPS = 1e-4f; - if (u >= -EPS && v >= -EPS && (u+v) <= 1+EPS) - { - const float y = a[1] + v0[1]*u + v1[1]*v; - return fabsf(y-p[1]); - } - return FLT_MAX; -} - -static float distancePtSeg(const float* pt, const float* p, const float* q) -{ - float pqx = q[0] - p[0]; - float pqy = q[1] - p[1]; - float pqz = q[2] - p[2]; - float dx = pt[0] - p[0]; - float dy = pt[1] - p[1]; - float dz = pt[2] - p[2]; - float d = pqx*pqx + pqy*pqy + pqz*pqz; - float t = pqx*dx + pqy*dy + pqz*dz; - if (d > 0) - t /= d; - if (t < 0) - t = 0; - else if (t > 1) - t = 1; - - dx = p[0] + t*pqx - pt[0]; - dy = p[1] + t*pqy - pt[1]; - dz = p[2] + t*pqz - pt[2]; - - return dx*dx + dy*dy + dz*dz; -} - -static float distancePtSeg2d(const float* pt, const float* p, const float* q) -{ - float pqx = q[0] - p[0]; - float pqz = q[2] - p[2]; - float dx = pt[0] - p[0]; - float dz = pt[2] - p[2]; - float d = pqx*pqx + pqz*pqz; - float t = pqx*dx + pqz*dz; - if (d > 0) - t /= d; - if (t < 0) - t = 0; - else if (t > 1) - t = 1; - - dx = p[0] + t*pqx - pt[0]; - dz = p[2] + t*pqz - pt[2]; - - return dx*dx + dz*dz; -} - -static float distToTriMesh(const float* p, const float* verts, const int /*nverts*/, const int* tris, const int ntris) -{ - float dmin = FLT_MAX; - for (int i = 0; i < ntris; ++i) - { - const float* va = &verts[tris[i*4+0]*3]; - const float* vb = &verts[tris[i*4+1]*3]; - const float* vc = &verts[tris[i*4+2]*3]; - float d = distPtTri(p, va,vb,vc); - if (d < dmin) - dmin = d; - } - if (dmin == FLT_MAX) return -1; - return dmin; -} - -static float distToPoly(int nvert, const float* verts, const float* p) -{ - - float dmin = FLT_MAX; - int i, j, c = 0; - for (i = 0, j = nvert-1; i < nvert; j = i++) - { - const float* vi = &verts[i*3]; - const float* vj = &verts[j*3]; - if (((vi[2] > p[2]) != (vj[2] > p[2])) && - (p[0] < (vj[0]-vi[0]) * (p[2]-vi[2]) / (vj[2]-vi[2]) + vi[0]) ) - c = !c; - dmin = rcMin(dmin, distancePtSeg2d(p, vj, vi)); - } - return c ? -dmin : dmin; -} - - -static unsigned short getHeight(const float fx, const float fy, const float fz, - const float /*cs*/, const float ics, const float ch, - const rcHeightPatch& hp) -{ - int ix = (int)floorf(fx*ics + 0.01f); - int iz = (int)floorf(fz*ics + 0.01f); - ix = rcClamp(ix-hp.xmin, 0, hp.width - 1); - iz = rcClamp(iz-hp.ymin, 0, hp.height - 1); - unsigned short h = hp.data[ix+iz*hp.width]; - if (h == RC_UNSET_HEIGHT) - { - // Special case when data might be bad. - // Find nearest neighbour pixel which has valid height. - const int off[8*2] = { -1,0, -1,-1, 0,-1, 1,-1, 1,0, 1,1, 0,1, -1,1}; - float dmin = FLT_MAX; - for (int i = 0; i < 8; ++i) - { - const int nx = ix+off[i*2+0]; - const int nz = iz+off[i*2+1]; - if (nx < 0 || nz < 0 || nx >= hp.width || nz >= hp.height) continue; - const unsigned short nh = hp.data[nx+nz*hp.width]; - if (nh == RC_UNSET_HEIGHT) continue; - - const float d = fabsf(nh*ch - fy); - if (d < dmin) - { - h = nh; - dmin = d; - } - -/* const float dx = (nx+0.5f)*cs - fx; - const float dz = (nz+0.5f)*cs - fz; - const float d = dx*dx+dz*dz; - if (d < dmin) - { - h = nh; - dmin = d; - } */ - } - } - return h; -} - - -enum EdgeValues -{ - UNDEF = -1, - HULL = -2, -}; - -static int findEdge(const int* edges, int nedges, int s, int t) -{ - for (int i = 0; i < nedges; i++) - { - const int* e = &edges[i*4]; - if ((e[0] == s && e[1] == t) || (e[0] == t && e[1] == s)) - return i; - } - return UNDEF; -} - -static int addEdge(rcContext* ctx, int* edges, int& nedges, const int maxEdges, int s, int t, int l, int r) -{ - if (nedges >= maxEdges) - { - ctx->log(RC_LOG_ERROR, "addEdge: Too many edges (%d/%d).", nedges, maxEdges); - return UNDEF; - } - - // Add edge if not already in the triangulation. - int e = findEdge(edges, nedges, s, t); - if (e == UNDEF) - { - int* edge = &edges[nedges*4]; - edge[0] = s; - edge[1] = t; - edge[2] = l; - edge[3] = r; - return nedges++; - } - else - { - return UNDEF; - } -} - -static void updateLeftFace(int* e, int s, int t, int f) -{ - if (e[0] == s && e[1] == t && e[2] == UNDEF) - e[2] = f; - else if (e[1] == s && e[0] == t && e[3] == UNDEF) - e[3] = f; -} - -static int overlapSegSeg2d(const float* a, const float* b, const float* c, const float* d) -{ - const float a1 = vcross2(a, b, d); - const float a2 = vcross2(a, b, c); - if (a1*a2 < 0.0f) - { - float a3 = vcross2(c, d, a); - float a4 = a3 + a2 - a1; - if (a3 * a4 < 0.0f) - return 1; - } - return 0; -} - -static bool overlapEdges(const float* pts, const int* edges, int nedges, int s1, int t1) -{ - for (int i = 0; i < nedges; ++i) - { - const int s0 = edges[i*4+0]; - const int t0 = edges[i*4+1]; - // Same or connected edges do not overlap. - if (s0 == s1 || s0 == t1 || t0 == s1 || t0 == t1) - continue; - if (overlapSegSeg2d(&pts[s0*3],&pts[t0*3], &pts[s1*3],&pts[t1*3])) - return true; - } - return false; -} - -static void completeFacet(rcContext* ctx, const float* pts, int npts, int* edges, int& nedges, const int maxEdges, int& nfaces, int e) -{ - static const float EPS = 1e-5f; - - int* edge = &edges[e*4]; - - // Cache s and t. - int s,t; - if (edge[2] == UNDEF) - { - s = edge[0]; - t = edge[1]; - } - else if (edge[3] == UNDEF) - { - s = edge[1]; - t = edge[0]; - } - else - { - // Edge already completed. - return; - } - - // Find best point on left of edge. - int pt = npts; - float c[3] = {0,0,0}; - float r = -1; - for (int u = 0; u < npts; ++u) - { - if (u == s || u == t) continue; - if (vcross2(&pts[s*3], &pts[t*3], &pts[u*3]) > EPS) - { - if (r < 0) - { - // The circle is not updated yet, do it now. - pt = u; - circumCircle(&pts[s*3], &pts[t*3], &pts[u*3], c, r); - continue; - } - const float d = vdist2(c, &pts[u*3]); - const float tol = 0.001f; - if (d > r*(1+tol)) - { - // Outside current circumcircle, skip. - continue; - } - else if (d < r*(1-tol)) - { - // Inside safe circumcircle, update circle. - pt = u; - circumCircle(&pts[s*3], &pts[t*3], &pts[u*3], c, r); - } - else - { - // Inside epsilon circum circle, do extra tests to make sure the edge is valid. - // s-u and t-u cannot overlap with s-pt nor t-pt if they exists. - if (overlapEdges(pts, edges, nedges, s,u)) - continue; - if (overlapEdges(pts, edges, nedges, t,u)) - continue; - // Edge is valid. - pt = u; - circumCircle(&pts[s*3], &pts[t*3], &pts[u*3], c, r); - } - } - } - - // Add new triangle or update edge info if s-t is on hull. - if (pt < npts) - { - // Update face information of edge being completed. - updateLeftFace(&edges[e*4], s, t, nfaces); - - // Add new edge or update face info of old edge. - e = findEdge(edges, nedges, pt, s); - if (e == UNDEF) - addEdge(ctx, edges, nedges, maxEdges, pt, s, nfaces, UNDEF); - else - updateLeftFace(&edges[e*4], pt, s, nfaces); - - // Add new edge or update face info of old edge. - e = findEdge(edges, nedges, t, pt); - if (e == UNDEF) - addEdge(ctx, edges, nedges, maxEdges, t, pt, nfaces, UNDEF); - else - updateLeftFace(&edges[e*4], t, pt, nfaces); - - nfaces++; - } - else - { - updateLeftFace(&edges[e*4], s, t, HULL); - } -} - -static void delaunayHull(rcContext* ctx, const int npts, const float* pts, - const int nhull, const int* hull, - rcIntArray& tris, rcIntArray& edges) -{ - int nfaces = 0; - int nedges = 0; - const int maxEdges = npts*10; - edges.resize(maxEdges*4); - - for (int i = 0, j = nhull-1; i < nhull; j=i++) - addEdge(ctx, &edges[0], nedges, maxEdges, hull[j],hull[i], HULL, UNDEF); - - int currentEdge = 0; - while (currentEdge < nedges) - { - if (edges[currentEdge*4+2] == UNDEF) - completeFacet(ctx, pts, npts, &edges[0], nedges, maxEdges, nfaces, currentEdge); - if (edges[currentEdge*4+3] == UNDEF) - completeFacet(ctx, pts, npts, &edges[0], nedges, maxEdges, nfaces, currentEdge); - currentEdge++; - } - - // Create tris - tris.resize(nfaces*4); - for (int i = 0; i < nfaces*4; ++i) - tris[i] = -1; - - for (int i = 0; i < nedges; ++i) - { - const int* e = &edges[i*4]; - if (e[3] >= 0) - { - // Left face - int* t = &tris[e[3]*4]; - if (t[0] == -1) - { - t[0] = e[0]; - t[1] = e[1]; - } - else if (t[0] == e[1]) - t[2] = e[0]; - else if (t[1] == e[0]) - t[2] = e[1]; - } - if (e[2] >= 0) - { - // Right - int* t = &tris[e[2]*4]; - if (t[0] == -1) - { - t[0] = e[1]; - t[1] = e[0]; - } - else if (t[0] == e[0]) - t[2] = e[1]; - else if (t[1] == e[1]) - t[2] = e[0]; - } - } - - for (int i = 0; i < tris.size()/4; ++i) - { - int* t = &tris[i*4]; - if (t[0] == -1 || t[1] == -1 || t[2] == -1) - { - ctx->log(RC_LOG_WARNING, "delaunayHull: Removing dangling face %d [%d,%d,%d].", i, t[0],t[1],t[2]); - t[0] = tris[tris.size()-4]; - t[1] = tris[tris.size()-3]; - t[2] = tris[tris.size()-2]; - t[3] = tris[tris.size()-1]; - tris.resize(tris.size()-4); - --i; - } - } -} - - -inline float getJitterX(const int i) -{ - return (((i * 0x8da6b343) & 0xffff) / 65535.0f * 2.0f) - 1.0f; -} - -inline float getJitterY(const int i) -{ - return (((i * 0xd8163841) & 0xffff) / 65535.0f * 2.0f) - 1.0f; -} - -static bool buildPolyDetail(rcContext* ctx, const float* in, const int nin, - const float sampleDist, const float sampleMaxError, - const rcCompactHeightfield& chf, const rcHeightPatch& hp, - float* verts, int& nverts, rcIntArray& tris, - rcIntArray& edges, rcIntArray& samples) -{ - static const int MAX_VERTS = 127; - static const int MAX_TRIS = 255; // Max tris for delaunay is 2n-2-k (n=num verts, k=num hull verts). - static const int MAX_VERTS_PER_EDGE = 32; - float edge[(MAX_VERTS_PER_EDGE+1)*3]; - int hull[MAX_VERTS]; - int nhull = 0; - - nverts = 0; - - for (int i = 0; i < nin; ++i) - rcVcopy(&verts[i*3], &in[i*3]); - nverts = nin; - - const float cs = chf.cs; - const float ics = 1.0f/cs; - - // Tessellate outlines. - // This is done in separate pass in order to ensure - // seamless height values across the ply boundaries. - if (sampleDist > 0) - { - for (int i = 0, j = nin-1; i < nin; j=i++) - { - const float* vj = &in[j*3]; - const float* vi = &in[i*3]; - bool swapped = false; - // Make sure the segments are always handled in same order - // using lexological sort or else there will be seams. - if (fabsf(vj[0]-vi[0]) < 1e-6f) - { - if (vj[2] > vi[2]) - { - rcSwap(vj,vi); - swapped = true; - } - } - else - { - if (vj[0] > vi[0]) - { - rcSwap(vj,vi); - swapped = true; - } - } - // Create samples along the edge. - float dx = vi[0] - vj[0]; - float dy = vi[1] - vj[1]; - float dz = vi[2] - vj[2]; - float d = sqrtf(dx*dx + dz*dz); - int nn = 1 + (int)floorf(d/sampleDist); - if (nn >= MAX_VERTS_PER_EDGE) nn = MAX_VERTS_PER_EDGE-1; - if (nverts+nn >= MAX_VERTS) - nn = MAX_VERTS-1-nverts; - - for (int k = 0; k <= nn; ++k) - { - float u = (float)k/(float)nn; - float* pos = &edge[k*3]; - pos[0] = vj[0] + dx*u; - pos[1] = vj[1] + dy*u; - pos[2] = vj[2] + dz*u; - pos[1] = getHeight(pos[0],pos[1],pos[2], cs, ics, chf.ch, hp)*chf.ch; - } - // Simplify samples. - int idx[MAX_VERTS_PER_EDGE] = {0,nn}; - int nidx = 2; - for (int k = 0; k < nidx-1; ) - { - const int a = idx[k]; - const int b = idx[k+1]; - const float* va = &edge[a*3]; - const float* vb = &edge[b*3]; - // Find maximum deviation along the segment. - float maxd = 0; - int maxi = -1; - for (int m = a+1; m < b; ++m) - { - float dev = distancePtSeg(&edge[m*3],va,vb); - if (dev > maxd) - { - maxd = dev; - maxi = m; - } - } - // If the max deviation is larger than accepted error, - // add new point, else continue to next segment. - if (maxi != -1 && maxd > rcSqr(sampleMaxError)) - { - for (int m = nidx; m > k; --m) - idx[m] = idx[m-1]; - idx[k+1] = maxi; - nidx++; - } - else - { - ++k; - } - } - - hull[nhull++] = j; - // Add new vertices. - if (swapped) - { - for (int k = nidx-2; k > 0; --k) - { - rcVcopy(&verts[nverts*3], &edge[idx[k]*3]); - hull[nhull++] = nverts; - nverts++; - } - } - else - { - for (int k = 1; k < nidx-1; ++k) - { - rcVcopy(&verts[nverts*3], &edge[idx[k]*3]); - hull[nhull++] = nverts; - nverts++; - } - } - } - } - - - // Tessellate the base mesh. - edges.resize(0); - tris.resize(0); - - delaunayHull(ctx, nverts, verts, nhull, hull, tris, edges); - - if (tris.size() == 0) - { - // Could not triangulate the poly, make sure there is some valid data there. - ctx->log(RC_LOG_WARNING, "buildPolyDetail: Could not triangulate polygon, adding default data."); - for (int i = 2; i < nverts; ++i) - { - tris.push(0); - tris.push(i-1); - tris.push(i); - tris.push(0); - } - return true; - } - - if (sampleDist > 0) - { - // Create sample locations in a grid. - float bmin[3], bmax[3]; - rcVcopy(bmin, in); - rcVcopy(bmax, in); - for (int i = 1; i < nin; ++i) - { - rcVmin(bmin, &in[i*3]); - rcVmax(bmax, &in[i*3]); - } - int x0 = (int)floorf(bmin[0]/sampleDist); - int x1 = (int)ceilf(bmax[0]/sampleDist); - int z0 = (int)floorf(bmin[2]/sampleDist); - int z1 = (int)ceilf(bmax[2]/sampleDist); - samples.resize(0); - for (int z = z0; z < z1; ++z) - { - for (int x = x0; x < x1; ++x) - { - float pt[3]; - pt[0] = x*sampleDist; - pt[1] = (bmax[1]+bmin[1])*0.5f; - pt[2] = z*sampleDist; - // Make sure the samples are not too close to the edges. - if (distToPoly(nin,in,pt) > -sampleDist/2) continue; - samples.push(x); - samples.push(getHeight(pt[0], pt[1], pt[2], cs, ics, chf.ch, hp)); - samples.push(z); - samples.push(0); // Not added - } - } - - // Add the samples starting from the one that has the most - // error. The procedure stops when all samples are added - // or when the max error is within treshold. - const int nsamples = samples.size()/4; - for (int iter = 0; iter < nsamples; ++iter) - { - if (nverts >= MAX_VERTS) - break; - - // Find sample with most error. - float bestpt[3] = {0,0,0}; - float bestd = 0; - int besti = -1; - for (int i = 0; i < nsamples; ++i) - { - const int* s = &samples[i*4]; - if (s[3]) continue; // skip added. - float pt[3]; - // The sample location is jittered to get rid of some bad triangulations - // which are cause by symmetrical data from the grid structure. - pt[0] = s[0]*sampleDist + getJitterX(i)*cs*0.1f; - pt[1] = s[1]*chf.ch; - pt[2] = s[2]*sampleDist + getJitterY(i)*cs*0.1f; - float d = distToTriMesh(pt, verts, nverts, &tris[0], tris.size()/4); - if (d < 0) continue; // did not hit the mesh. - if (d > bestd) - { - bestd = d; - besti = i; - rcVcopy(bestpt,pt); - } - } - // If the max error is within accepted threshold, stop tesselating. - if (bestd <= sampleMaxError || besti == -1) - break; - // Mark sample as added. - samples[besti*4+3] = 1; - // Add the new sample point. - rcVcopy(&verts[nverts*3],bestpt); - nverts++; - - // Create new triangulation. - // TODO: Incremental add instead of full rebuild. - edges.resize(0); - tris.resize(0); - delaunayHull(ctx, nverts, verts, nhull, hull, tris, edges); - } - } - - const int ntris = tris.size()/4; - if (ntris > MAX_TRIS) - { - tris.resize(MAX_TRIS*4); - ctx->log(RC_LOG_ERROR, "rcBuildPolyMeshDetail: Shrinking triangle count from %d to max %d.", ntris, MAX_TRIS); - } - - return true; -} - -static void getHeightData(const rcCompactHeightfield& chf, - const unsigned short* poly, const int npoly, - const unsigned short* verts, const int bs, - rcHeightPatch& hp, rcIntArray& stack) -{ - // Floodfill the heightfield to get 2D height data, - // starting at vertex locations as seeds. - - // Note: Reads to the compact heightfield are offset by border size (bs) - // since border size offset is already removed from the polymesh vertices. - - memset(hp.data, 0, sizeof(unsigned short)*hp.width*hp.height); - - stack.resize(0); - - static const int offset[9*2] = - { - 0,0, -1,-1, 0,-1, 1,-1, 1,0, 1,1, 0,1, -1,1, -1,0, - }; - - // Use poly vertices as seed points for the flood fill. - for (int j = 0; j < npoly; ++j) - { - int cx = 0, cz = 0, ci =-1; - int dmin = RC_UNSET_HEIGHT; - for (int k = 0; k < 9; ++k) - { - const int ax = (int)verts[poly[j]*3+0] + offset[k*2+0]; - const int ay = (int)verts[poly[j]*3+1]; - const int az = (int)verts[poly[j]*3+2] + offset[k*2+1]; - if (ax < hp.xmin || ax >= hp.xmin+hp.width || - az < hp.ymin || az >= hp.ymin+hp.height) - continue; - - const rcCompactCell& c = chf.cells[(ax+bs)+(az+bs)*chf.width]; - for (int i = (int)c.index, ni = (int)(c.index+c.count); i < ni; ++i) - { - const rcCompactSpan& s = chf.spans[i]; - int d = rcAbs(ay - (int)s.y); - if (d < dmin) - { - cx = ax; - cz = az; - ci = i; - dmin = d; - } - } - } - if (ci != -1) - { - stack.push(cx); - stack.push(cz); - stack.push(ci); - } - } - - // Find center of the polygon using flood fill. - int pcx = 0, pcz = 0; - for (int j = 0; j < npoly; ++j) - { - pcx += (int)verts[poly[j]*3+0]; - pcz += (int)verts[poly[j]*3+2]; - } - pcx /= npoly; - pcz /= npoly; - - for (int i = 0; i < stack.size(); i += 3) - { - int cx = stack[i+0]; - int cy = stack[i+1]; - int idx = cx-hp.xmin+(cy-hp.ymin)*hp.width; - hp.data[idx] = 1; - } - - while (stack.size() > 0) - { - int ci = stack.pop(); - int cy = stack.pop(); - int cx = stack.pop(); - - // Check if close to center of the polygon. - if (rcAbs(cx-pcx) <= 1 && rcAbs(cy-pcz) <= 1) - { - stack.resize(0); - stack.push(cx); - stack.push(cy); - stack.push(ci); - break; - } - - const rcCompactSpan& cs = chf.spans[ci]; - - for (int dir = 0; dir < 4; ++dir) - { - if (rcGetCon(cs, dir) == RC_NOT_CONNECTED) continue; - - const int ax = cx + rcGetDirOffsetX(dir); - const int ay = cy + rcGetDirOffsetY(dir); - - if (ax < hp.xmin || ax >= (hp.xmin+hp.width) || - ay < hp.ymin || ay >= (hp.ymin+hp.height)) - continue; - - if (hp.data[ax-hp.xmin+(ay-hp.ymin)*hp.width] != 0) - continue; - - const int ai = (int)chf.cells[(ax+bs)+(ay+bs)*chf.width].index + rcGetCon(cs, dir); - - int idx = ax-hp.xmin+(ay-hp.ymin)*hp.width; - hp.data[idx] = 1; - - stack.push(ax); - stack.push(ay); - stack.push(ai); - } - } - - memset(hp.data, 0xff, sizeof(unsigned short)*hp.width*hp.height); - - // Mark start locations. - for (int i = 0; i < stack.size(); i += 3) - { - int cx = stack[i+0]; - int cy = stack[i+1]; - int ci = stack[i+2]; - int idx = cx-hp.xmin+(cy-hp.ymin)*hp.width; - const rcCompactSpan& cs = chf.spans[ci]; - hp.data[idx] = cs.y; - } - - static const int RETRACT_SIZE = 256; - int head = 0; - - while (head*3 < stack.size()) - { - int cx = stack[head*3+0]; - int cy = stack[head*3+1]; - int ci = stack[head*3+2]; - head++; - if (head >= RETRACT_SIZE) - { - head = 0; - if (stack.size() > RETRACT_SIZE*3) - memmove(&stack[0], &stack[RETRACT_SIZE*3], sizeof(int)*(stack.size()-RETRACT_SIZE*3)); - stack.resize(stack.size()-RETRACT_SIZE*3); - } - - const rcCompactSpan& cs = chf.spans[ci]; - for (int dir = 0; dir < 4; ++dir) - { - if (rcGetCon(cs, dir) == RC_NOT_CONNECTED) continue; - - const int ax = cx + rcGetDirOffsetX(dir); - const int ay = cy + rcGetDirOffsetY(dir); - - if (ax < hp.xmin || ax >= (hp.xmin+hp.width) || - ay < hp.ymin || ay >= (hp.ymin+hp.height)) - continue; - - if (hp.data[ax-hp.xmin+(ay-hp.ymin)*hp.width] != RC_UNSET_HEIGHT) - continue; - - const int ai = (int)chf.cells[(ax+bs)+(ay+bs)*chf.width].index + rcGetCon(cs, dir); - - const rcCompactSpan& as = chf.spans[ai]; - int idx = ax-hp.xmin+(ay-hp.ymin)*hp.width; - hp.data[idx] = as.y; - - stack.push(ax); - stack.push(ay); - stack.push(ai); - } - } - -} - -static unsigned char getEdgeFlags(const float* va, const float* vb, - const float* vpoly, const int npoly) -{ - // Return true if edge (va,vb) is part of the polygon. - static const float thrSqr = rcSqr(0.001f); - for (int i = 0, j = npoly-1; i < npoly; j=i++) - { - if (distancePtSeg2d(va, &vpoly[j*3], &vpoly[i*3]) < thrSqr && - distancePtSeg2d(vb, &vpoly[j*3], &vpoly[i*3]) < thrSqr) - return 1; - } - return 0; -} - -static unsigned char getTriFlags(const float* va, const float* vb, const float* vc, - const float* vpoly, const int npoly) -{ - unsigned char flags = 0; - flags |= getEdgeFlags(va,vb,vpoly,npoly) << 0; - flags |= getEdgeFlags(vb,vc,vpoly,npoly) << 2; - flags |= getEdgeFlags(vc,va,vpoly,npoly) << 4; - return flags; -} - -/// @par -/// -/// See the #rcConfig documentation for more information on the configuration parameters. -/// -/// @see rcAllocPolyMeshDetail, rcPolyMesh, rcCompactHeightfield, rcPolyMeshDetail, rcConfig -bool rcBuildPolyMeshDetail(rcContext* ctx, const rcPolyMesh& mesh, const rcCompactHeightfield& chf, - const float sampleDist, const float sampleMaxError, - rcPolyMeshDetail& dmesh) -{ - rcAssert(ctx); - - ctx->startTimer(RC_TIMER_BUILD_POLYMESHDETAIL); - - if (mesh.nverts == 0 || mesh.npolys == 0) - return true; - - const int nvp = mesh.nvp; - const float cs = mesh.cs; - const float ch = mesh.ch; - const float* orig = mesh.bmin; - const int borderSize = mesh.borderSize; - - rcIntArray edges(64); - rcIntArray tris(512); - rcIntArray stack(512); - rcIntArray samples(512); - float verts[256*3]; - rcHeightPatch hp; - int nPolyVerts = 0; - int maxhw = 0, maxhh = 0; - - rcScopedDelete bounds = (int*)rcAlloc(sizeof(int)*mesh.npolys*4, RC_ALLOC_TEMP); - if (!bounds) - { - ctx->log(RC_LOG_ERROR, "rcBuildPolyMeshDetail: Out of memory 'bounds' (%d).", mesh.npolys*4); - return false; - } - rcScopedDelete poly = (float*)rcAlloc(sizeof(float)*nvp*3, RC_ALLOC_TEMP); - if (!poly) - { - ctx->log(RC_LOG_ERROR, "rcBuildPolyMeshDetail: Out of memory 'poly' (%d).", nvp*3); - return false; - } - - // Find max size for a polygon area. - for (int i = 0; i < mesh.npolys; ++i) - { - const unsigned short* p = &mesh.polys[i*nvp*2]; - int& xmin = bounds[i*4+0]; - int& xmax = bounds[i*4+1]; - int& ymin = bounds[i*4+2]; - int& ymax = bounds[i*4+3]; - xmin = chf.width; - xmax = 0; - ymin = chf.height; - ymax = 0; - for (int j = 0; j < nvp; ++j) - { - if(p[j] == RC_MESH_NULL_IDX) break; - const unsigned short* v = &mesh.verts[p[j]*3]; - xmin = rcMin(xmin, (int)v[0]); - xmax = rcMax(xmax, (int)v[0]); - ymin = rcMin(ymin, (int)v[2]); - ymax = rcMax(ymax, (int)v[2]); - nPolyVerts++; - } - xmin = rcMax(0,xmin-1); - xmax = rcMin(chf.width,xmax+1); - ymin = rcMax(0,ymin-1); - ymax = rcMin(chf.height,ymax+1); - if (xmin >= xmax || ymin >= ymax) continue; - maxhw = rcMax(maxhw, xmax-xmin); - maxhh = rcMax(maxhh, ymax-ymin); - } - - hp.data = (unsigned short*)rcAlloc(sizeof(unsigned short)*maxhw*maxhh, RC_ALLOC_TEMP); - if (!hp.data) - { - ctx->log(RC_LOG_ERROR, "rcBuildPolyMeshDetail: Out of memory 'hp.data' (%d).", maxhw*maxhh); - return false; - } - - dmesh.nmeshes = mesh.npolys; - dmesh.nverts = 0; - dmesh.ntris = 0; - dmesh.meshes = (unsigned int*)rcAlloc(sizeof(unsigned int)*dmesh.nmeshes*4, RC_ALLOC_PERM); - if (!dmesh.meshes) - { - ctx->log(RC_LOG_ERROR, "rcBuildPolyMeshDetail: Out of memory 'dmesh.meshes' (%d).", dmesh.nmeshes*4); - return false; - } - - int vcap = nPolyVerts+nPolyVerts/2; - int tcap = vcap*2; - - dmesh.nverts = 0; - dmesh.verts = (float*)rcAlloc(sizeof(float)*vcap*3, RC_ALLOC_PERM); - if (!dmesh.verts) - { - ctx->log(RC_LOG_ERROR, "rcBuildPolyMeshDetail: Out of memory 'dmesh.verts' (%d).", vcap*3); - return false; - } - dmesh.ntris = 0; - dmesh.tris = (unsigned char*)rcAlloc(sizeof(unsigned char*)*tcap*4, RC_ALLOC_PERM); - if (!dmesh.tris) - { - ctx->log(RC_LOG_ERROR, "rcBuildPolyMeshDetail: Out of memory 'dmesh.tris' (%d).", tcap*4); - return false; - } - - for (int i = 0; i < mesh.npolys; ++i) - { - const unsigned short* p = &mesh.polys[i*nvp*2]; - - // Store polygon vertices for processing. - int npoly = 0; - for (int j = 0; j < nvp; ++j) - { - if(p[j] == RC_MESH_NULL_IDX) break; - const unsigned short* v = &mesh.verts[p[j]*3]; - poly[j*3+0] = v[0]*cs; - poly[j*3+1] = v[1]*ch; - poly[j*3+2] = v[2]*cs; - npoly++; - } - - // Get the height data from the area of the polygon. - hp.xmin = bounds[i*4+0]; - hp.ymin = bounds[i*4+2]; - hp.width = bounds[i*4+1]-bounds[i*4+0]; - hp.height = bounds[i*4+3]-bounds[i*4+2]; - getHeightData(chf, p, npoly, mesh.verts, borderSize, hp, stack); - - // Build detail mesh. - int nverts = 0; - if (!buildPolyDetail(ctx, poly, npoly, - sampleDist, sampleMaxError, - chf, hp, verts, nverts, tris, - edges, samples)) - { - return false; - } - - // Move detail verts to world space. - for (int j = 0; j < nverts; ++j) - { - verts[j*3+0] += orig[0]; - verts[j*3+1] += orig[1] + chf.ch; // Is this offset necessary? - verts[j*3+2] += orig[2]; - } - // Offset poly too, will be used to flag checking. - for (int j = 0; j < npoly; ++j) - { - poly[j*3+0] += orig[0]; - poly[j*3+1] += orig[1]; - poly[j*3+2] += orig[2]; - } - - // Store detail submesh. - const int ntris = tris.size()/4; - - dmesh.meshes[i*4+0] = (unsigned int)dmesh.nverts; - dmesh.meshes[i*4+1] = (unsigned int)nverts; - dmesh.meshes[i*4+2] = (unsigned int)dmesh.ntris; - dmesh.meshes[i*4+3] = (unsigned int)ntris; - - // Store vertices, allocate more memory if necessary. - if (dmesh.nverts+nverts > vcap) - { - while (dmesh.nverts+nverts > vcap) - vcap += 256; - - float* newv = (float*)rcAlloc(sizeof(float)*vcap*3, RC_ALLOC_PERM); - if (!newv) - { - ctx->log(RC_LOG_ERROR, "rcBuildPolyMeshDetail: Out of memory 'newv' (%d).", vcap*3); - return false; - } - if (dmesh.nverts) - memcpy(newv, dmesh.verts, sizeof(float)*3*dmesh.nverts); - rcFree(dmesh.verts); - dmesh.verts = newv; - } - for (int j = 0; j < nverts; ++j) - { - dmesh.verts[dmesh.nverts*3+0] = verts[j*3+0]; - dmesh.verts[dmesh.nverts*3+1] = verts[j*3+1]; - dmesh.verts[dmesh.nverts*3+2] = verts[j*3+2]; - dmesh.nverts++; - } - - // Store triangles, allocate more memory if necessary. - if (dmesh.ntris+ntris > tcap) - { - while (dmesh.ntris+ntris > tcap) - tcap += 256; - unsigned char* newt = (unsigned char*)rcAlloc(sizeof(unsigned char)*tcap*4, RC_ALLOC_PERM); - if (!newt) - { - ctx->log(RC_LOG_ERROR, "rcBuildPolyMeshDetail: Out of memory 'newt' (%d).", tcap*4); - return false; - } - if (dmesh.ntris) - memcpy(newt, dmesh.tris, sizeof(unsigned char)*4*dmesh.ntris); - rcFree(dmesh.tris); - dmesh.tris = newt; - } - for (int j = 0; j < ntris; ++j) - { - const int* t = &tris[j*4]; - dmesh.tris[dmesh.ntris*4+0] = (unsigned char)t[0]; - dmesh.tris[dmesh.ntris*4+1] = (unsigned char)t[1]; - dmesh.tris[dmesh.ntris*4+2] = (unsigned char)t[2]; - dmesh.tris[dmesh.ntris*4+3] = getTriFlags(&verts[t[0]*3], &verts[t[1]*3], &verts[t[2]*3], poly, npoly); - dmesh.ntris++; - } - } - - ctx->stopTimer(RC_TIMER_BUILD_POLYMESHDETAIL); - - return true; -} - -/// @see rcAllocPolyMeshDetail, rcPolyMeshDetail -bool rcMergePolyMeshDetails(rcContext* ctx, rcPolyMeshDetail** meshes, const int nmeshes, rcPolyMeshDetail& mesh) -{ - rcAssert(ctx); - - ctx->startTimer(RC_TIMER_MERGE_POLYMESHDETAIL); - - int maxVerts = 0; - int maxTris = 0; - int maxMeshes = 0; - - for (int i = 0; i < nmeshes; ++i) - { - if (!meshes[i]) continue; - maxVerts += meshes[i]->nverts; - maxTris += meshes[i]->ntris; - maxMeshes += meshes[i]->nmeshes; - } - - mesh.nmeshes = 0; - mesh.meshes = (unsigned int*)rcAlloc(sizeof(unsigned int)*maxMeshes*4, RC_ALLOC_PERM); - if (!mesh.meshes) - { - ctx->log(RC_LOG_ERROR, "rcBuildPolyMeshDetail: Out of memory 'pmdtl.meshes' (%d).", maxMeshes*4); - return false; - } - - mesh.ntris = 0; - mesh.tris = (unsigned char*)rcAlloc(sizeof(unsigned char)*maxTris*4, RC_ALLOC_PERM); - if (!mesh.tris) - { - ctx->log(RC_LOG_ERROR, "rcBuildPolyMeshDetail: Out of memory 'dmesh.tris' (%d).", maxTris*4); - return false; - } - - mesh.nverts = 0; - mesh.verts = (float*)rcAlloc(sizeof(float)*maxVerts*3, RC_ALLOC_PERM); - if (!mesh.verts) - { - ctx->log(RC_LOG_ERROR, "rcBuildPolyMeshDetail: Out of memory 'dmesh.verts' (%d).", maxVerts*3); - return false; - } - - // Merge datas. - for (int i = 0; i < nmeshes; ++i) - { - rcPolyMeshDetail* dm = meshes[i]; - if (!dm) continue; - for (int j = 0; j < dm->nmeshes; ++j) - { - unsigned int* dst = &mesh.meshes[mesh.nmeshes*4]; - unsigned int* src = &dm->meshes[j*4]; - dst[0] = (unsigned int)mesh.nverts+src[0]; - dst[1] = src[1]; - dst[2] = (unsigned int)mesh.ntris+src[2]; - dst[3] = src[3]; - mesh.nmeshes++; - } - - for (int k = 0; k < dm->nverts; ++k) - { - rcVcopy(&mesh.verts[mesh.nverts*3], &dm->verts[k*3]); - mesh.nverts++; - } - for (int k = 0; k < dm->ntris; ++k) - { - mesh.tris[mesh.ntris*4+0] = dm->tris[k*4+0]; - mesh.tris[mesh.ntris*4+1] = dm->tris[k*4+1]; - mesh.tris[mesh.ntris*4+2] = dm->tris[k*4+2]; - mesh.tris[mesh.ntris*4+3] = dm->tris[k*4+3]; - mesh.ntris++; - } - } - - ctx->stopTimer(RC_TIMER_MERGE_POLYMESHDETAIL); - - return true; -} - diff --git a/KREngine/3rdparty/recast/source/RecastRasterization.cpp b/KREngine/3rdparty/recast/source/RecastRasterization.cpp deleted file mode 100755 index d2bb7c9..0000000 --- a/KREngine/3rdparty/recast/source/RecastRasterization.cpp +++ /dev/null @@ -1,387 +0,0 @@ -// -// Copyright (c) 2009-2010 Mikko Mononen memon@inside.org -// -// This software is provided 'as-is', without any express or implied -// warranty. In no event will the authors be held liable for any damages -// arising from the use of this software. -// Permission is granted to anyone to use this software for any purpose, -// including commercial applications, and to alter it and redistribute it -// freely, subject to the following restrictions: -// 1. The origin of this software must not be misrepresented; you must not -// claim that you wrote the original software. If you use this software -// in a product, an acknowledgment in the product documentation would be -// appreciated but is not required. -// 2. Altered source versions must be plainly marked as such, and must not be -// misrepresented as being the original software. -// 3. This notice may not be removed or altered from any source distribution. -// - -#define _USE_MATH_DEFINES -#include -#include -#include "Recast.h" -#include "RecastAlloc.h" -#include "RecastAssert.h" - -inline bool overlapBounds(const float* amin, const float* amax, const float* bmin, const float* bmax) -{ - bool overlap = true; - overlap = (amin[0] > bmax[0] || amax[0] < bmin[0]) ? false : overlap; - overlap = (amin[1] > bmax[1] || amax[1] < bmin[1]) ? false : overlap; - overlap = (amin[2] > bmax[2] || amax[2] < bmin[2]) ? false : overlap; - return overlap; -} - -inline bool overlapInterval(unsigned short amin, unsigned short amax, - unsigned short bmin, unsigned short bmax) -{ - if (amax < bmin) return false; - if (amin > bmax) return false; - return true; -} - - -static rcSpan* allocSpan(rcHeightfield& hf) -{ - // If running out of memory, allocate new page and update the freelist. - if (!hf.freelist || !hf.freelist->next) - { - // Create new page. - // Allocate memory for the new pool. - rcSpanPool* pool = (rcSpanPool*)rcAlloc(sizeof(rcSpanPool), RC_ALLOC_PERM); - if (!pool) return 0; - pool->next = 0; - // Add the pool into the list of pools. - pool->next = hf.pools; - hf.pools = pool; - // Add new items to the free list. - rcSpan* freelist = hf.freelist; - rcSpan* head = &pool->items[0]; - rcSpan* it = &pool->items[RC_SPANS_PER_POOL]; - do - { - --it; - it->next = freelist; - freelist = it; - } - while (it != head); - hf.freelist = it; - } - - // Pop item from in front of the free list. - rcSpan* it = hf.freelist; - hf.freelist = hf.freelist->next; - return it; -} - -static void freeSpan(rcHeightfield& hf, rcSpan* ptr) -{ - if (!ptr) return; - // Add the node in front of the free list. - ptr->next = hf.freelist; - hf.freelist = ptr; -} - -static void addSpan(rcHeightfield& hf, const int x, const int y, - const unsigned short smin, const unsigned short smax, - const unsigned char area, const int flagMergeThr) -{ - - int idx = x + y*hf.width; - - rcSpan* s = allocSpan(hf); - s->smin = smin; - s->smax = smax; - s->area = area; - s->next = 0; - - // Empty cell, add he first span. - if (!hf.spans[idx]) - { - hf.spans[idx] = s; - return; - } - rcSpan* prev = 0; - rcSpan* cur = hf.spans[idx]; - - // Insert and merge spans. - while (cur) - { - if (cur->smin > s->smax) - { - // Current span is further than the new span, break. - break; - } - else if (cur->smax < s->smin) - { - // Current span is before the new span advance. - prev = cur; - cur = cur->next; - } - else - { - // Merge spans. - if (cur->smin < s->smin) - s->smin = cur->smin; - if (cur->smax > s->smax) - s->smax = cur->smax; - - // Merge flags. - if (rcAbs((int)s->smax - (int)cur->smax) <= flagMergeThr) - s->area = rcMax(s->area, cur->area); - - // Remove current span. - rcSpan* next = cur->next; - freeSpan(hf, cur); - if (prev) - prev->next = next; - else - hf.spans[idx] = next; - cur = next; - } - } - - // Insert new span. - if (prev) - { - s->next = prev->next; - prev->next = s; - } - else - { - s->next = hf.spans[idx]; - hf.spans[idx] = s; - } -} - -/// @par -/// -/// The span addition can be set to favor flags. If the span is merged to -/// another span and the new @p smax is within @p flagMergeThr units -/// from the existing span, the span flags are merged. -/// -/// @see rcHeightfield, rcSpan. -void rcAddSpan(rcContext* /*ctx*/, rcHeightfield& hf, const int x, const int y, - const unsigned short smin, const unsigned short smax, - const unsigned char area, const int flagMergeThr) -{ -// rcAssert(ctx); - addSpan(hf, x,y, smin, smax, area, flagMergeThr); -} - -static int clipPoly(const float* in, int n, float* out, float pnx, float pnz, float pd) -{ - float d[12]; - for (int i = 0; i < n; ++i) - d[i] = pnx*in[i*3+0] + pnz*in[i*3+2] + pd; - - int m = 0; - for (int i = 0, j = n-1; i < n; j=i, ++i) - { - bool ina = d[j] >= 0; - bool inb = d[i] >= 0; - if (ina != inb) - { - float s = d[j] / (d[j] - d[i]); - out[m*3+0] = in[j*3+0] + (in[i*3+0] - in[j*3+0])*s; - out[m*3+1] = in[j*3+1] + (in[i*3+1] - in[j*3+1])*s; - out[m*3+2] = in[j*3+2] + (in[i*3+2] - in[j*3+2])*s; - m++; - } - if (inb) - { - out[m*3+0] = in[i*3+0]; - out[m*3+1] = in[i*3+1]; - out[m*3+2] = in[i*3+2]; - m++; - } - } - return m; -} - -static void rasterizeTri(const float* v0, const float* v1, const float* v2, - const unsigned char area, rcHeightfield& hf, - const float* bmin, const float* bmax, - const float cs, const float ics, const float ich, - const int flagMergeThr) -{ - const int w = hf.width; - const int h = hf.height; - float tmin[3], tmax[3]; - const float by = bmax[1] - bmin[1]; - - // Calculate the bounding box of the triangle. - rcVcopy(tmin, v0); - rcVcopy(tmax, v0); - rcVmin(tmin, v1); - rcVmin(tmin, v2); - rcVmax(tmax, v1); - rcVmax(tmax, v2); - - // If the triangle does not touch the bbox of the heightfield, skip the triagle. - if (!overlapBounds(bmin, bmax, tmin, tmax)) - return; - - // Calculate the footpring of the triangle on the grid. - int x0 = (int)((tmin[0] - bmin[0])*ics); - int y0 = (int)((tmin[2] - bmin[2])*ics); - int x1 = (int)((tmax[0] - bmin[0])*ics); - int y1 = (int)((tmax[2] - bmin[2])*ics); - x0 = rcClamp(x0, 0, w-1); - y0 = rcClamp(y0, 0, h-1); - x1 = rcClamp(x1, 0, w-1); - y1 = rcClamp(y1, 0, h-1); - - // Clip the triangle into all grid cells it touches. - float in[7*3], out[7*3], inrow[7*3]; - - for (int y = y0; y <= y1; ++y) - { - // Clip polygon to row. - rcVcopy(&in[0], v0); - rcVcopy(&in[1*3], v1); - rcVcopy(&in[2*3], v2); - int nvrow = 3; - const float cz = bmin[2] + y*cs; - nvrow = clipPoly(in, nvrow, out, 0, 1, -cz); - if (nvrow < 3) continue; - nvrow = clipPoly(out, nvrow, inrow, 0, -1, cz+cs); - if (nvrow < 3) continue; - - for (int x = x0; x <= x1; ++x) - { - // Clip polygon to column. - int nv = nvrow; - const float cx = bmin[0] + x*cs; - nv = clipPoly(inrow, nv, out, 1, 0, -cx); - if (nv < 3) continue; - nv = clipPoly(out, nv, in, -1, 0, cx+cs); - if (nv < 3) continue; - - // Calculate min and max of the span. - float smin = in[1], smax = in[1]; - for (int i = 1; i < nv; ++i) - { - smin = rcMin(smin, in[i*3+1]); - smax = rcMax(smax, in[i*3+1]); - } - smin -= bmin[1]; - smax -= bmin[1]; - // Skip the span if it is outside the heightfield bbox - if (smax < 0.0f) continue; - if (smin > by) continue; - // Clamp the span to the heightfield bbox. - if (smin < 0.0f) smin = 0; - if (smax > by) smax = by; - - // Snap the span to the heightfield height grid. - unsigned short ismin = (unsigned short)rcClamp((int)floorf(smin * ich), 0, RC_SPAN_MAX_HEIGHT); - unsigned short ismax = (unsigned short)rcClamp((int)ceilf(smax * ich), (int)ismin+1, RC_SPAN_MAX_HEIGHT); - - addSpan(hf, x, y, ismin, ismax, area, flagMergeThr); - } - } -} - -/// @par -/// -/// No spans will be added if the triangle does not overlap the heightfield grid. -/// -/// @see rcHeightfield -void rcRasterizeTriangle(rcContext* ctx, const float* v0, const float* v1, const float* v2, - const unsigned char area, rcHeightfield& solid, - const int flagMergeThr) -{ - rcAssert(ctx); - - ctx->startTimer(RC_TIMER_RASTERIZE_TRIANGLES); - - const float ics = 1.0f/solid.cs; - const float ich = 1.0f/solid.ch; - rasterizeTri(v0, v1, v2, area, solid, solid.bmin, solid.bmax, solid.cs, ics, ich, flagMergeThr); - - ctx->stopTimer(RC_TIMER_RASTERIZE_TRIANGLES); -} - -/// @par -/// -/// Spans will only be added for triangles that overlap the heightfield grid. -/// -/// @see rcHeightfield -void rcRasterizeTriangles(rcContext* ctx, const float* verts, const int /*nv*/, - const int* tris, const unsigned char* areas, const int nt, - rcHeightfield& solid, const int flagMergeThr) -{ - rcAssert(ctx); - - ctx->startTimer(RC_TIMER_RASTERIZE_TRIANGLES); - - const float ics = 1.0f/solid.cs; - const float ich = 1.0f/solid.ch; - // Rasterize triangles. - for (int i = 0; i < nt; ++i) - { - const float* v0 = &verts[tris[i*3+0]*3]; - const float* v1 = &verts[tris[i*3+1]*3]; - const float* v2 = &verts[tris[i*3+2]*3]; - // Rasterize. - rasterizeTri(v0, v1, v2, areas[i], solid, solid.bmin, solid.bmax, solid.cs, ics, ich, flagMergeThr); - } - - ctx->stopTimer(RC_TIMER_RASTERIZE_TRIANGLES); -} - -/// @par -/// -/// Spans will only be added for triangles that overlap the heightfield grid. -/// -/// @see rcHeightfield -void rcRasterizeTriangles(rcContext* ctx, const float* verts, const int /*nv*/, - const unsigned short* tris, const unsigned char* areas, const int nt, - rcHeightfield& solid, const int flagMergeThr) -{ - rcAssert(ctx); - - ctx->startTimer(RC_TIMER_RASTERIZE_TRIANGLES); - - const float ics = 1.0f/solid.cs; - const float ich = 1.0f/solid.ch; - // Rasterize triangles. - for (int i = 0; i < nt; ++i) - { - const float* v0 = &verts[tris[i*3+0]*3]; - const float* v1 = &verts[tris[i*3+1]*3]; - const float* v2 = &verts[tris[i*3+2]*3]; - // Rasterize. - rasterizeTri(v0, v1, v2, areas[i], solid, solid.bmin, solid.bmax, solid.cs, ics, ich, flagMergeThr); - } - - ctx->stopTimer(RC_TIMER_RASTERIZE_TRIANGLES); -} - -/// @par -/// -/// Spans will only be added for triangles that overlap the heightfield grid. -/// -/// @see rcHeightfield -void rcRasterizeTriangles(rcContext* ctx, const float* verts, const unsigned char* areas, const int nt, - rcHeightfield& solid, const int flagMergeThr) -{ - rcAssert(ctx); - - ctx->startTimer(RC_TIMER_RASTERIZE_TRIANGLES); - - const float ics = 1.0f/solid.cs; - const float ich = 1.0f/solid.ch; - // Rasterize triangles. - for (int i = 0; i < nt; ++i) - { - const float* v0 = &verts[(i*3+0)*3]; - const float* v1 = &verts[(i*3+1)*3]; - const float* v2 = &verts[(i*3+2)*3]; - // Rasterize. - rasterizeTri(v0, v1, v2, areas[i], solid, solid.bmin, solid.bmax, solid.cs, ics, ich, flagMergeThr); - } - - ctx->stopTimer(RC_TIMER_RASTERIZE_TRIANGLES); -} diff --git a/KREngine/3rdparty/recast/source/RecastRegion.cpp b/KREngine/3rdparty/recast/source/RecastRegion.cpp deleted file mode 100755 index 76e631c..0000000 --- a/KREngine/3rdparty/recast/source/RecastRegion.cpp +++ /dev/null @@ -1,1337 +0,0 @@ -// -// Copyright (c) 2009-2010 Mikko Mononen memon@inside.org -// -// This software is provided 'as-is', without any express or implied -// warranty. In no event will the authors be held liable for any damages -// arising from the use of this software. -// Permission is granted to anyone to use this software for any purpose, -// including commercial applications, and to alter it and redistribute it -// freely, subject to the following restrictions: -// 1. The origin of this software must not be misrepresented; you must not -// claim that you wrote the original software. If you use this software -// in a product, an acknowledgment in the product documentation would be -// appreciated but is not required. -// 2. Altered source versions must be plainly marked as such, and must not be -// misrepresented as being the original software. -// 3. This notice may not be removed or altered from any source distribution. -// - -#include -#define _USE_MATH_DEFINES -#include -#include -#include -#include -#include "Recast.h" -#include "RecastAlloc.h" -#include "RecastAssert.h" -#include - - -static void calculateDistanceField(rcCompactHeightfield& chf, unsigned short* src, unsigned short& maxDist) -{ - const int w = chf.width; - const int h = chf.height; - - // Init distance and points. - for (int i = 0; i < chf.spanCount; ++i) - src[i] = 0xffff; - - // Mark boundary cells. - for (int y = 0; y < h; ++y) - { - for (int x = 0; x < w; ++x) - { - const rcCompactCell& c = chf.cells[x+y*w]; - for (int i = (int)c.index, ni = (int)(c.index+c.count); i < ni; ++i) - { - const rcCompactSpan& s = chf.spans[i]; - const unsigned char area = chf.areas[i]; - - int nc = 0; - for (int dir = 0; dir < 4; ++dir) - { - if (rcGetCon(s, dir) != RC_NOT_CONNECTED) - { - const int ax = x + rcGetDirOffsetX(dir); - const int ay = y + rcGetDirOffsetY(dir); - const int ai = (int)chf.cells[ax+ay*w].index + rcGetCon(s, dir); - if (area == chf.areas[ai]) - nc++; - } - } - if (nc != 4) - src[i] = 0; - } - } - } - - - // Pass 1 - for (int y = 0; y < h; ++y) - { - for (int x = 0; x < w; ++x) - { - const rcCompactCell& c = chf.cells[x+y*w]; - for (int i = (int)c.index, ni = (int)(c.index+c.count); i < ni; ++i) - { - const rcCompactSpan& s = chf.spans[i]; - - if (rcGetCon(s, 0) != RC_NOT_CONNECTED) - { - // (-1,0) - const int ax = x + rcGetDirOffsetX(0); - const int ay = y + rcGetDirOffsetY(0); - const int ai = (int)chf.cells[ax+ay*w].index + rcGetCon(s, 0); - const rcCompactSpan& as = chf.spans[ai]; - if (src[ai]+2 < src[i]) - src[i] = src[ai]+2; - - // (-1,-1) - if (rcGetCon(as, 3) != RC_NOT_CONNECTED) - { - const int aax = ax + rcGetDirOffsetX(3); - const int aay = ay + rcGetDirOffsetY(3); - const int aai = (int)chf.cells[aax+aay*w].index + rcGetCon(as, 3); - if (src[aai]+3 < src[i]) - src[i] = src[aai]+3; - } - } - if (rcGetCon(s, 3) != RC_NOT_CONNECTED) - { - // (0,-1) - const int ax = x + rcGetDirOffsetX(3); - const int ay = y + rcGetDirOffsetY(3); - const int ai = (int)chf.cells[ax+ay*w].index + rcGetCon(s, 3); - const rcCompactSpan& as = chf.spans[ai]; - if (src[ai]+2 < src[i]) - src[i] = src[ai]+2; - - // (1,-1) - if (rcGetCon(as, 2) != RC_NOT_CONNECTED) - { - const int aax = ax + rcGetDirOffsetX(2); - const int aay = ay + rcGetDirOffsetY(2); - const int aai = (int)chf.cells[aax+aay*w].index + rcGetCon(as, 2); - if (src[aai]+3 < src[i]) - src[i] = src[aai]+3; - } - } - } - } - } - - // Pass 2 - for (int y = h-1; y >= 0; --y) - { - for (int x = w-1; x >= 0; --x) - { - const rcCompactCell& c = chf.cells[x+y*w]; - for (int i = (int)c.index, ni = (int)(c.index+c.count); i < ni; ++i) - { - const rcCompactSpan& s = chf.spans[i]; - - if (rcGetCon(s, 2) != RC_NOT_CONNECTED) - { - // (1,0) - const int ax = x + rcGetDirOffsetX(2); - const int ay = y + rcGetDirOffsetY(2); - const int ai = (int)chf.cells[ax+ay*w].index + rcGetCon(s, 2); - const rcCompactSpan& as = chf.spans[ai]; - if (src[ai]+2 < src[i]) - src[i] = src[ai]+2; - - // (1,1) - if (rcGetCon(as, 1) != RC_NOT_CONNECTED) - { - const int aax = ax + rcGetDirOffsetX(1); - const int aay = ay + rcGetDirOffsetY(1); - const int aai = (int)chf.cells[aax+aay*w].index + rcGetCon(as, 1); - if (src[aai]+3 < src[i]) - src[i] = src[aai]+3; - } - } - if (rcGetCon(s, 1) != RC_NOT_CONNECTED) - { - // (0,1) - const int ax = x + rcGetDirOffsetX(1); - const int ay = y + rcGetDirOffsetY(1); - const int ai = (int)chf.cells[ax+ay*w].index + rcGetCon(s, 1); - const rcCompactSpan& as = chf.spans[ai]; - if (src[ai]+2 < src[i]) - src[i] = src[ai]+2; - - // (-1,1) - if (rcGetCon(as, 0) != RC_NOT_CONNECTED) - { - const int aax = ax + rcGetDirOffsetX(0); - const int aay = ay + rcGetDirOffsetY(0); - const int aai = (int)chf.cells[aax+aay*w].index + rcGetCon(as, 0); - if (src[aai]+3 < src[i]) - src[i] = src[aai]+3; - } - } - } - } - } - - maxDist = 0; - for (int i = 0; i < chf.spanCount; ++i) - maxDist = rcMax(src[i], maxDist); - -} - -static unsigned short* boxBlur(rcCompactHeightfield& chf, int thr, - unsigned short* src, unsigned short* dst) -{ - const int w = chf.width; - const int h = chf.height; - - thr *= 2; - - for (int y = 0; y < h; ++y) - { - for (int x = 0; x < w; ++x) - { - const rcCompactCell& c = chf.cells[x+y*w]; - for (int i = (int)c.index, ni = (int)(c.index+c.count); i < ni; ++i) - { - const rcCompactSpan& s = chf.spans[i]; - const unsigned short cd = src[i]; - if (cd <= thr) - { - dst[i] = cd; - continue; - } - - int d = (int)cd; - for (int dir = 0; dir < 4; ++dir) - { - if (rcGetCon(s, dir) != RC_NOT_CONNECTED) - { - const int ax = x + rcGetDirOffsetX(dir); - const int ay = y + rcGetDirOffsetY(dir); - const int ai = (int)chf.cells[ax+ay*w].index + rcGetCon(s, dir); - d += (int)src[ai]; - - const rcCompactSpan& as = chf.spans[ai]; - const int dir2 = (dir+1) & 0x3; - if (rcGetCon(as, dir2) != RC_NOT_CONNECTED) - { - const int ax2 = ax + rcGetDirOffsetX(dir2); - const int ay2 = ay + rcGetDirOffsetY(dir2); - const int ai2 = (int)chf.cells[ax2+ay2*w].index + rcGetCon(as, dir2); - d += (int)src[ai2]; - } - else - { - d += cd; - } - } - else - { - d += cd*2; - } - } - dst[i] = (unsigned short)((d+5)/9); - } - } - } - return dst; -} - - -static bool floodRegion(int x, int y, int i, - unsigned short level, unsigned short r, - rcCompactHeightfield& chf, - unsigned short* srcReg, unsigned short* srcDist, - rcIntArray& stack) -{ - const int w = chf.width; - - const unsigned char area = chf.areas[i]; - - // Flood fill mark region. - stack.resize(0); - stack.push((int)x); - stack.push((int)y); - stack.push((int)i); - srcReg[i] = r; - srcDist[i] = 0; - - unsigned short lev = level >= 2 ? level-2 : 0; - int count = 0; - - while (stack.size() > 0) - { - int ci = stack.pop(); - int cy = stack.pop(); - int cx = stack.pop(); - - const rcCompactSpan& cs = chf.spans[ci]; - - // Check if any of the neighbours already have a valid region set. - unsigned short ar = 0; - for (int dir = 0; dir < 4; ++dir) - { - // 8 connected - if (rcGetCon(cs, dir) != RC_NOT_CONNECTED) - { - const int ax = cx + rcGetDirOffsetX(dir); - const int ay = cy + rcGetDirOffsetY(dir); - const int ai = (int)chf.cells[ax+ay*w].index + rcGetCon(cs, dir); - if (chf.areas[ai] != area) - continue; - unsigned short nr = srcReg[ai]; - if (nr & RC_BORDER_REG) // Do not take borders into account. - continue; - if (nr != 0 && nr != r) - ar = nr; - - const rcCompactSpan& as = chf.spans[ai]; - - const int dir2 = (dir+1) & 0x3; - if (rcGetCon(as, dir2) != RC_NOT_CONNECTED) - { - const int ax2 = ax + rcGetDirOffsetX(dir2); - const int ay2 = ay + rcGetDirOffsetY(dir2); - const int ai2 = (int)chf.cells[ax2+ay2*w].index + rcGetCon(as, dir2); - if (chf.areas[ai2] != area) - continue; - unsigned short nr2 = srcReg[ai2]; - if (nr2 != 0 && nr2 != r) - ar = nr2; - } - } - } - if (ar != 0) - { - srcReg[ci] = 0; - continue; - } - count++; - - // Expand neighbours. - for (int dir = 0; dir < 4; ++dir) - { - if (rcGetCon(cs, dir) != RC_NOT_CONNECTED) - { - const int ax = cx + rcGetDirOffsetX(dir); - const int ay = cy + rcGetDirOffsetY(dir); - const int ai = (int)chf.cells[ax+ay*w].index + rcGetCon(cs, dir); - if (chf.areas[ai] != area) - continue; - if (chf.dist[ai] >= lev && srcReg[ai] == 0) - { - srcReg[ai] = r; - srcDist[ai] = 0; - stack.push(ax); - stack.push(ay); - stack.push(ai); - } - } - } - } - - return count > 0; -} - -static unsigned short* expandRegions(int maxIter, unsigned short level, - rcCompactHeightfield& chf, - unsigned short* srcReg, unsigned short* srcDist, - unsigned short* dstReg, unsigned short* dstDist, - rcIntArray& stack) -{ - const int w = chf.width; - const int h = chf.height; - - // Find cells revealed by the raised level. - stack.resize(0); - for (int y = 0; y < h; ++y) - { - for (int x = 0; x < w; ++x) - { - const rcCompactCell& c = chf.cells[x+y*w]; - for (int i = (int)c.index, ni = (int)(c.index+c.count); i < ni; ++i) - { - if (chf.dist[i] >= level && srcReg[i] == 0 && chf.areas[i] != RC_NULL_AREA) - { - stack.push(x); - stack.push(y); - stack.push(i); - } - } - } - } - - int iter = 0; - while (stack.size() > 0) - { - int failed = 0; - - memcpy(dstReg, srcReg, sizeof(unsigned short)*chf.spanCount); - memcpy(dstDist, srcDist, sizeof(unsigned short)*chf.spanCount); - - for (int j = 0; j < stack.size(); j += 3) - { - int x = stack[j+0]; - int y = stack[j+1]; - int i = stack[j+2]; - if (i < 0) - { - failed++; - continue; - } - - unsigned short r = srcReg[i]; - unsigned short d2 = 0xffff; - const unsigned char area = chf.areas[i]; - const rcCompactSpan& s = chf.spans[i]; - for (int dir = 0; dir < 4; ++dir) - { - if (rcGetCon(s, dir) == RC_NOT_CONNECTED) continue; - const int ax = x + rcGetDirOffsetX(dir); - const int ay = y + rcGetDirOffsetY(dir); - const int ai = (int)chf.cells[ax+ay*w].index + rcGetCon(s, dir); - if (chf.areas[ai] != area) continue; - if (srcReg[ai] > 0 && (srcReg[ai] & RC_BORDER_REG) == 0) - { - if ((int)srcDist[ai]+2 < (int)d2) - { - r = srcReg[ai]; - d2 = srcDist[ai]+2; - } - } - } - if (r) - { - stack[j+2] = -1; // mark as used - dstReg[i] = r; - dstDist[i] = d2; - } - else - { - failed++; - } - } - - // rcSwap source and dest. - rcSwap(srcReg, dstReg); - rcSwap(srcDist, dstDist); - - if (failed*3 == stack.size()) - break; - - if (level > 0) - { - ++iter; - if (iter >= maxIter) - break; - } - } - - return srcReg; -} - - -struct rcRegion -{ - inline rcRegion(unsigned short i) : - spanCount(0), - id(i), - areaType(0), - remap(false), - visited(false) - {} - - int spanCount; // Number of spans belonging to this region - unsigned short id; // ID of the region - unsigned char areaType; // Are type. - bool remap; - bool visited; - rcIntArray connections; - rcIntArray floors; -}; - -static void removeAdjacentNeighbours(rcRegion& reg) -{ - // Remove adjacent duplicates. - for (int i = 0; i < reg.connections.size() && reg.connections.size() > 1; ) - { - int ni = (i+1) % reg.connections.size(); - if (reg.connections[i] == reg.connections[ni]) - { - // Remove duplicate - for (int j = i; j < reg.connections.size()-1; ++j) - reg.connections[j] = reg.connections[j+1]; - reg.connections.pop(); - } - else - ++i; - } -} - -static void replaceNeighbour(rcRegion& reg, unsigned short oldId, unsigned short newId) -{ - bool neiChanged = false; - for (int i = 0; i < reg.connections.size(); ++i) - { - if (reg.connections[i] == oldId) - { - reg.connections[i] = newId; - neiChanged = true; - } - } - for (int i = 0; i < reg.floors.size(); ++i) - { - if (reg.floors[i] == oldId) - reg.floors[i] = newId; - } - if (neiChanged) - removeAdjacentNeighbours(reg); -} - -static bool canMergeWithRegion(const rcRegion& rega, const rcRegion& regb) -{ - if (rega.areaType != regb.areaType) - return false; - int n = 0; - for (int i = 0; i < rega.connections.size(); ++i) - { - if (rega.connections[i] == regb.id) - n++; - } - if (n > 1) - return false; - for (int i = 0; i < rega.floors.size(); ++i) - { - if (rega.floors[i] == regb.id) - return false; - } - return true; -} - -static void addUniqueFloorRegion(rcRegion& reg, int n) -{ - for (int i = 0; i < reg.floors.size(); ++i) - if (reg.floors[i] == n) - return; - reg.floors.push(n); -} - -static bool mergeRegions(rcRegion& rega, rcRegion& regb) -{ - unsigned short aid = rega.id; - unsigned short bid = regb.id; - - // Duplicate current neighbourhood. - rcIntArray acon; - acon.resize(rega.connections.size()); - for (int i = 0; i < rega.connections.size(); ++i) - acon[i] = rega.connections[i]; - rcIntArray& bcon = regb.connections; - - // Find insertion point on A. - int insa = -1; - for (int i = 0; i < acon.size(); ++i) - { - if (acon[i] == bid) - { - insa = i; - break; - } - } - if (insa == -1) - return false; - - // Find insertion point on B. - int insb = -1; - for (int i = 0; i < bcon.size(); ++i) - { - if (bcon[i] == aid) - { - insb = i; - break; - } - } - if (insb == -1) - return false; - - // Merge neighbours. - rega.connections.resize(0); - for (int i = 0, ni = acon.size(); i < ni-1; ++i) - rega.connections.push(acon[(insa+1+i) % ni]); - - for (int i = 0, ni = bcon.size(); i < ni-1; ++i) - rega.connections.push(bcon[(insb+1+i) % ni]); - - removeAdjacentNeighbours(rega); - - for (int j = 0; j < regb.floors.size(); ++j) - addUniqueFloorRegion(rega, regb.floors[j]); - rega.spanCount += regb.spanCount; - regb.spanCount = 0; - regb.connections.resize(0); - - return true; -} - -static bool isRegionConnectedToBorder(const rcRegion& reg) -{ - // Region is connected to border if - // one of the neighbours is null id. - for (int i = 0; i < reg.connections.size(); ++i) - { - if (reg.connections[i] == 0) - return true; - } - return false; -} - -static bool isSolidEdge(rcCompactHeightfield& chf, unsigned short* srcReg, - int x, int y, int i, int dir) -{ - const rcCompactSpan& s = chf.spans[i]; - unsigned short r = 0; - if (rcGetCon(s, dir) != RC_NOT_CONNECTED) - { - const int ax = x + rcGetDirOffsetX(dir); - const int ay = y + rcGetDirOffsetY(dir); - const int ai = (int)chf.cells[ax+ay*chf.width].index + rcGetCon(s, dir); - r = srcReg[ai]; - } - if (r == srcReg[i]) - return false; - return true; -} - -static void walkContour(int x, int y, int i, int dir, - rcCompactHeightfield& chf, - unsigned short* srcReg, - rcIntArray& cont) -{ - int startDir = dir; - int starti = i; - - const rcCompactSpan& ss = chf.spans[i]; - unsigned short curReg = 0; - if (rcGetCon(ss, dir) != RC_NOT_CONNECTED) - { - const int ax = x + rcGetDirOffsetX(dir); - const int ay = y + rcGetDirOffsetY(dir); - const int ai = (int)chf.cells[ax+ay*chf.width].index + rcGetCon(ss, dir); - curReg = srcReg[ai]; - } - cont.push(curReg); - - int iter = 0; - while (++iter < 40000) - { - const rcCompactSpan& s = chf.spans[i]; - - if (isSolidEdge(chf, srcReg, x, y, i, dir)) - { - // Choose the edge corner - unsigned short r = 0; - if (rcGetCon(s, dir) != RC_NOT_CONNECTED) - { - const int ax = x + rcGetDirOffsetX(dir); - const int ay = y + rcGetDirOffsetY(dir); - const int ai = (int)chf.cells[ax+ay*chf.width].index + rcGetCon(s, dir); - r = srcReg[ai]; - } - if (r != curReg) - { - curReg = r; - cont.push(curReg); - } - - dir = (dir+1) & 0x3; // Rotate CW - } - else - { - int ni = -1; - const int nx = x + rcGetDirOffsetX(dir); - const int ny = y + rcGetDirOffsetY(dir); - if (rcGetCon(s, dir) != RC_NOT_CONNECTED) - { - const rcCompactCell& nc = chf.cells[nx+ny*chf.width]; - ni = (int)nc.index + rcGetCon(s, dir); - } - if (ni == -1) - { - // Should not happen. - return; - } - x = nx; - y = ny; - i = ni; - dir = (dir+3) & 0x3; // Rotate CCW - } - - if (starti == i && startDir == dir) - { - break; - } - } - - // Remove adjacent duplicates. - if (cont.size() > 1) - { - for (int j = 0; j < cont.size(); ) - { - int nj = (j+1) % cont.size(); - if (cont[j] == cont[nj]) - { - for (int k = j; k < cont.size()-1; ++k) - cont[k] = cont[k+1]; - cont.pop(); - } - else - ++j; - } - } -} - -static bool filterSmallRegions(rcContext* ctx, int minRegionArea, int mergeRegionSize, - unsigned short& maxRegionId, - rcCompactHeightfield& chf, - unsigned short* srcReg) -{ - const int w = chf.width; - const int h = chf.height; - - const int nreg = maxRegionId+1; - rcRegion* regions = (rcRegion*)rcAlloc(sizeof(rcRegion)*nreg, RC_ALLOC_TEMP); - if (!regions) - { - ctx->log(RC_LOG_ERROR, "filterSmallRegions: Out of memory 'regions' (%d).", nreg); - return false; - } - - // Construct regions - for (int i = 0; i < nreg; ++i) - new(®ions[i]) rcRegion((unsigned short)i); - - // Find edge of a region and find connections around the contour. - for (int y = 0; y < h; ++y) - { - for (int x = 0; x < w; ++x) - { - const rcCompactCell& c = chf.cells[x+y*w]; - for (int i = (int)c.index, ni = (int)(c.index+c.count); i < ni; ++i) - { - unsigned short r = srcReg[i]; - if (r == 0 || r >= nreg) - continue; - - rcRegion& reg = regions[r]; - reg.spanCount++; - - - // Update floors. - for (int j = (int)c.index; j < ni; ++j) - { - if (i == j) continue; - unsigned short floorId = srcReg[j]; - if (floorId == 0 || floorId >= nreg) - continue; - addUniqueFloorRegion(reg, floorId); - } - - // Have found contour - if (reg.connections.size() > 0) - continue; - - reg.areaType = chf.areas[i]; - - // Check if this cell is next to a border. - int ndir = -1; - for (int dir = 0; dir < 4; ++dir) - { - if (isSolidEdge(chf, srcReg, x, y, i, dir)) - { - ndir = dir; - break; - } - } - - if (ndir != -1) - { - // The cell is at border. - // Walk around the contour to find all the neighbours. - walkContour(x, y, i, ndir, chf, srcReg, reg.connections); - } - } - } - } - - // Remove too small regions. - rcIntArray stack(32); - rcIntArray trace(32); - for (int i = 0; i < nreg; ++i) - { - rcRegion& reg = regions[i]; - if (reg.id == 0 || (reg.id & RC_BORDER_REG)) - continue; - if (reg.spanCount == 0) - continue; - if (reg.visited) - continue; - - // Count the total size of all the connected regions. - // Also keep track of the regions connects to a tile border. - bool connectsToBorder = false; - int spanCount = 0; - stack.resize(0); - trace.resize(0); - - reg.visited = true; - stack.push(i); - - while (stack.size()) - { - // Pop - int ri = stack.pop(); - - rcRegion& creg = regions[ri]; - - spanCount += creg.spanCount; - trace.push(ri); - - for (int j = 0; j < creg.connections.size(); ++j) - { - if (creg.connections[j] & RC_BORDER_REG) - { - connectsToBorder = true; - continue; - } - rcRegion& neireg = regions[creg.connections[j]]; - if (neireg.visited) - continue; - if (neireg.id == 0 || (neireg.id & RC_BORDER_REG)) - continue; - // Visit - stack.push(neireg.id); - neireg.visited = true; - } - } - - // If the accumulated regions size is too small, remove it. - // Do not remove areas which connect to tile borders - // as their size cannot be estimated correctly and removing them - // can potentially remove necessary areas. - if (spanCount < minRegionArea && !connectsToBorder) - { - // Kill all visited regions. - for (int j = 0; j < trace.size(); ++j) - { - regions[trace[j]].spanCount = 0; - regions[trace[j]].id = 0; - } - } - } - - // Merge too small regions to neighbour regions. - int mergeCount = 0 ; - do - { - mergeCount = 0; - for (int i = 0; i < nreg; ++i) - { - rcRegion& reg = regions[i]; - if (reg.id == 0 || (reg.id & RC_BORDER_REG)) - continue; - if (reg.spanCount == 0) - continue; - - // Check to see if the region should be merged. - if (reg.spanCount > mergeRegionSize && isRegionConnectedToBorder(reg)) - continue; - - // Small region with more than 1 connection. - // Or region which is not connected to a border at all. - // Find smallest neighbour region that connects to this one. - int smallest = 0xfffffff; - unsigned short mergeId = reg.id; - for (int j = 0; j < reg.connections.size(); ++j) - { - if (reg.connections[j] & RC_BORDER_REG) continue; - rcRegion& mreg = regions[reg.connections[j]]; - if (mreg.id == 0 || (mreg.id & RC_BORDER_REG)) continue; - if (mreg.spanCount < smallest && - canMergeWithRegion(reg, mreg) && - canMergeWithRegion(mreg, reg)) - { - smallest = mreg.spanCount; - mergeId = mreg.id; - } - } - // Found new id. - if (mergeId != reg.id) - { - unsigned short oldId = reg.id; - rcRegion& target = regions[mergeId]; - - // Merge neighbours. - if (mergeRegions(target, reg)) - { - // Fixup regions pointing to current region. - for (int j = 0; j < nreg; ++j) - { - if (regions[j].id == 0 || (regions[j].id & RC_BORDER_REG)) continue; - // If another region was already merged into current region - // change the nid of the previous region too. - if (regions[j].id == oldId) - regions[j].id = mergeId; - // Replace the current region with the new one if the - // current regions is neighbour. - replaceNeighbour(regions[j], oldId, mergeId); - } - mergeCount++; - } - } - } - } - while (mergeCount > 0); - - // Compress region Ids. - for (int i = 0; i < nreg; ++i) - { - regions[i].remap = false; - if (regions[i].id == 0) continue; // Skip nil regions. - if (regions[i].id & RC_BORDER_REG) continue; // Skip external regions. - regions[i].remap = true; - } - - unsigned short regIdGen = 0; - for (int i = 0; i < nreg; ++i) - { - if (!regions[i].remap) - continue; - unsigned short oldId = regions[i].id; - unsigned short newId = ++regIdGen; - for (int j = i; j < nreg; ++j) - { - if (regions[j].id == oldId) - { - regions[j].id = newId; - regions[j].remap = false; - } - } - } - maxRegionId = regIdGen; - - // Remap regions. - for (int i = 0; i < chf.spanCount; ++i) - { - if ((srcReg[i] & RC_BORDER_REG) == 0) - srcReg[i] = regions[srcReg[i]].id; - } - - for (int i = 0; i < nreg; ++i) - regions[i].~rcRegion(); - rcFree(regions); - - return true; -} - -/// @par -/// -/// This is usually the second to the last step in creating a fully built -/// compact heightfield. This step is required before regions are built -/// using #rcBuildRegions or #rcBuildRegionsMonotone. -/// -/// After this step, the distance data is available via the rcCompactHeightfield::maxDistance -/// and rcCompactHeightfield::dist fields. -/// -/// @see rcCompactHeightfield, rcBuildRegions, rcBuildRegionsMonotone -bool rcBuildDistanceField(rcContext* ctx, rcCompactHeightfield& chf) -{ - rcAssert(ctx); - - ctx->startTimer(RC_TIMER_BUILD_DISTANCEFIELD); - - if (chf.dist) - { - rcFree(chf.dist); - chf.dist = 0; - } - - unsigned short* src = (unsigned short*)rcAlloc(sizeof(unsigned short)*chf.spanCount, RC_ALLOC_TEMP); - if (!src) - { - ctx->log(RC_LOG_ERROR, "rcBuildDistanceField: Out of memory 'src' (%d).", chf.spanCount); - return false; - } - unsigned short* dst = (unsigned short*)rcAlloc(sizeof(unsigned short)*chf.spanCount, RC_ALLOC_TEMP); - if (!dst) - { - ctx->log(RC_LOG_ERROR, "rcBuildDistanceField: Out of memory 'dst' (%d).", chf.spanCount); - rcFree(src); - return false; - } - - unsigned short maxDist = 0; - - ctx->startTimer(RC_TIMER_BUILD_DISTANCEFIELD_DIST); - - calculateDistanceField(chf, src, maxDist); - chf.maxDistance = maxDist; - - ctx->stopTimer(RC_TIMER_BUILD_DISTANCEFIELD_DIST); - - ctx->startTimer(RC_TIMER_BUILD_DISTANCEFIELD_BLUR); - - // Blur - if (boxBlur(chf, 1, src, dst) != src) - rcSwap(src, dst); - - // Store distance. - chf.dist = src; - - ctx->stopTimer(RC_TIMER_BUILD_DISTANCEFIELD_BLUR); - - ctx->stopTimer(RC_TIMER_BUILD_DISTANCEFIELD); - - rcFree(dst); - - return true; -} - -static void paintRectRegion(int minx, int maxx, int miny, int maxy, unsigned short regId, - rcCompactHeightfield& chf, unsigned short* srcReg) -{ - const int w = chf.width; - for (int y = miny; y < maxy; ++y) - { - for (int x = minx; x < maxx; ++x) - { - const rcCompactCell& c = chf.cells[x+y*w]; - for (int i = (int)c.index, ni = (int)(c.index+c.count); i < ni; ++i) - { - if (chf.areas[i] != RC_NULL_AREA) - srcReg[i] = regId; - } - } - } -} - - -static const unsigned short RC_NULL_NEI = 0xffff; - -struct rcSweepSpan -{ - unsigned short rid; // row id - unsigned short id; // region id - unsigned short ns; // number samples - unsigned short nei; // neighbour id -}; - -/// @par -/// -/// Non-null regions will consist of connected, non-overlapping walkable spans that form a single contour. -/// Contours will form simple polygons. -/// -/// If multiple regions form an area that is smaller than @p minRegionArea, then all spans will be -/// re-assigned to the zero (null) region. -/// -/// Partitioning can result in smaller than necessary regions. @p mergeRegionArea helps -/// reduce unecessarily small regions. -/// -/// See the #rcConfig documentation for more information on the configuration parameters. -/// -/// The region data will be available via the rcCompactHeightfield::maxRegions -/// and rcCompactSpan::reg fields. -/// -/// @warning The distance field must be created using #rcBuildDistanceField before attempting to build regions. -/// -/// @see rcCompactHeightfield, rcCompactSpan, rcBuildDistanceField, rcBuildRegionsMonotone, rcConfig -bool rcBuildRegionsMonotone(rcContext* ctx, rcCompactHeightfield& chf, - const int borderSize, const int minRegionArea, const int mergeRegionArea) -{ - rcAssert(ctx); - - ctx->startTimer(RC_TIMER_BUILD_REGIONS); - - const int w = chf.width; - const int h = chf.height; - unsigned short id = 1; - - rcScopedDelete srcReg = (unsigned short*)rcAlloc(sizeof(unsigned short)*chf.spanCount, RC_ALLOC_TEMP); - if (!srcReg) - { - ctx->log(RC_LOG_ERROR, "rcBuildRegionsMonotone: Out of memory 'src' (%d).", chf.spanCount); - return false; - } - memset(srcReg,0,sizeof(unsigned short)*chf.spanCount); - - const int nsweeps = rcMax(chf.width,chf.height); - rcScopedDelete sweeps = (rcSweepSpan*)rcAlloc(sizeof(rcSweepSpan)*nsweeps, RC_ALLOC_TEMP); - if (!sweeps) - { - ctx->log(RC_LOG_ERROR, "rcBuildRegionsMonotone: Out of memory 'sweeps' (%d).", nsweeps); - return false; - } - - - // Mark border regions. - if (borderSize > 0) - { - // Make sure border will not overflow. - const int bw = rcMin(w, borderSize); - const int bh = rcMin(h, borderSize); - // Paint regions - paintRectRegion(0, bw, 0, h, id|RC_BORDER_REG, chf, srcReg); id++; - paintRectRegion(w-bw, w, 0, h, id|RC_BORDER_REG, chf, srcReg); id++; - paintRectRegion(0, w, 0, bh, id|RC_BORDER_REG, chf, srcReg); id++; - paintRectRegion(0, w, h-bh, h, id|RC_BORDER_REG, chf, srcReg); id++; - - chf.borderSize = borderSize; - } - - rcIntArray prev(256); - - // Sweep one line at a time. - for (int y = borderSize; y < h-borderSize; ++y) - { - // Collect spans from this row. - prev.resize(id+1); - memset(&prev[0],0,sizeof(int)*id); - unsigned short rid = 1; - - for (int x = borderSize; x < w-borderSize; ++x) - { - const rcCompactCell& c = chf.cells[x+y*w]; - - for (int i = (int)c.index, ni = (int)(c.index+c.count); i < ni; ++i) - { - const rcCompactSpan& s = chf.spans[i]; - if (chf.areas[i] == RC_NULL_AREA) continue; - - // -x - unsigned short previd = 0; - if (rcGetCon(s, 0) != RC_NOT_CONNECTED) - { - const int ax = x + rcGetDirOffsetX(0); - const int ay = y + rcGetDirOffsetY(0); - const int ai = (int)chf.cells[ax+ay*w].index + rcGetCon(s, 0); - if ((srcReg[ai] & RC_BORDER_REG) == 0 && chf.areas[i] == chf.areas[ai]) - previd = srcReg[ai]; - } - - if (!previd) - { - previd = rid++; - sweeps[previd].rid = previd; - sweeps[previd].ns = 0; - sweeps[previd].nei = 0; - } - - // -y - if (rcGetCon(s,3) != RC_NOT_CONNECTED) - { - const int ax = x + rcGetDirOffsetX(3); - const int ay = y + rcGetDirOffsetY(3); - const int ai = (int)chf.cells[ax+ay*w].index + rcGetCon(s, 3); - if (srcReg[ai] && (srcReg[ai] & RC_BORDER_REG) == 0 && chf.areas[i] == chf.areas[ai]) - { - unsigned short nr = srcReg[ai]; - if (!sweeps[previd].nei || sweeps[previd].nei == nr) - { - sweeps[previd].nei = nr; - sweeps[previd].ns++; - prev[nr]++; - } - else - { - sweeps[previd].nei = RC_NULL_NEI; - } - } - } - - srcReg[i] = previd; - } - } - - // Create unique ID. - for (int i = 1; i < rid; ++i) - { - if (sweeps[i].nei != RC_NULL_NEI && sweeps[i].nei != 0 && - prev[sweeps[i].nei] == (int)sweeps[i].ns) - { - sweeps[i].id = sweeps[i].nei; - } - else - { - sweeps[i].id = id++; - } - } - - // Remap IDs - for (int x = borderSize; x < w-borderSize; ++x) - { - const rcCompactCell& c = chf.cells[x+y*w]; - - for (int i = (int)c.index, ni = (int)(c.index+c.count); i < ni; ++i) - { - if (srcReg[i] > 0 && srcReg[i] < rid) - srcReg[i] = sweeps[srcReg[i]].id; - } - } - } - - ctx->startTimer(RC_TIMER_BUILD_REGIONS_FILTER); - - // Filter out small regions. - chf.maxRegions = id; - if (!filterSmallRegions(ctx, minRegionArea, mergeRegionArea, chf.maxRegions, chf, srcReg)) - return false; - - ctx->stopTimer(RC_TIMER_BUILD_REGIONS_FILTER); - - // Store the result out. - for (int i = 0; i < chf.spanCount; ++i) - chf.spans[i].reg = srcReg[i]; - - ctx->stopTimer(RC_TIMER_BUILD_REGIONS); - - return true; -} - -/// @par -/// -/// Non-null regions will consist of connected, non-overlapping walkable spans that form a single contour. -/// Contours will form simple polygons. -/// -/// If multiple regions form an area that is smaller than @p minRegionArea, then all spans will be -/// re-assigned to the zero (null) region. -/// -/// Watershed partitioning can result in smaller than necessary regions, especially in diagonal corridors. -/// @p mergeRegionArea helps reduce unecessarily small regions. -/// -/// See the #rcConfig documentation for more information on the configuration parameters. -/// -/// The region data will be available via the rcCompactHeightfield::maxRegions -/// and rcCompactSpan::reg fields. -/// -/// @warning The distance field must be created using #rcBuildDistanceField before attempting to build regions. -/// -/// @see rcCompactHeightfield, rcCompactSpan, rcBuildDistanceField, rcBuildRegionsMonotone, rcConfig -bool rcBuildRegions(rcContext* ctx, rcCompactHeightfield& chf, - const int borderSize, const int minRegionArea, const int mergeRegionArea) -{ - rcAssert(ctx); - - ctx->startTimer(RC_TIMER_BUILD_REGIONS); - - const int w = chf.width; - const int h = chf.height; - - rcScopedDelete buf = (unsigned short*)rcAlloc(sizeof(unsigned short)*chf.spanCount*4, RC_ALLOC_TEMP); - if (!buf) - { - ctx->log(RC_LOG_ERROR, "rcBuildRegions: Out of memory 'tmp' (%d).", chf.spanCount*4); - return false; - } - - ctx->startTimer(RC_TIMER_BUILD_REGIONS_WATERSHED); - - rcIntArray stack(1024); - rcIntArray visited(1024); - - unsigned short* srcReg = buf; - unsigned short* srcDist = buf+chf.spanCount; - unsigned short* dstReg = buf+chf.spanCount*2; - unsigned short* dstDist = buf+chf.spanCount*3; - - memset(srcReg, 0, sizeof(unsigned short)*chf.spanCount); - memset(srcDist, 0, sizeof(unsigned short)*chf.spanCount); - - unsigned short regionId = 1; - unsigned short level = (chf.maxDistance+1) & ~1; - - // TODO: Figure better formula, expandIters defines how much the - // watershed "overflows" and simplifies the regions. Tying it to - // agent radius was usually good indication how greedy it could be. -// const int expandIters = 4 + walkableRadius * 2; - const int expandIters = 8; - - if (borderSize > 0) - { - // Make sure border will not overflow. - const int bw = rcMin(w, borderSize); - const int bh = rcMin(h, borderSize); - // Paint regions - paintRectRegion(0, bw, 0, h, regionId|RC_BORDER_REG, chf, srcReg); regionId++; - paintRectRegion(w-bw, w, 0, h, regionId|RC_BORDER_REG, chf, srcReg); regionId++; - paintRectRegion(0, w, 0, bh, regionId|RC_BORDER_REG, chf, srcReg); regionId++; - paintRectRegion(0, w, h-bh, h, regionId|RC_BORDER_REG, chf, srcReg); regionId++; - - chf.borderSize = borderSize; - } - - while (level > 0) - { - level = level >= 2 ? level-2 : 0; - - ctx->startTimer(RC_TIMER_BUILD_REGIONS_EXPAND); - - // Expand current regions until no empty connected cells found. - if (expandRegions(expandIters, level, chf, srcReg, srcDist, dstReg, dstDist, stack) != srcReg) - { - rcSwap(srcReg, dstReg); - rcSwap(srcDist, dstDist); - } - - ctx->stopTimer(RC_TIMER_BUILD_REGIONS_EXPAND); - - ctx->startTimer(RC_TIMER_BUILD_REGIONS_FLOOD); - - // Mark new regions with IDs. - for (int y = 0; y < h; ++y) - { - for (int x = 0; x < w; ++x) - { - const rcCompactCell& c = chf.cells[x+y*w]; - for (int i = (int)c.index, ni = (int)(c.index+c.count); i < ni; ++i) - { - if (chf.dist[i] < level || srcReg[i] != 0 || chf.areas[i] == RC_NULL_AREA) - continue; - if (floodRegion(x, y, i, level, regionId, chf, srcReg, srcDist, stack)) - regionId++; - } - } - } - - ctx->stopTimer(RC_TIMER_BUILD_REGIONS_FLOOD); - } - - // Expand current regions until no empty connected cells found. - if (expandRegions(expandIters*8, 0, chf, srcReg, srcDist, dstReg, dstDist, stack) != srcReg) - { - rcSwap(srcReg, dstReg); - rcSwap(srcDist, dstDist); - } - - ctx->stopTimer(RC_TIMER_BUILD_REGIONS_WATERSHED); - - ctx->startTimer(RC_TIMER_BUILD_REGIONS_FILTER); - - // Filter out small regions. - chf.maxRegions = regionId; - if (!filterSmallRegions(ctx, minRegionArea, mergeRegionArea, chf.maxRegions, chf, srcReg)) - return false; - - ctx->stopTimer(RC_TIMER_BUILD_REGIONS_FILTER); - - // Write the result out. - for (int i = 0; i < chf.spanCount; ++i) - chf.spans[i].reg = srcReg[i]; - - ctx->stopTimer(RC_TIMER_BUILD_REGIONS); - - return true; -} - - diff --git a/KREngine/Kraken.xcodeproj/project.pbxproj b/KREngine/Kraken.xcodeproj/project.pbxproj index 44f78fe..63028e0 100644 --- a/KREngine/Kraken.xcodeproj/project.pbxproj +++ b/KREngine/Kraken.xcodeproj/project.pbxproj @@ -44,32 +44,6 @@ E416AA9A16713749000F6786 /* KRAnimationCurveManager.h in Headers */ = {isa = PBXBuildFile; fileRef = E416AA9816713749000F6786 /* KRAnimationCurveManager.h */; settings = {ATTRIBUTES = (Public, ); }; }; E416AA9C1671375C000F6786 /* KRAnimationCurveManager.cpp in Sources */ = {isa = PBXBuildFile; fileRef = E416AA9B1671375C000F6786 /* KRAnimationCurveManager.cpp */; }; E416AA9D1671375C000F6786 /* KRAnimationCurveManager.cpp in Sources */ = {isa = PBXBuildFile; fileRef = E416AA9B1671375C000F6786 /* KRAnimationCurveManager.cpp */; }; - E416E4881879111300FC2EEB /* Recast.h in Headers */ = {isa = PBXBuildFile; fileRef = E416E4851879111300FC2EEB /* Recast.h */; }; - E416E4891879111300FC2EEB /* Recast.h in Headers */ = {isa = PBXBuildFile; fileRef = E416E4851879111300FC2EEB /* Recast.h */; settings = {ATTRIBUTES = (Public, ); }; }; - E416E48A1879111300FC2EEB /* RecastAlloc.h in Headers */ = {isa = PBXBuildFile; fileRef = E416E4861879111300FC2EEB /* RecastAlloc.h */; }; - E416E48B1879111300FC2EEB /* RecastAlloc.h in Headers */ = {isa = PBXBuildFile; fileRef = E416E4861879111300FC2EEB /* RecastAlloc.h */; settings = {ATTRIBUTES = (Public, ); }; }; - E416E48C1879111300FC2EEB /* RecastAssert.h in Headers */ = {isa = PBXBuildFile; fileRef = E416E4871879111300FC2EEB /* RecastAssert.h */; }; - E416E48D1879111300FC2EEB /* RecastAssert.h in Headers */ = {isa = PBXBuildFile; fileRef = E416E4871879111300FC2EEB /* RecastAssert.h */; settings = {ATTRIBUTES = (Public, ); }; }; - E416E4981879112700FC2EEB /* Recast.cpp in Sources */ = {isa = PBXBuildFile; fileRef = E416E48E1879112700FC2EEB /* Recast.cpp */; }; - E416E4991879112700FC2EEB /* Recast.cpp in Sources */ = {isa = PBXBuildFile; fileRef = E416E48E1879112700FC2EEB /* Recast.cpp */; }; - E416E49A1879112700FC2EEB /* RecastAlloc.cpp in Sources */ = {isa = PBXBuildFile; fileRef = E416E48F1879112700FC2EEB /* RecastAlloc.cpp */; }; - E416E49B1879112700FC2EEB /* RecastAlloc.cpp in Sources */ = {isa = PBXBuildFile; fileRef = E416E48F1879112700FC2EEB /* RecastAlloc.cpp */; }; - E416E49C1879112700FC2EEB /* RecastArea.cpp in Sources */ = {isa = PBXBuildFile; fileRef = E416E4901879112700FC2EEB /* RecastArea.cpp */; }; - E416E49D1879112700FC2EEB /* RecastArea.cpp in Sources */ = {isa = PBXBuildFile; fileRef = E416E4901879112700FC2EEB /* RecastArea.cpp */; }; - E416E49E1879112700FC2EEB /* RecastContour.cpp in Sources */ = {isa = PBXBuildFile; fileRef = E416E4911879112700FC2EEB /* RecastContour.cpp */; }; - E416E49F1879112700FC2EEB /* RecastContour.cpp in Sources */ = {isa = PBXBuildFile; fileRef = E416E4911879112700FC2EEB /* RecastContour.cpp */; }; - E416E4A01879112700FC2EEB /* RecastFilter.cpp in Sources */ = {isa = PBXBuildFile; fileRef = E416E4921879112700FC2EEB /* RecastFilter.cpp */; }; - E416E4A11879112700FC2EEB /* RecastFilter.cpp in Sources */ = {isa = PBXBuildFile; fileRef = E416E4921879112700FC2EEB /* RecastFilter.cpp */; }; - E416E4A21879112700FC2EEB /* RecastLayers.cpp in Sources */ = {isa = PBXBuildFile; fileRef = E416E4931879112700FC2EEB /* RecastLayers.cpp */; }; - E416E4A31879112700FC2EEB /* RecastLayers.cpp in Sources */ = {isa = PBXBuildFile; fileRef = E416E4931879112700FC2EEB /* RecastLayers.cpp */; }; - E416E4A41879112700FC2EEB /* RecastMesh.cpp in Sources */ = {isa = PBXBuildFile; fileRef = E416E4941879112700FC2EEB /* RecastMesh.cpp */; }; - E416E4A51879112700FC2EEB /* RecastMesh.cpp in Sources */ = {isa = PBXBuildFile; fileRef = E416E4941879112700FC2EEB /* RecastMesh.cpp */; }; - E416E4A61879112700FC2EEB /* RecastMeshDetail.cpp in Sources */ = {isa = PBXBuildFile; fileRef = E416E4951879112700FC2EEB /* RecastMeshDetail.cpp */; }; - E416E4A71879112700FC2EEB /* RecastMeshDetail.cpp in Sources */ = {isa = PBXBuildFile; fileRef = E416E4951879112700FC2EEB /* RecastMeshDetail.cpp */; }; - E416E4A81879112700FC2EEB /* RecastRasterization.cpp in Sources */ = {isa = PBXBuildFile; fileRef = E416E4961879112700FC2EEB /* RecastRasterization.cpp */; }; - E416E4A91879112700FC2EEB /* RecastRasterization.cpp in Sources */ = {isa = PBXBuildFile; fileRef = E416E4961879112700FC2EEB /* RecastRasterization.cpp */; }; - E416E4AA1879112700FC2EEB /* RecastRegion.cpp in Sources */ = {isa = PBXBuildFile; fileRef = E416E4971879112700FC2EEB /* RecastRegion.cpp */; }; - E416E4AB1879112700FC2EEB /* RecastRegion.cpp in Sources */ = {isa = PBXBuildFile; fileRef = E416E4971879112700FC2EEB /* RecastRegion.cpp */; }; E41843921678704000DBD6CF /* KRCollider.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 104A335C1672D31B001C8BA6 /* KRCollider.cpp */; }; E41B6BA816BE436100B510EB /* CoreAudio.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = E41B6BA716BE436100B510EB /* CoreAudio.framework */; }; E41B6BAA16BE437800B510EB /* CoreAudio.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = E41B6BA916BE437800B510EB /* CoreAudio.framework */; }; @@ -470,19 +444,6 @@ E414F9AB1694DA37000B3D58 /* KRUnknown.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; lineEnding = 0; path = KRUnknown.h; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.objcpp; }; E416AA9816713749000F6786 /* KRAnimationCurveManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = KRAnimationCurveManager.h; sourceTree = ""; }; E416AA9B1671375C000F6786 /* KRAnimationCurveManager.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = KRAnimationCurveManager.cpp; sourceTree = ""; }; - E416E4851879111300FC2EEB /* Recast.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Recast.h; sourceTree = ""; }; - E416E4861879111300FC2EEB /* RecastAlloc.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RecastAlloc.h; sourceTree = ""; }; - E416E4871879111300FC2EEB /* RecastAssert.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RecastAssert.h; sourceTree = ""; }; - E416E48E1879112700FC2EEB /* Recast.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = Recast.cpp; sourceTree = ""; }; - E416E48F1879112700FC2EEB /* RecastAlloc.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = RecastAlloc.cpp; sourceTree = ""; }; - E416E4901879112700FC2EEB /* RecastArea.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = RecastArea.cpp; sourceTree = ""; }; - E416E4911879112700FC2EEB /* RecastContour.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = RecastContour.cpp; sourceTree = ""; }; - E416E4921879112700FC2EEB /* RecastFilter.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = RecastFilter.cpp; sourceTree = ""; }; - E416E4931879112700FC2EEB /* RecastLayers.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = RecastLayers.cpp; sourceTree = ""; }; - E416E4941879112700FC2EEB /* RecastMesh.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = RecastMesh.cpp; sourceTree = ""; }; - E416E4951879112700FC2EEB /* RecastMeshDetail.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = RecastMeshDetail.cpp; sourceTree = ""; }; - E416E4961879112700FC2EEB /* RecastRasterization.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = RecastRasterization.cpp; sourceTree = ""; }; - E416E4971879112700FC2EEB /* RecastRegion.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = RecastRegion.cpp; sourceTree = ""; }; E41AE1DD16B124CA00980428 /* font.tga */ = {isa = PBXFileReference; lastKnownFileType = file; path = font.tga; sourceTree = ""; }; E41B6BA716BE436100B510EB /* CoreAudio.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreAudio.framework; path = System/Library/Frameworks/CoreAudio.framework; sourceTree = SDKROOT; }; E41B6BA916BE437800B510EB /* CoreAudio.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreAudio.framework; path = Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.8.sdk/System/Library/Frameworks/CoreAudio.framework; sourceTree = DEVELOPER_DIR; }; @@ -803,42 +764,6 @@ name = AnimationCurve; sourceTree = ""; }; - E416E482187910DF00FC2EEB /* recast */ = { - isa = PBXGroup; - children = ( - E416E484187910FB00FC2EEB /* include */, - E416E483187910ED00FC2EEB /* source */, - ); - path = recast; - sourceTree = ""; - }; - E416E483187910ED00FC2EEB /* source */ = { - isa = PBXGroup; - children = ( - E416E48E1879112700FC2EEB /* Recast.cpp */, - E416E48F1879112700FC2EEB /* RecastAlloc.cpp */, - E416E4901879112700FC2EEB /* RecastArea.cpp */, - E416E4911879112700FC2EEB /* RecastContour.cpp */, - E416E4921879112700FC2EEB /* RecastFilter.cpp */, - E416E4931879112700FC2EEB /* RecastLayers.cpp */, - E416E4941879112700FC2EEB /* RecastMesh.cpp */, - E416E4951879112700FC2EEB /* RecastMeshDetail.cpp */, - E416E4961879112700FC2EEB /* RecastRasterization.cpp */, - E416E4971879112700FC2EEB /* RecastRegion.cpp */, - ); - path = source; - sourceTree = ""; - }; - E416E484187910FB00FC2EEB /* include */ = { - isa = PBXGroup; - children = ( - E416E4851879111300FC2EEB /* Recast.h */, - E416E4861879111300FC2EEB /* RecastAlloc.h */, - E416E4871879111300FC2EEB /* RecastAssert.h */, - ); - path = include; - sourceTree = ""; - }; E41AE1DF16B125EC00980428 /* Shaders */ = { isa = PBXGroup; children = ( @@ -915,7 +840,6 @@ E45E03A118790D92006DA23F /* 3rdparty */ = { isa = PBXGroup; children = ( - E416E482187910DF00FC2EEB /* recast */, E45E03C218790E55006DA23F /* tinyxml2 */, E45E03C118790E4F006DA23F /* forsyth */, E45E03A218790DA3006DA23F /* pvrtexlib */, @@ -1393,7 +1317,6 @@ E491018A13C99BDC0098455B /* KREngine.h in Headers */, E491018E13C99BDC0098455B /* KRMat4.h in Headers */, E491019B13C99BDC0098455B /* KRMeshManager.h in Headers */, - E416E48C1879111300FC2EEB /* RecastAssert.h in Headers */, E491019213C99BDC0098455B /* KRMesh.h in Headers */, E491019613C99BDC0098455B /* KRVector3.h in Headers */, E47C25A213F4F65A00FF4370 /* KRShaderManager.h in Headers */, @@ -1422,7 +1345,6 @@ E488399E15F92BE000BD66D5 /* KRBundleManager.h in Headers */, E4030E4C160A3CF000592648 /* KRStockGeometry.h in Headers */, E4B175AE161F5A1000B8FB80 /* KRTexture.h in Headers */, - E416E48A1879111300FC2EEB /* RecastAlloc.h in Headers */, E4B175B4161F5FAF00B8FB80 /* KRTextureCube.h in Headers */, E4CA10E51637BD0A005D9400 /* KRTexturePVR.h in Headers */, E4CA10EC1637BD47005D9400 /* KRTextureTGA.h in Headers */, @@ -1456,7 +1378,6 @@ E45E03C918790EC0006DA23F /* tinyxml2.h in Headers */, E4F027D016979CE200D4427D /* KRAudioSample.h in Headers */, E450273B16E0491D00FDEC5C /* KRReverbZone.h in Headers */, - E416E4881879111300FC2EEB /* Recast.h in Headers */, E4AE635F1704FB0A00B460CD /* KRLODGroup.h in Headers */, E4EC73C31720B1FF0065299F /* KRVector4.h in Headers */, E48CF944173453990005EBBB /* KRFloat.h in Headers */, @@ -1534,9 +1455,7 @@ E45E03BA18790DD1006DA23F /* PVRTGlobal.h in Headers */, E45E03B418790DD1006DA23F /* PVRTexture.h in Headers */, E45E03B818790DD1006DA23F /* PVRTextureUtilities.h in Headers */, - E416E48D1879111300FC2EEB /* RecastAssert.h in Headers */, E45E03B918790DD1006DA23F /* PVRTextureVersion.h in Headers */, - E416E4891879111300FC2EEB /* Recast.h in Headers */, E45E03B118790DD1006DA23F /* PVRTArray.h in Headers */, E45E03CA18790EC0006DA23F /* tinyxml2.h in Headers */, E45E03D018790EFF006DA23F /* forsyth.h in Headers */, @@ -1546,7 +1465,6 @@ E45E03B718790DD1006DA23F /* PVRTextureHeader.h in Headers */, E45E03BB18790DD1006DA23F /* PVRTMap.h in Headers */, E45E03BC18790DD1006DA23F /* PVRTString.h in Headers */, - E416E48B1879111300FC2EEB /* RecastAlloc.h in Headers */, E499BF2216AE760F007FCDBE /* krengine_osx.h in Headers */, E4C454B3167BC04C003586CD /* KRMeshSphere.h in Headers */, E450273C16E0491D00FDEC5C /* KRReverbZone.h in Headers */, @@ -1768,52 +1686,42 @@ E491019113C99BDC0098455B /* KRMesh.cpp in Sources */, E491019313C99BDC0098455B /* KRMaterialManager.cpp in Sources */, E491019413C99BDC0098455B /* KRMaterial.cpp in Sources */, - E416E49E1879112700FC2EEB /* RecastContour.cpp in Sources */, E491019713C99BDC0098455B /* KRVector3.cpp in Sources */, E491019813C99BDC0098455B /* KRTextureManager.cpp in Sources */, E491019913C99BDC0098455B /* KRTexture2D.cpp in Sources */, - E416E4AA1879112700FC2EEB /* RecastRegion.cpp in Sources */, E491019A13C99BDC0098455B /* KRMeshManager.cpp in Sources */, E45E03C718790EC0006DA23F /* tinyxml2.cpp in Sources */, E45E03CD18790EFF006DA23F /* forsyth.cpp in Sources */, E47C25A713F4F6AB00FF4370 /* KRShaderManager.cpp in Sources */, E47C25A913F4F6DD00FF4370 /* KRShader.cpp in Sources */, - E416E4A41879112700FC2EEB /* RecastMesh.cpp in Sources */, E43F70DC181B20E400136169 /* KRLODSet.cpp in Sources */, E414BAE51435558900A668C4 /* KRModel.cpp in Sources */, E414BAE91435585A00A668C4 /* KRScene.cpp in Sources */, E48B3CC014393E30000C50E2 /* KRCamera.cpp in Sources */, - E416E4A61879112700FC2EEB /* RecastMeshDetail.cpp in Sources */, E497B946151BA99500D3DC67 /* KRVector2.cpp in Sources */, E497B94D151BCF2500D3DC67 /* KRResource.cpp in Sources */, - E416E4981879112700FC2EEB /* Recast.cpp in Sources */, E497B950151BD2CE00D3DC67 /* KRResource+obj.cpp in Sources */, E43F70FF1824E73100136169 /* KRMeshStreamer.mm in Sources */, E461A156152E54F800F2044A /* KRLight.cpp in Sources */, E461A159152E557E00F2044A /* KRPointLight.cpp in Sources */, E468447F17FFDF51001F1FA1 /* KRLocator.cpp in Sources */, E461A15F152E565700F2044A /* KRDirectionalLight.cpp in Sources */, - E416E4A21879112700FC2EEB /* RecastLayers.cpp in Sources */, E461A165152E56C000F2044A /* KRSpotLight.cpp in Sources */, E4F975361536221C00FD60B2 /* KRNode.cpp in Sources */, E46C214B15364DEC009CABF3 /* KRSceneManager.cpp in Sources */, E48C697215374F7E00232E28 /* KRContext.cpp in Sources */, E46F4A0E155E003000CCF8B8 /* KRDataBlock.cpp in Sources */, - E416E49A1879112700FC2EEB /* RecastAlloc.cpp in Sources */, E42CB1F0158446AB0066E0D8 /* KRQuaternion.cpp in Sources */, E43B0AD615DDCA0F00A5CB9F /* KRContextObject.cpp in Sources */, E4924C2615EE95E800B965C6 /* KROctree.cpp in Sources */, - E416E4A81879112700FC2EEB /* RecastRasterization.cpp in Sources */, E4924C2B15EE96AB00B965C6 /* KROctreeNode.cpp in Sources */, E404702018695DD200F01F42 /* KRTextureKTX.cpp in Sources */, E40BA45415EFF79500D7C3DD /* KRAABB.cpp in Sources */, - E416E49C1879112700FC2EEB /* RecastArea.cpp in Sources */, E488399415F928CA00BD66D5 /* KRBundle.cpp in Sources */, E488399C15F92BE000BD66D5 /* KRBundleManager.cpp in Sources */, E4B175AC161F5A1000B8FB80 /* KRTexture.cpp in Sources */, E4B175B2161F5FAF00B8FB80 /* KRTextureCube.cpp in Sources */, E4CA10E91637BD2B005D9400 /* KRTexturePVR.cpp in Sources */, - E416E4A01879112700FC2EEB /* RecastFilter.cpp in Sources */, E4CA10EF1637BD58005D9400 /* KRTextureTGA.cpp in Sources */, E4CA11781639CC90005D9400 /* KRViewport.cpp in Sources */, E4324BA816444C230043185B /* KRParticleSystem.cpp in Sources */, @@ -1860,7 +1768,6 @@ E461A175152E5C4800F2044A /* KRLight.cpp in Sources */, E4BBBBA71512A6DC00F43B5B /* KRVector3.cpp in Sources */, E4B2A43B1523B02E004CB0EC /* KRMaterial.cpp in Sources */, - E416E4A71879112700FC2EEB /* RecastMeshDetail.cpp in Sources */, E4BBBB8E1512A40300F43B5B /* krengine_osx.mm in Sources */, E468448017FFDF51001F1FA1 /* KRLocator.cpp in Sources */, E497B947151BA99500D3DC67 /* KRVector2.cpp in Sources */, @@ -1868,20 +1775,16 @@ E497B951151BD2CE00D3DC67 /* KRResource+obj.cpp in Sources */, E497B954151BEDA600D3DC67 /* KRResource+fbx.cpp in Sources */, E4F97551153633E200FD60B2 /* KRMaterialManager.cpp in Sources */, - E416E49F1879112700FC2EEB /* RecastContour.cpp in Sources */, E461A15A152E557E00F2044A /* KRPointLight.cpp in Sources */, E45E03C818790EC0006DA23F /* tinyxml2.cpp in Sources */, E4F9754F1536333200FD60B2 /* KRMesh.cpp in Sources */, E4F9754B153632D800FD60B2 /* KRMeshManager.cpp in Sources */, - E416E4A31879112700FC2EEB /* RecastLayers.cpp in Sources */, E461A160152E565700F2044A /* KRDirectionalLight.cpp in Sources */, E4F975531536340000FD60B2 /* KRTexture2D.cpp in Sources */, E4F9754015362CD400FD60B2 /* KRScene.cpp in Sources */, E461A166152E56C000F2044A /* KRSpotLight.cpp in Sources */, E4F9754315362D0F00FD60B2 /* KRModel.cpp in Sources */, - E416E4A11879112700FC2EEB /* RecastFilter.cpp in Sources */, E45E03CE18790EFF006DA23F /* forsyth.cpp in Sources */, - E416E4991879112700FC2EEB /* Recast.cpp in Sources */, E4F975371536221C00FD60B2 /* KRNode.cpp in Sources */, E4F9754E1536331D00FD60B2 /* KRTextureManager.cpp in Sources */, E4D13367153768610070068C /* KRShader.cpp in Sources */, @@ -1894,7 +1797,6 @@ E46F4A0F155E003000CCF8B8 /* KRDataBlock.cpp in Sources */, E42CB1F1158446AB0066E0D8 /* KRQuaternion.cpp in Sources */, E4AFC6BB15F7C7D600DDB4C8 /* KROctreeNode.cpp in Sources */, - E416E4A51879112700FC2EEB /* RecastMesh.cpp in Sources */, E43B0AD715DDCA0F00A5CB9F /* KRContextObject.cpp in Sources */, E40BA45515EFF79500D7C3DD /* KRAABB.cpp in Sources */, E488399515F928CA00BD66D5 /* KRBundle.cpp in Sources */, @@ -1902,7 +1804,6 @@ E43F70DD181B20E400136169 /* KRLODSet.cpp in Sources */, E43F71001824E73100136169 /* KRMeshStreamer.mm in Sources */, E4B175AD161F5A1000B8FB80 /* KRTexture.cpp in Sources */, - E416E4AB1879112700FC2EEB /* RecastRegion.cpp in Sources */, E4B175B3161F5FAF00B8FB80 /* KRTextureCube.cpp in Sources */, E40F9833184A7BAC00CFA4D8 /* KRSprite.cpp in Sources */, E4CA10EA1637BD2B005D9400 /* KRTexturePVR.cpp in Sources */, @@ -1929,14 +1830,11 @@ E4F027C816979CCD00D4427D /* KRAudioManager.cpp in Sources */, E4F027CF16979CE200D4427D /* KRAudioSample.cpp in Sources */, E4F027DF1697BFFF00D4427D /* KRAudioBuffer.cpp in Sources */, - E416E4A91879112700FC2EEB /* RecastRasterization.cpp in Sources */, E4943232169E08D200BCB891 /* KRAmbientZone.cpp in Sources */, E404702118695DD200F01F42 /* KRTextureKTX.cpp in Sources */, E450273A16E0491D00FDEC5C /* KRReverbZone.cpp in Sources */, - E416E49B1879112700FC2EEB /* RecastAlloc.cpp in Sources */, E4AE635E1704FB0A00B460CD /* KRLODGroup.cpp in Sources */, E4EC73C21720B1FF0065299F /* KRVector4.cpp in Sources */, - E416E49D1879112700FC2EEB /* RecastArea.cpp in Sources */, E48CF943173453990005EBBB /* KRFloat.cpp in Sources */, E45134B71746A4A300443C21 /* KRBehavior.cpp in Sources */, ); From d8082ba50d7377bba527110ed104fc7fd512cc96 Mon Sep 17 00:00:00 2001 From: Kearwood Gilbert Date: Wed, 15 Jan 2014 00:53:03 -0800 Subject: [PATCH 29/84] Modified behaviour of KRSprite's KRSprite's no longer have a "sprite_size" attribute. Instead, the node's scale is used. KRSprite's no longer orient towards the camera automatically. This enables applications to perform their own orientation behaviours and use the sprites for UI elements that may rotate. --HG-- branch : nfb --- KREngine/kraken/KRSprite.cpp | 15 ++------------- KREngine/kraken/KRSprite.h | 2 -- .../kraken_standard_assets_ios/Shaders/sprite.vsh | 3 +-- .../Shaders/sprite_osx.vsh | 3 +-- 4 files changed, 4 insertions(+), 19 deletions(-) diff --git a/KREngine/kraken/KRSprite.cpp b/KREngine/kraken/KRSprite.cpp index 3ee9d93..c9f63c6 100644 --- a/KREngine/kraken/KRSprite.cpp +++ b/KREngine/kraken/KRSprite.cpp @@ -28,7 +28,6 @@ KRSprite::KRSprite(KRScene &scene, std::string name) : KRNode(scene, name) { m_spriteTexture = ""; m_pSpriteTexture = NULL; - m_spriteSize = 0.0f; m_spriteAlpha = 1.0f; } @@ -43,7 +42,6 @@ std::string KRSprite::getElementName() { tinyxml2::XMLElement *KRSprite::saveXML( tinyxml2::XMLNode *parent) { tinyxml2::XMLElement *e = KRNode::saveXML(parent); - e->SetAttribute("sprite_size", m_spriteSize); e->SetAttribute("sprite_texture", m_spriteTexture.c_str()); e->SetAttribute("sprite_alpha", m_spriteAlpha); return e; @@ -52,9 +50,6 @@ tinyxml2::XMLElement *KRSprite::saveXML( tinyxml2::XMLNode *parent) void KRSprite::loadXML(tinyxml2::XMLElement *e) { KRNode::loadXML(e); - if(e->QueryFloatAttribute("sprite_size", &m_spriteSize) != tinyxml2::XML_SUCCESS) { - m_spriteSize = 0.0f; - } if(e->QueryFloatAttribute("sprite_alpha", &m_spriteAlpha) != tinyxml2::XML_SUCCESS) { m_spriteAlpha = 1.0f; } @@ -73,11 +68,6 @@ void KRSprite::setSpriteTexture(std::string sprite_texture) { m_pSpriteTexture = NULL; } -void KRSprite::setSpriteSize(float sprite_size) { - // TODO - Deprecated - This should come from the localScale - m_spriteSize = sprite_size; -} - void KRSprite::setSpriteAlpha(float alpha) { m_spriteAlpha = alpha; @@ -89,7 +79,7 @@ float KRSprite::getSpriteAlpha() const } KRAABB KRSprite::getBounds() { - return KRAABB(KRVector3(-m_spriteSize), KRVector3(m_spriteSize), getModelMatrix()); + return KRAABB(-KRVector3::One() * 0.5f, KRVector3::One() * 0.5f, getModelMatrix()); } @@ -99,7 +89,7 @@ void KRSprite::render(KRCamera *pCamera, std::vector &point_ligh if(renderPass == KRNode::RENDER_PASS_ADDITIVE_PARTICLES) { - if(m_spriteTexture.size() && m_spriteSize > 0.0f && m_spriteAlpha > 0.0f) { + if(m_spriteTexture.size() && m_spriteAlpha > 0.0f) { if(!m_pSpriteTexture && m_spriteTexture.size()) { @@ -128,7 +118,6 @@ void KRSprite::render(KRCamera *pCamera, std::vector &point_ligh KRVector3 rim_color; if(getContext().getShaderManager()->selectShader(*pCamera, pShader, viewport, getModelMatrix(), point_lights, directional_lights, spot_lights, 0, renderPass, rim_color, 0.0f)) { pShader->setUniform(KRShader::KRENGINE_UNIFORM_MATERIAL_ALPHA, m_spriteAlpha); - pShader->setUniform(KRShader::KRENGINE_UNIFORM_FLARE_SIZE, m_spriteSize); m_pContext->getTextureManager()->selectTexture(0, m_pSpriteTexture); m_pContext->getModelManager()->bindVBO(getContext().getModelManager()->KRENGINE_VBO_2D_SQUARE_VERTICES, getContext().getModelManager()->KRENGINE_VBO_2D_SQUARE_INDEXES, getContext().getModelManager()->KRENGINE_VBO_2D_SQUARE_ATTRIBS, true); GLDEBUG(glDrawArrays(GL_TRIANGLE_STRIP, 0, 4)); diff --git a/KREngine/kraken/KRSprite.h b/KREngine/kraken/KRSprite.h index e64be0f..9ddc84e 100644 --- a/KREngine/kraken/KRSprite.h +++ b/KREngine/kraken/KRSprite.h @@ -23,7 +23,6 @@ public: virtual void loadXML(tinyxml2::XMLElement *e); void setSpriteTexture(std::string sprite_texture); - void setSpriteSize(float sprite_size); void setSpriteAlpha(float alpha); float getSpriteAlpha() const; @@ -35,7 +34,6 @@ protected: std::string m_spriteTexture; KRTexture *m_pSpriteTexture; - float m_spriteSize; float m_spriteAlpha; }; diff --git a/KREngine/kraken_standard_assets_ios/Shaders/sprite.vsh b/KREngine/kraken_standard_assets_ios/Shaders/sprite.vsh index 347191e..1e8682a 100644 --- a/KREngine/kraken_standard_assets_ios/Shaders/sprite.vsh +++ b/KREngine/kraken_standard_assets_ios/Shaders/sprite.vsh @@ -32,11 +32,10 @@ attribute mediump vec2 vertex_uv; uniform highp mat4 mvp_matrix; // mvp_matrix is the result of multiplying the model, view, and projection matrices uniform mediump vec4 viewport; -uniform mediump float flare_size; varying mediump vec2 texCoord; void main() { texCoord = vertex_uv; - gl_Position = mvp_matrix * vec4(0.0, 0.0, 0.0, 1.0) + vec4(vertex_uv.x * viewport.w / viewport.z * 2.0 - 1.0, vertex_uv.y * 2.0 - 1.0, 0.0, 0.0) * flare_size; + gl_Position = mvp_matrix * vec4(vertex_uv.x * 2.0 - 1.0, vertex_uv.y * 2.0 - 1.0, 0.0, 1.0); } diff --git a/KREngine/kraken_standard_assets_osx/Shaders/sprite_osx.vsh b/KREngine/kraken_standard_assets_osx/Shaders/sprite_osx.vsh index 347191e..1e8682a 100644 --- a/KREngine/kraken_standard_assets_osx/Shaders/sprite_osx.vsh +++ b/KREngine/kraken_standard_assets_osx/Shaders/sprite_osx.vsh @@ -32,11 +32,10 @@ attribute mediump vec2 vertex_uv; uniform highp mat4 mvp_matrix; // mvp_matrix is the result of multiplying the model, view, and projection matrices uniform mediump vec4 viewport; -uniform mediump float flare_size; varying mediump vec2 texCoord; void main() { texCoord = vertex_uv; - gl_Position = mvp_matrix * vec4(0.0, 0.0, 0.0, 1.0) + vec4(vertex_uv.x * viewport.w / viewport.z * 2.0 - 1.0, vertex_uv.y * 2.0 - 1.0, 0.0, 0.0) * flare_size; + gl_Position = mvp_matrix * vec4(vertex_uv.x * 2.0 - 1.0, vertex_uv.y * 2.0 - 1.0, 0.0, 1.0); } From c5da1dd7e1af73d1803f7aacf0fdacfabda5c138 Mon Sep 17 00:00:00 2001 From: Peter Courtemanche Date: Thu, 16 Jan 2014 17:01:12 -0800 Subject: [PATCH 30/84] Adjusted a few more constants - reverb and max active sources. --HG-- branch : nfb --- KREngine/kraken/KRAudioManager.h | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/KREngine/kraken/KRAudioManager.h b/KREngine/kraken/KRAudioManager.h index c19697c..e829380 100644 --- a/KREngine/kraken/KRAudioManager.h +++ b/KREngine/kraken/KRAudioManager.h @@ -68,11 +68,11 @@ const int KRENGINE_REVERB_WORKSPACE_SIZE = 1 << KRENGINE_REVERB_MAX_FFT_LOG2; const float KRENGINE_AUDIO_CUTOFF = 0.02f; // Cutoff gain level, to cull out processing of very quiet sounds -const int KRENGINE_REVERB_MAX_SAMPLES = 435200; // At least 10s reverb impulse response length, divisible by KRENGINE_AUDIO_BLOCK_LENGTH -const int KRENGINE_MAX_REVERB_IMPULSE_MIX = 16; // Maximum number of impulse response filters that can be mixed simultaneously +const int KRENGINE_REVERB_MAX_SAMPLES = 128000; // 2.9 seconds //435200; // At least 10s reverb impulse response length, divisible by KRENGINE_AUDIO_BLOCK_LENGTH +const int KRENGINE_MAX_REVERB_IMPULSE_MIX = 8; // Maximum number of impulse response filters that can be mixed simultaneously const int KRENGINE_MAX_OUTPUT_CHANNELS = 2; -const int KRENGINE_MAX_ACTIVE_SOURCES = 24; +const int KRENGINE_MAX_ACTIVE_SOURCES = 16; const int KRENGINE_AUDIO_ANTICLICK_SAMPLES = 64; From 2ec9d5cf767fd58bb7fa2de99dbf9d363c0390e4 Mon Sep 17 00:00:00 2001 From: Kearwood Gilbert Date: Thu, 23 Jan 2014 21:40:29 -0800 Subject: [PATCH 31/84] Implemented support for RLE compressed TGA images Image import pipeline can now generate pre-multiplied alpha output images while they are compressed. --HG-- branch : nfb --- KREngine/kraken/KRTexture.cpp | 2 +- KREngine/kraken/KRTexture.h | 2 +- KREngine/kraken/KRTexture2D.h | 2 +- KREngine/kraken/KRTextureKTX.cpp | 2 +- KREngine/kraken/KRTextureKTX.h | 2 +- KREngine/kraken/KRTextureManager.cpp | 4 +- KREngine/kraken/KRTextureManager.h | 2 +- KREngine/kraken/KRTexturePVR.cpp | 2 +- KREngine/kraken/KRTexturePVR.h | 2 +- KREngine/kraken/KRTextureTGA.cpp | 142 ++++++++++++++++++++++++++- KREngine/kraken/KRTextureTGA.h | 4 +- 11 files changed, 150 insertions(+), 16 deletions(-) diff --git a/KREngine/kraken/KRTexture.cpp b/KREngine/kraken/KRTexture.cpp index 0f64489..ba640e5 100644 --- a/KREngine/kraken/KRTexture.cpp +++ b/KREngine/kraken/KRTexture.cpp @@ -111,7 +111,7 @@ bool KRTexture::isAnimated() return false; } -KRTexture *KRTexture::compress() +KRTexture *KRTexture::compress(bool premultiply_alpha) { return NULL; } diff --git a/KREngine/kraken/KRTexture.h b/KREngine/kraken/KRTexture.h index defc431..dccdd72 100644 --- a/KREngine/kraken/KRTexture.h +++ b/KREngine/kraken/KRTexture.h @@ -59,7 +59,7 @@ public: virtual void resetPoolExpiry(); virtual bool isAnimated(); - virtual KRTexture *compress(); + virtual KRTexture *compress(bool premultiply_alpha = false); int getCurrentLodMaxDim(); int getMaxMipMap(); int getMinMipMap(); diff --git a/KREngine/kraken/KRTexture2D.h b/KREngine/kraken/KRTexture2D.h index f7f4303..921370b 100644 --- a/KREngine/kraken/KRTexture2D.h +++ b/KREngine/kraken/KRTexture2D.h @@ -46,7 +46,7 @@ public: virtual bool save(const std::string& path); virtual bool save(KRDataBlock &data); - virtual bool uploadTexture(GLenum target, int lod_max_dim, int ¤t_lod_max_dim, int prev_lod_max_dim, bool compress = false) = 0; + virtual bool uploadTexture(GLenum target, int lod_max_dim, int ¤t_lod_max_dim, int prev_lod_max_dim, bool compress = false, bool premultiply_alpha = false) = 0; virtual void bind(GLuint texture_unit); protected: diff --git a/KREngine/kraken/KRTextureKTX.cpp b/KREngine/kraken/KRTextureKTX.cpp index 0550554..863c624 100644 --- a/KREngine/kraken/KRTextureKTX.cpp +++ b/KREngine/kraken/KRTextureKTX.cpp @@ -153,7 +153,7 @@ long KRTextureKTX::getMemRequiredForSize(int max_dim) return memoryRequired; } -bool KRTextureKTX::uploadTexture(GLenum target, int lod_max_dim, int ¤t_lod_max_dim, int prev_lod_max_dim, bool compress) +bool KRTextureKTX::uploadTexture(GLenum target, int lod_max_dim, int ¤t_lod_max_dim, int prev_lod_max_dim, bool compress, bool premultiply_alpha) { int target_dim = lod_max_dim; if(target_dim < m_min_lod_max_dim) target_dim = m_min_lod_max_dim; diff --git a/KREngine/kraken/KRTextureKTX.h b/KREngine/kraken/KRTextureKTX.h index 13308b8..f9f5d30 100644 --- a/KREngine/kraken/KRTextureKTX.h +++ b/KREngine/kraken/KRTextureKTX.h @@ -19,7 +19,7 @@ public: virtual ~KRTextureKTX(); virtual std::string getExtension(); - bool uploadTexture(GLenum target, int lod_max_dim, int ¤t_lod_max_dim, int prev_lod_max_dim, bool compress = false); + bool uploadTexture(GLenum target, int lod_max_dim, int ¤t_lod_max_dim, int prev_lod_max_dim, bool compress = false, bool premultiply_alpha = false); virtual long getMemRequiredForSize(int max_dim); diff --git a/KREngine/kraken/KRTextureManager.cpp b/KREngine/kraken/KRTextureManager.cpp index 0e02202..58cca0e 100644 --- a/KREngine/kraken/KRTextureManager.cpp +++ b/KREngine/kraken/KRTextureManager.cpp @@ -393,14 +393,14 @@ unordered_map &KRTextureManager::getTextures() return m_textures; } -void KRTextureManager::compress() +void KRTextureManager::compress(bool premultiply_alpha) { std::vector textures_to_remove; std::vector textures_to_add; for(unordered_map::iterator itr=m_textures.begin(); itr != m_textures.end(); itr++) { KRTexture *texture = (*itr).second; - KRTexture *compressed_texture = texture->compress(); + KRTexture *compressed_texture = texture->compress(premultiply_alpha); if(compressed_texture) { textures_to_remove.push_back(texture); textures_to_add.push_back(compressed_texture); diff --git a/KREngine/kraken/KRTextureManager.h b/KREngine/kraken/KRTextureManager.h index cdf8b99..41b4b21 100644 --- a/KREngine/kraken/KRTextureManager.h +++ b/KREngine/kraken/KRTextureManager.h @@ -66,7 +66,7 @@ public: unordered_map &getTextures(); - void compress(); + void compress(bool premultiply_alpha = false); std::set &getActiveTextures(); std::set &getPoolTextures(); diff --git a/KREngine/kraken/KRTexturePVR.cpp b/KREngine/kraken/KRTexturePVR.cpp index 419ccbe..46f3ef4 100644 --- a/KREngine/kraken/KRTexturePVR.cpp +++ b/KREngine/kraken/KRTexturePVR.cpp @@ -173,7 +173,7 @@ long KRTexturePVR::getMemRequiredForSize(int max_dim) return memoryRequired; } -bool KRTexturePVR::uploadTexture(GLenum target, int lod_max_dim, int ¤t_lod_max_dim, int prev_lod_max_dim, bool compress) +bool KRTexturePVR::uploadTexture(GLenum target, int lod_max_dim, int ¤t_lod_max_dim, int prev_lod_max_dim, bool compress, bool premultiply_alpha) { int target_dim = lod_max_dim; if(target_dim < m_min_lod_max_dim) target_dim = m_min_lod_max_dim; diff --git a/KREngine/kraken/KRTexturePVR.h b/KREngine/kraken/KRTexturePVR.h index ea15878..3d973d4 100644 --- a/KREngine/kraken/KRTexturePVR.h +++ b/KREngine/kraken/KRTexturePVR.h @@ -18,7 +18,7 @@ public: virtual ~KRTexturePVR(); virtual std::string getExtension(); - bool uploadTexture(GLenum target, int lod_max_dim, int ¤t_lod_max_dim, int prev_lod_max_dim, bool compress = false); + bool uploadTexture(GLenum target, int lod_max_dim, int ¤t_lod_max_dim, int prev_lod_max_dim, bool compress = false, bool premultiply_alpha = false); virtual long getMemRequiredForSize(int max_dim); diff --git a/KREngine/kraken/KRTextureTGA.cpp b/KREngine/kraken/KRTextureTGA.cpp index 257be52..c6b6a56 100644 --- a/KREngine/kraken/KRTextureTGA.cpp +++ b/KREngine/kraken/KRTextureTGA.cpp @@ -36,6 +36,7 @@ KRTextureTGA::KRTextureTGA(KRContext &context, KRDataBlock *data, std::string na m_min_lod_max_dim = m_max_lod_max_dim; // Mipmaps not yet supported for TGA images switch(pHeader->imagetype) { case 2: // rgb + case 10: // rgb + rle switch(pHeader->bitsperpixel) { case 24: { @@ -70,7 +71,7 @@ KRTextureTGA::~KRTextureTGA() } -bool KRTextureTGA::uploadTexture(GLenum target, int lod_max_dim, int ¤t_lod_max_dim, int prev_lod_max_dim, bool compress) +bool KRTextureTGA::uploadTexture(GLenum target, int lod_max_dim, int ¤t_lod_max_dim, int prev_lod_max_dim, bool compress, bool premultiply_alpha) { m_pData->lock(); TGA_HEADER *pHeader = (TGA_HEADER *)m_pData->getStart(); @@ -133,7 +134,26 @@ bool KRTextureTGA::uploadTexture(GLenum target, int lod_max_dim, int ¤t_lo break; case 32: { - glTexImage2D(target, 0, internal_format, pHeader->width, pHeader->height, 0, GL_BGRA, GL_UNSIGNED_BYTE, (GLvoid *)pData); + if(premultiply_alpha) { + unsigned char *converted_image = (unsigned char *)malloc(pHeader->width * pHeader->height * 4); + + unsigned char *pSource = pData; + unsigned char *pDest = converted_image; + unsigned char *pEnd = pData + pHeader->height * pHeader->width * 3; + while(pSource < pEnd) { + *pDest++ = (__uint32_t)pSource[0] * (__uint32_t)pSource[3] / 0xff; + *pDest++ = (__uint32_t)pSource[1] * (__uint32_t)pSource[3] / 0xff; + *pDest++ = (__uint32_t)pSource[2] * (__uint32_t)pSource[3] / 0xff; + *pDest++ = pSource[3]; + pSource += 4; + } + + glTexImage2D(target, 0, internal_format, pHeader->width, pHeader->height, 0, GL_BGRA, GL_UNSIGNED_BYTE, (GLvoid *)converted_image); + free(converted_image); + } else { + glTexImage2D(target, 0, internal_format, pHeader->width, pHeader->height, 0, GL_BGRA, GL_UNSIGNED_BYTE, (GLvoid *)pData); + } + err = glGetError(); if (err != GL_NO_ERROR) { m_pData->unlock(); @@ -147,6 +167,120 @@ bool KRTextureTGA::uploadTexture(GLenum target, int lod_max_dim, int ¤t_lo return false; // 16-bit images not yet supported } break; + case 10: // rgb + rle + switch(pHeader->bitsperpixel) { + case 24: + { + unsigned char *converted_image = (unsigned char *)malloc(pHeader->width * pHeader->height * 4); + unsigned char *pSource = pData; + unsigned char *pDest = converted_image; + unsigned char *pEnd = converted_image + pHeader->height * pHeader->width * 4; + if(premultiply_alpha) { + while(pDest < pEnd) { + int count = *pSource & 0x7f + 1; + if(*pSource & 0x80) { + // RLE Packet + pSource++; + while(count--) { + *pDest++ = (__uint32_t)pSource[0] * (__uint32_t)pSource[3] / 0xff; + *pDest++ = (__uint32_t)pSource[1] * (__uint32_t)pSource[3] / 0xff; + *pDest++ = (__uint32_t)pSource[2] * (__uint32_t)pSource[3] / 0xff; + *pDest++ = pSource[3]; + } + pSource += 4; + } else { + // RAW Packet + pSource++; + while(count--) { + *pDest++ = (__uint32_t)pSource[0] * (__uint32_t)pSource[3] / 0xff; + *pDest++ = (__uint32_t)pSource[1] * (__uint32_t)pSource[3] / 0xff; + *pDest++ = (__uint32_t)pSource[2] * (__uint32_t)pSource[3] / 0xff; + *pDest++ = pSource[3]; + pSource += 4; + } + } + } + } else { + while(pDest < pEnd) { + int count = *pSource & 0x7f + 1; + if(*pSource & 0x80) { + // RLE Packet + pSource++; + while(count--) { + *pDest++ = pSource[0]; + *pDest++ = pSource[1]; + *pDest++ = pSource[2]; + *pDest++ = pSource[3]; + } + pSource += 4; + } else { + // RAW Packet + pSource++; + while(count--) { + *pDest++ = pSource[0]; + *pDest++ = pSource[1]; + *pDest++ = pSource[2]; + *pDest++ = pSource[3]; + pSource += 4; + } + } + } + } + glTexImage2D(target, 0, internal_format, pHeader->width, pHeader->height, 0, GL_BGRA, GL_UNSIGNED_BYTE, (GLvoid *)converted_image); + free(converted_image); + err = glGetError(); + if (err != GL_NO_ERROR) { + m_pData->unlock(); + return false; + } + current_lod_max_dim = m_max_lod_max_dim; + } + break; + case 32: + { + unsigned char *converted_image = (unsigned char *)malloc(pHeader->width * pHeader->height * 4); + unsigned char *pSource = pData; + unsigned char *pDest = converted_image; + unsigned char *pEnd = converted_image + pHeader->height * pHeader->width * 4; + while(pDest < pEnd) { + int count = *pSource & 0x7f + 1; + if(*pSource & 0x80) { + // RLE Packet + pSource++; + while(count--) { + *pDest++ = pSource[0]; + *pDest++ = pSource[1]; + *pDest++ = pSource[2]; + *pDest++ = 0xff; + } + pSource += 3; + } else { + // RAW Packet + pSource++; + while(count--) { + *pDest++ = pSource[0]; + *pDest++ = pSource[1]; + *pDest++ = pSource[2]; + *pDest++ = 0xff; + pSource += 3; + } + } + } + glTexImage2D(target, 0, internal_format, pHeader->width, pHeader->height, 0, GL_BGRA, GL_UNSIGNED_BYTE, (GLvoid *)converted_image); + free(converted_image); + err = glGetError(); + if (err != GL_NO_ERROR) { + m_pData->unlock(); + return false; + } + current_lod_max_dim = m_max_lod_max_dim; + } + break; + default: + m_pData->unlock(); + return false; // 16-bit images not yet supported + } + break; default: m_pData->unlock(); return false; // Image type not yet supported @@ -156,7 +290,7 @@ bool KRTextureTGA::uploadTexture(GLenum target, int lod_max_dim, int ¤t_lo return true; } -KRTexture *KRTextureTGA::compress() +KRTexture *KRTextureTGA::compress(bool premultiply_alpha) { m_pData->lock(); @@ -170,7 +304,7 @@ KRTexture *KRTextureTGA::compress() GLDEBUG(glBindTexture(GL_TEXTURE_2D, compressed_handle)); int current_max_dim = 0; - if(!uploadTexture(GL_TEXTURE_2D, m_max_lod_max_dim, current_max_dim, 0, true)) { + if(!uploadTexture(GL_TEXTURE_2D, m_max_lod_max_dim, current_max_dim, 0, true, premultiply_alpha)) { assert(false); // Failed to upload the texture } GLDEBUG(glGenerateMipmap(GL_TEXTURE_2D)); diff --git a/KREngine/kraken/KRTextureTGA.h b/KREngine/kraken/KRTextureTGA.h index 34edb9d..ddea36d 100644 --- a/KREngine/kraken/KRTextureTGA.h +++ b/KREngine/kraken/KRTextureTGA.h @@ -18,8 +18,8 @@ public: virtual ~KRTextureTGA(); virtual std::string getExtension(); - bool uploadTexture(GLenum target, int lod_max_dim, int ¤t_lod_max_dim, int prev_lod_max_dim, bool compress = false); - virtual KRTexture *compress(); + bool uploadTexture(GLenum target, int lod_max_dim, int ¤t_lod_max_dim, int prev_lod_max_dim, bool compress = false, bool premultiply_alpha = false); + virtual KRTexture *compress(bool premultiply_alpha = false); virtual long getMemRequiredForSize(int max_dim); private: From d906f67f769f1c497dcee58ce15d3e0e4497688f Mon Sep 17 00:00:00 2001 From: Kearwood Gilbert Date: Fri, 24 Jan 2014 02:13:34 -0800 Subject: [PATCH 32/84] TGA Images working once again on iOS and OSX runtimes. Image import / compression pipeline fixed -- was broken with TGA support Corrected TGA RLE decompression -- 24bpp RLE compressed TGA's now tested and working. No longer attempting to generate mipmaps for cube maps automatically at runtime. --HG-- branch : nfb --- KREngine/kraken/KRTexture.cpp | 4 +--- KREngine/kraken/KRTextureCube.cpp | 6 +++--- KREngine/kraken/KRTextureManager.cpp | 4 ---- KREngine/kraken/KRTextureTGA.cpp | 24 ++++++++++++------------ KREngine/kraken/KRTextureTGA.h | 3 +++ 5 files changed, 19 insertions(+), 22 deletions(-) diff --git a/KREngine/kraken/KRTexture.cpp b/KREngine/kraken/KRTexture.cpp index ba640e5..4bc425f 100644 --- a/KREngine/kraken/KRTexture.cpp +++ b/KREngine/kraken/KRTexture.cpp @@ -79,9 +79,7 @@ void KRTexture::resize(int max_dim) if(!createGLTexture(target_dim)) { getContext().getTextureManager()->memoryChanged(-m_newTextureMemUsed); m_newTextureMemUsed = 0; -// assert(false); -// FINDME - assert commented out .. this crashes the app when running in the debug UI and changing 'Debug-Display' to show the framerate -// the FPS doesn't draw anymore either (the drop shadow draws but the text doesn't) + assert(false); // Failed to create the texture } } } diff --git a/KREngine/kraken/KRTextureCube.cpp b/KREngine/kraken/KRTextureCube.cpp index dcf8e0f..982e6bb 100644 --- a/KREngine/kraken/KRTextureCube.cpp +++ b/KREngine/kraken/KRTextureCube.cpp @@ -89,9 +89,9 @@ bool KRTextureCube::createGLTexture(int lod_max_dim) if(bMipMaps) { GLDEBUG(glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR)); } else { - // GLDEBUG(glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MIN_FILTER, GL_LINEAR)); - GLDEBUG(glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR)); - GLDEBUG(glGenerateMipmap(GL_TEXTURE_CUBE_MAP)); + GLDEBUG(glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MIN_FILTER, GL_LINEAR)); + // GLDEBUG(glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR)); + // GLDEBUG(glGenerateMipmap(GL_TEXTURE_CUBE_MAP)); } diff --git a/KREngine/kraken/KRTextureManager.cpp b/KREngine/kraken/KRTextureManager.cpp index 58cca0e..6abd66e 100644 --- a/KREngine/kraken/KRTextureManager.cpp +++ b/KREngine/kraken/KRTextureManager.cpp @@ -329,10 +329,6 @@ void KRTextureManager::balanceTextureMemory() } } - if (maxDimActive > 512) maxDimActive = 512; // FINDME - hack to stop the texture resizer from blowing out its brains .. - // when the texture quality goes up to 1024, the app runs for about 2 mins and then - // runs out of memory. - // Resize active textures to balance the memory usage and mipmap levels for(std::set::iterator itr=m_activeTextures_streamer.begin(); itr != m_activeTextures_streamer.end() && memory_available > 0; itr++) { KRTexture *activeTexture = *itr; diff --git a/KREngine/kraken/KRTextureTGA.cpp b/KREngine/kraken/KRTextureTGA.cpp index c6b6a56..163e58b 100644 --- a/KREngine/kraken/KRTextureTGA.cpp +++ b/KREngine/kraken/KRTextureTGA.cpp @@ -77,17 +77,15 @@ bool KRTextureTGA::uploadTexture(GLenum target, int lod_max_dim, int ¤t_lo TGA_HEADER *pHeader = (TGA_HEADER *)m_pData->getStart(); unsigned char *pData = (unsigned char *)pHeader + (long)pHeader->idlength + (long)pHeader->colourmaplength * (long)pHeader->colourmaptype + sizeof(TGA_HEADER); -// -// FINDME - many of the GL constants in here are not defined in GLES2 -#ifdef TARGET_OS_IPHONE +#if TARGET_OS_IPHONEz GLenum base_internal_format = GL_BGRA; #else GLenum base_internal_format = pHeader->bitsperpixel == 24 ? GL_BGR : GL_BGRA; #endif - GLenum internal_format = 0; + GLenum internal_format = GL_RGBA; -#ifndef TARGET_OS_IPHONE +#if !TARGET_OS_IPHONE if(compress) { internal_format = pHeader->bitsperpixel == 24 ? GL_COMPRESSED_RGB_S3TC_DXT1_EXT : GL_COMPRESSED_RGBA_S3TC_DXT5_EXT; } @@ -169,7 +167,7 @@ bool KRTextureTGA::uploadTexture(GLenum target, int lod_max_dim, int ¤t_lo break; case 10: // rgb + rle switch(pHeader->bitsperpixel) { - case 24: + case 32: { unsigned char *converted_image = (unsigned char *)malloc(pHeader->width * pHeader->height * 4); unsigned char *pSource = pData; @@ -177,7 +175,7 @@ bool KRTextureTGA::uploadTexture(GLenum target, int lod_max_dim, int ¤t_lo unsigned char *pEnd = converted_image + pHeader->height * pHeader->width * 4; if(premultiply_alpha) { while(pDest < pEnd) { - int count = *pSource & 0x7f + 1; + int count = (*pSource & 0x7f) + 1; if(*pSource & 0x80) { // RLE Packet pSource++; @@ -202,7 +200,7 @@ bool KRTextureTGA::uploadTexture(GLenum target, int lod_max_dim, int ¤t_lo } } else { while(pDest < pEnd) { - int count = *pSource & 0x7f + 1; + int count = (*pSource & 0x7f) + 1; if(*pSource & 0x80) { // RLE Packet pSource++; @@ -236,14 +234,14 @@ bool KRTextureTGA::uploadTexture(GLenum target, int lod_max_dim, int ¤t_lo current_lod_max_dim = m_max_lod_max_dim; } break; - case 32: + case 24: { unsigned char *converted_image = (unsigned char *)malloc(pHeader->width * pHeader->height * 4); unsigned char *pSource = pData; unsigned char *pDest = converted_image; unsigned char *pEnd = converted_image + pHeader->height * pHeader->width * 4; while(pDest < pEnd) { - int count = *pSource & 0x7f + 1; + int count = (*pSource & 0x7f) + 1; if(*pSource & 0x80) { // RLE Packet pSource++; @@ -290,6 +288,8 @@ bool KRTextureTGA::uploadTexture(GLenum target, int lod_max_dim, int ¤t_lo return true; } +#if !TARGET_OS_IPHONE + KRTexture *KRTextureTGA::compress(bool premultiply_alpha) { m_pData->lock(); @@ -311,7 +311,7 @@ KRTexture *KRTextureTGA::compress(bool premultiply_alpha) GLint width = 0, height = 0, internal_format, base_internal_format; -#ifndef TARGET_OS_IPHONE + GLDEBUG(glGetTexLevelParameteriv(GL_TEXTURE_2D, 0, GL_TEXTURE_WIDTH, &width)); GLDEBUG(glGetTexLevelParameteriv(GL_TEXTURE_2D, 0, GL_TEXTURE_HEIGHT, &height)); GLDEBUG(glGetTexLevelParameteriv(GL_TEXTURE_2D, 0, GL_TEXTURE_INTERNAL_FORMAT, &internal_format)); @@ -350,7 +350,6 @@ KRTexture *KRTextureTGA::compress(bool premultiply_alpha) // err will equal GL_INVALID_VALUE when // assert(false); // Unexpected error } -#endif GLDEBUG(glBindTexture(GL_TEXTURE_2D, 0)); getContext().getTextureManager()->selectTexture(0, NULL); @@ -369,6 +368,7 @@ KRTexture *KRTextureTGA::compress(bool premultiply_alpha) return new_texture; } +#endif long KRTextureTGA::getMemRequiredForSize(int max_dim) { diff --git a/KREngine/kraken/KRTextureTGA.h b/KREngine/kraken/KRTextureTGA.h index ddea36d..abca2a8 100644 --- a/KREngine/kraken/KRTextureTGA.h +++ b/KREngine/kraken/KRTextureTGA.h @@ -19,7 +19,10 @@ public: virtual std::string getExtension(); bool uploadTexture(GLenum target, int lod_max_dim, int ¤t_lod_max_dim, int prev_lod_max_dim, bool compress = false, bool premultiply_alpha = false); + +#if !TARGET_OS_IPHONE virtual KRTexture *compress(bool premultiply_alpha = false); +#endif virtual long getMemRequiredForSize(int max_dim); private: From 77550118f048178ce67bba0a256e56d24d6192d7 Mon Sep 17 00:00:00 2001 From: Kearwood Gilbert Date: Sat, 25 Jan 2014 18:43:34 -0800 Subject: [PATCH 33/84] Fixed typo causing compilation error on iOS Corrected inversion of skybox on OSX --HG-- branch : nfb --- KREngine/kraken/KRTextureTGA.cpp | 2 +- KREngine/kraken_standard_assets_osx/Shaders/sky_box_osx.vsh | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/KREngine/kraken/KRTextureTGA.cpp b/KREngine/kraken/KRTextureTGA.cpp index 163e58b..083c7fa 100644 --- a/KREngine/kraken/KRTextureTGA.cpp +++ b/KREngine/kraken/KRTextureTGA.cpp @@ -77,7 +77,7 @@ bool KRTextureTGA::uploadTexture(GLenum target, int lod_max_dim, int ¤t_lo TGA_HEADER *pHeader = (TGA_HEADER *)m_pData->getStart(); unsigned char *pData = (unsigned char *)pHeader + (long)pHeader->idlength + (long)pHeader->colourmaplength * (long)pHeader->colourmaptype + sizeof(TGA_HEADER); -#if TARGET_OS_IPHONEz +#if TARGET_OS_IPHONE GLenum base_internal_format = GL_BGRA; #else GLenum base_internal_format = pHeader->bitsperpixel == 24 ? GL_BGR : GL_BGRA; diff --git a/KREngine/kraken_standard_assets_osx/Shaders/sky_box_osx.vsh b/KREngine/kraken_standard_assets_osx/Shaders/sky_box_osx.vsh index bccc0b9..3540e26 100644 --- a/KREngine/kraken_standard_assets_osx/Shaders/sky_box_osx.vsh +++ b/KREngine/kraken_standard_assets_osx/Shaders/sky_box_osx.vsh @@ -42,8 +42,8 @@ uniform mediump vec4 viewport; void main() { gl_Position = vec4(vertex_position.xy, 1.0, 1.0); - + vec4 t = inv_mvp_matrix_no_translate * vec4(vertex_position.xy, 1.0, 1.0); - t /= t.w; + t *= 1.0 / t.w; texCoord = vec3(t); } From 9999b91ee1c50e06f16d6e867d1bf39165451005 Mon Sep 17 00:00:00 2001 From: Kearwood Gilbert Date: Sat, 1 Feb 2014 16:07:02 -0800 Subject: [PATCH 34/84] Corrected infinite loop in octree generation that occurred due to NaN being returned as the AABB bounds for point lights and spot lights. --HG-- branch : nfb --- KREngine/kraken/KRPointLight.cpp | 4 ++-- KREngine/kraken/KRScene.cpp | 4 ++-- KREngine/kraken/KRSpotLight.cpp | 10 ++++++++++ KREngine/kraken/KRSpotLight.h | 2 +- 4 files changed, 15 insertions(+), 5 deletions(-) diff --git a/KREngine/kraken/KRPointLight.cpp b/KREngine/kraken/KRPointLight.cpp index fd7cbce..8ce2f07 100644 --- a/KREngine/kraken/KRPointLight.cpp +++ b/KREngine/kraken/KRPointLight.cpp @@ -34,7 +34,7 @@ std::string KRPointLight::getElementName() { } KRAABB KRPointLight::getBounds() { - float influence_radius = sqrt((m_intensity / 100.0) / KRLIGHT_MIN_INFLUENCE - 1.0) + m_decayStart; + float influence_radius = m_decayStart - sqrt(m_intensity * 0.01f) / sqrt(KRLIGHT_MIN_INFLUENCE); if(influence_radius < m_flareOcclusionSize) { influence_radius = m_flareOcclusionSize; } @@ -56,7 +56,7 @@ void KRPointLight::render(KRCamera *pCamera, std::vector &point_ KRVector3 light_position = getLocalTranslation(); - float influence_radius = sqrt((m_intensity / 100.0) / KRLIGHT_MIN_INFLUENCE - 1.0) + m_decayStart; + float influence_radius = m_decayStart - sqrt(m_intensity * 0.01f) / sqrt(KRLIGHT_MIN_INFLUENCE); KRMat4 sphereModelMatrix = KRMat4(); sphereModelMatrix.scale(influence_radius); diff --git a/KREngine/kraken/KRScene.cpp b/KREngine/kraken/KRScene.cpp index 5e31dce..b103167 100644 --- a/KREngine/kraken/KRScene.cpp +++ b/KREngine/kraken/KRScene.cpp @@ -479,8 +479,8 @@ void KRScene::updateOctree(const KRViewport &viewport) { m_pRootNode->updateLODVisibility(viewport); - std::set newNodes = m_newNodes; - std::set modifiedNodes = m_modifiedNodes; + std::set newNodes = std::move(m_newNodes); + std::set modifiedNodes = std::move(m_modifiedNodes); m_newNodes.clear(); m_modifiedNodes.clear(); diff --git a/KREngine/kraken/KRSpotLight.cpp b/KREngine/kraken/KRSpotLight.cpp index 0ca149c..f8018c5 100644 --- a/KREngine/kraken/KRSpotLight.cpp +++ b/KREngine/kraken/KRSpotLight.cpp @@ -49,3 +49,13 @@ void KRSpotLight::setInnerAngle(float innerAngle) { void KRSpotLight::setOuterAngle(float outerAngle) { m_outerAngle = outerAngle; } + +KRAABB KRSpotLight::getBounds() { + float influence_radius = m_decayStart - sqrt(m_intensity * 0.01f) / sqrt(KRLIGHT_MIN_INFLUENCE); + if(influence_radius < m_flareOcclusionSize) { + influence_radius = m_flareOcclusionSize; + } + return KRAABB(KRVector3(-influence_radius), KRVector3(influence_radius), getModelMatrix()); +} + + diff --git a/KREngine/kraken/KRSpotLight.h b/KREngine/kraken/KRSpotLight.h index 6ea1b60..7ab2772 100644 --- a/KREngine/kraken/KRSpotLight.h +++ b/KREngine/kraken/KRSpotLight.h @@ -19,7 +19,7 @@ public: virtual std::string getElementName(); virtual tinyxml2::XMLElement *saveXML( tinyxml2::XMLNode *parent); virtual void loadXML(tinyxml2::XMLElement *e); - + virtual KRAABB getBounds(); float getInnerAngle(); float getOuterAngle(); From d05b6c434cd2611d6f371b3a927278010c21e9ea Mon Sep 17 00:00:00 2001 From: Kearwood Gilbert Date: Wed, 5 Feb 2014 01:09:10 -0800 Subject: [PATCH 35/84] Corrected bug in KRTextureTGA that caused it to assert on release builds (glDebug should only be called on debug builds). --HG-- branch : nfb --- KREngine/kraken/KRTextureTGA.cpp | 52 +++++++++----------------------- 1 file changed, 15 insertions(+), 37 deletions(-) diff --git a/KREngine/kraken/KRTextureTGA.cpp b/KREngine/kraken/KRTextureTGA.cpp index 083c7fa..05b1352 100644 --- a/KREngine/kraken/KRTextureTGA.cpp +++ b/KREngine/kraken/KRTextureTGA.cpp @@ -120,13 +120,9 @@ bool KRTextureTGA::uploadTexture(GLenum target, int lod_max_dim, int ¤t_lo pSource += 3; } //#endif - glTexImage2D(target, 0, internal_format, pHeader->width, pHeader->height, 0, GL_BGRA, GL_UNSIGNED_BYTE, (GLvoid *)converted_image); + GLDEBUG(glTexImage2D(target, 0, internal_format, pHeader->width, pHeader->height, 0, GL_BGRA, GL_UNSIGNED_BYTE, (GLvoid *)converted_image)); free(converted_image); - err = glGetError(); - if (err != GL_NO_ERROR) { - m_pData->unlock(); - return false; - } + current_lod_max_dim = m_max_lod_max_dim; } break; @@ -146,17 +142,12 @@ bool KRTextureTGA::uploadTexture(GLenum target, int lod_max_dim, int ¤t_lo pSource += 4; } - glTexImage2D(target, 0, internal_format, pHeader->width, pHeader->height, 0, GL_BGRA, GL_UNSIGNED_BYTE, (GLvoid *)converted_image); + GLDEBUG(glTexImage2D(target, 0, internal_format, pHeader->width, pHeader->height, 0, GL_BGRA, GL_UNSIGNED_BYTE, (GLvoid *)converted_image)); free(converted_image); } else { - glTexImage2D(target, 0, internal_format, pHeader->width, pHeader->height, 0, GL_BGRA, GL_UNSIGNED_BYTE, (GLvoid *)pData); + GLDEBUG(glTexImage2D(target, 0, internal_format, pHeader->width, pHeader->height, 0, GL_BGRA, GL_UNSIGNED_BYTE, (GLvoid *)pData)); } - err = glGetError(); - if (err != GL_NO_ERROR) { - m_pData->unlock(); - return false; - } current_lod_max_dim = m_max_lod_max_dim; } break; @@ -224,13 +215,8 @@ bool KRTextureTGA::uploadTexture(GLenum target, int lod_max_dim, int ¤t_lo } } } - glTexImage2D(target, 0, internal_format, pHeader->width, pHeader->height, 0, GL_BGRA, GL_UNSIGNED_BYTE, (GLvoid *)converted_image); + GLDEBUG(glTexImage2D(target, 0, internal_format, pHeader->width, pHeader->height, 0, GL_BGRA, GL_UNSIGNED_BYTE, (GLvoid *)converted_image)); free(converted_image); - err = glGetError(); - if (err != GL_NO_ERROR) { - m_pData->unlock(); - return false; - } current_lod_max_dim = m_max_lod_max_dim; } break; @@ -264,13 +250,8 @@ bool KRTextureTGA::uploadTexture(GLenum target, int lod_max_dim, int ¤t_lo } } } - glTexImage2D(target, 0, internal_format, pHeader->width, pHeader->height, 0, GL_BGRA, GL_UNSIGNED_BYTE, (GLvoid *)converted_image); + GLDEBUG(glTexImage2D(target, 0, internal_format, pHeader->width, pHeader->height, 0, GL_BGRA, GL_UNSIGNED_BYTE, (GLvoid *)converted_image)); free(converted_image); - err = glGetError(); - if (err != GL_NO_ERROR) { - m_pData->unlock(); - return false; - } current_lod_max_dim = m_max_lod_max_dim; } break; @@ -333,18 +314,15 @@ KRTexture *KRTextureTGA::compress(bool premultiply_alpha) GLint compressed_size = 0; GLenum err = GL_NO_ERROR; while(err == GL_NO_ERROR) { - glGetTexLevelParameteriv(GL_TEXTURE_2D, lod_level, GL_TEXTURE_COMPRESSED_IMAGE_SIZE, &compressed_size); - err = glGetError(); - if(err == GL_NO_ERROR) { - KRDataBlock *new_block = new KRDataBlock(); - new_block->expand(compressed_size); - new_block->lock(); - GLDEBUG(glGetCompressedTexImage(GL_TEXTURE_2D, lod_level, new_block->getStart())); - new_block->unlock(); - blocks.push_back(new_block); - - lod_level++; - } + GLDEBUG(glGetTexLevelParameteriv(GL_TEXTURE_2D, lod_level, GL_TEXTURE_COMPRESSED_IMAGE_SIZE, &compressed_size)); + KRDataBlock *new_block = new KRDataBlock(); + new_block->expand(compressed_size); + new_block->lock(); + GLDEBUG(glGetCompressedTexImage(GL_TEXTURE_2D, lod_level, new_block->getStart())); + new_block->unlock(); + blocks.push_back(new_block); + + lod_level++; } if(err != GL_INVALID_VALUE) { // err will equal GL_INVALID_VALUE when From 877b4b9bb74ecaa7a6ca0faab9431bf442a9c92a Mon Sep 17 00:00:00 2001 From: Kearwood Gilbert Date: Thu, 13 Feb 2014 00:36:54 -0800 Subject: [PATCH 36/84] Implemented KRTriangle3 class. Implemented swept sphere - mesh intersection testing / sphereCast functionality. - Not yet fully tested or optimized. --HG-- branch : nfb --- KREngine/Kraken.xcodeproj/project.pbxproj | 12 + KREngine/kraken/KRAABB.cpp | 36 +++ KREngine/kraken/KRAABB.h | 1 + KREngine/kraken/KRCollider.cpp | 47 +++- KREngine/kraken/KRCollider.h | 1 + KREngine/kraken/KRHitInfo.cpp | 16 +- KREngine/kraken/KRHitInfo.h | 6 +- KREngine/kraken/KRMesh.cpp | 200 +++++++++------- KREngine/kraken/KRMesh.h | 6 +- KREngine/kraken/KROctree.cpp | 22 ++ KREngine/kraken/KROctree.h | 1 + KREngine/kraken/KROctreeNode.cpp | 36 +++ KREngine/kraken/KROctreeNode.h | 2 + KREngine/kraken/KRScene.cpp | 6 + KREngine/kraken/KRScene.h | 1 + KREngine/kraken/KRTriangle3.cpp | 278 ++++++++++++++++++++++ KREngine/kraken/KRTriangle3.h | 63 +++++ 17 files changed, 634 insertions(+), 100 deletions(-) create mode 100644 KREngine/kraken/KRTriangle3.cpp create mode 100644 KREngine/kraken/KRTriangle3.h diff --git a/KREngine/Kraken.xcodeproj/project.pbxproj b/KREngine/Kraken.xcodeproj/project.pbxproj index 63028e0..5708595 100644 --- a/KREngine/Kraken.xcodeproj/project.pbxproj +++ b/KREngine/Kraken.xcodeproj/project.pbxproj @@ -388,6 +388,10 @@ E4F027E11697BFFF00D4427D /* KRAudioBuffer.h in Headers */ = {isa = PBXBuildFile; fileRef = E4F027DD1697BFFF00D4427D /* KRAudioBuffer.h */; settings = {ATTRIBUTES = (Public, ); }; }; E4F027F71698115600D4427D /* AudioToolbox.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = E4F027F61698115600D4427D /* AudioToolbox.framework */; }; E4F027FA1698116000D4427D /* AudioToolbox.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = E4F027F91698116000D4427D /* AudioToolbox.framework */; }; + E4F89BB418A6DB1200015637 /* KRTriangle3.cpp in Sources */ = {isa = PBXBuildFile; fileRef = E4F89BB218A6DB1200015637 /* KRTriangle3.cpp */; }; + E4F89BB518A6DB1200015637 /* KRTriangle3.cpp in Sources */ = {isa = PBXBuildFile; fileRef = E4F89BB218A6DB1200015637 /* KRTriangle3.cpp */; }; + E4F89BB618A6DB1200015637 /* KRTriangle3.h in Headers */ = {isa = PBXBuildFile; fileRef = E4F89BB318A6DB1200015637 /* KRTriangle3.h */; }; + E4F89BB718A6DB1200015637 /* KRTriangle3.h in Headers */ = {isa = PBXBuildFile; fileRef = E4F89BB318A6DB1200015637 /* KRTriangle3.h */; }; E4F975321536220900FD60B2 /* KRNode.h in Headers */ = {isa = PBXBuildFile; fileRef = E4F975311536220900FD60B2 /* KRNode.h */; settings = {ATTRIBUTES = (); }; }; E4F975331536220900FD60B2 /* KRNode.h in Headers */ = {isa = PBXBuildFile; fileRef = E4F975311536220900FD60B2 /* KRNode.h */; settings = {ATTRIBUTES = (Public, ); }; }; E4F975361536221C00FD60B2 /* KRNode.cpp in Sources */ = {isa = PBXBuildFile; fileRef = E4F975351536221C00FD60B2 /* KRNode.cpp */; }; @@ -685,6 +689,8 @@ E4F027DD1697BFFF00D4427D /* KRAudioBuffer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; lineEnding = 0; path = KRAudioBuffer.h; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.objcpp; }; E4F027F61698115600D4427D /* AudioToolbox.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AudioToolbox.framework; path = Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.7.sdk/System/Library/Frameworks/AudioToolbox.framework; sourceTree = DEVELOPER_DIR; }; E4F027F91698116000D4427D /* AudioToolbox.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AudioToolbox.framework; path = System/Library/Frameworks/AudioToolbox.framework; sourceTree = SDKROOT; }; + E4F89BB218A6DB1200015637 /* KRTriangle3.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = KRTriangle3.cpp; sourceTree = ""; }; + E4F89BB318A6DB1200015637 /* KRTriangle3.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = KRTriangle3.h; sourceTree = ""; }; E4F975311536220900FD60B2 /* KRNode.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; lineEnding = 0; path = KRNode.h; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.objcpp; }; E4F975351536221C00FD60B2 /* KRNode.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; lineEnding = 0; path = KRNode.cpp; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.cpp; }; /* End PBXFileReference section */ @@ -948,6 +954,8 @@ E4EC73C01720B1FF0065299F /* KRVector4.h */, E48CF940173453990005EBBB /* KRFloat.cpp */, E48CF941173453990005EBBB /* KRFloat.h */, + E4F89BB218A6DB1200015637 /* KRTriangle3.cpp */, + E4F89BB318A6DB1200015637 /* KRTriangle3.h */, ); name = Math; sourceTree = ""; @@ -1369,6 +1377,7 @@ E48B68171697794F00D99917 /* KRAudioSource.h in Headers */, E4F027C916979CCD00D4427D /* KRAudioManager.h in Headers */, E4F027E01697BFFF00D4427D /* KRAudioBuffer.h in Headers */, + E4F89BB618A6DB1200015637 /* KRTriangle3.h in Headers */, E4943233169E08D200BCB891 /* KRAmbientZone.h in Headers */, E499BF1B16AE747C007FCDBE /* KRVector2.h in Headers */, E499BF1E16AE751E007FCDBE /* KRSceneManager.h in Headers */, @@ -1447,6 +1456,7 @@ E4F027CA16979CCD00D4427D /* KRAudioManager.h in Headers */, E4F027D116979CE200D4427D /* KRAudioSample.h in Headers */, E4F027E11697BFFF00D4427D /* KRAudioBuffer.h in Headers */, + E4F89BB718A6DB1200015637 /* KRTriangle3.h in Headers */, E4943234169E08D200BCB891 /* KRAmbientZone.h in Headers */, E414F9AF1694DA37000B3D58 /* KRUnknown.h in Headers */, E45E03BD18790DD1006DA23F /* PVRTTexture.h in Headers */, @@ -1717,6 +1727,7 @@ E4924C2B15EE96AB00B965C6 /* KROctreeNode.cpp in Sources */, E404702018695DD200F01F42 /* KRTextureKTX.cpp in Sources */, E40BA45415EFF79500D7C3DD /* KRAABB.cpp in Sources */, + E4F89BB418A6DB1200015637 /* KRTriangle3.cpp in Sources */, E488399415F928CA00BD66D5 /* KRBundle.cpp in Sources */, E488399C15F92BE000BD66D5 /* KRBundleManager.cpp in Sources */, E4B175AC161F5A1000B8FB80 /* KRTexture.cpp in Sources */, @@ -1808,6 +1819,7 @@ E40F9833184A7BAC00CFA4D8 /* KRSprite.cpp in Sources */, E4CA10EA1637BD2B005D9400 /* KRTexturePVR.cpp in Sources */, E4CA10F01637BD58005D9400 /* KRTextureTGA.cpp in Sources */, + E4F89BB518A6DB1200015637 /* KRTriangle3.cpp in Sources */, E4CA11791639CC90005D9400 /* KRViewport.cpp in Sources */, E41843921678704000DBD6CF /* KRCollider.cpp in Sources */, E4324BAF16444E120043185B /* KRParticleSystemNewtonian.cpp in Sources */, diff --git a/KREngine/kraken/KRAABB.cpp b/KREngine/kraken/KRAABB.cpp index 2532d26..9998775 100644 --- a/KREngine/kraken/KRAABB.cpp +++ b/KREngine/kraken/KRAABB.cpp @@ -279,6 +279,42 @@ bool KRAABB::intersectsRay(const KRVector3 &v1, const KRVector3 &dir) const return true; /* ray hits box */ } +bool KRAABB::intersectsSphere(const KRVector3 ¢er, float radius) const +{ + // Arvo's Algorithm + + float squaredDistance = 0; + + // process X + if (center.x < min.x) { + float diff = center.x - min.x; + squaredDistance += diff * diff; + } else if (center.x > max.x) { + float diff = center.x - max.x; + squaredDistance += diff * diff; + } + + // process Y + if (center.y < min.y) { + float diff = center.y - min.y; + squaredDistance += diff * diff; + } else if (center.y > max.y) { + float diff = center.y - max.y; + squaredDistance += diff * diff; + } + + // process Z + if (center.z < min.z) { + float diff = center.z - min.z; + squaredDistance += diff * diff; + } else if (center.z > max.z) { + float diff = center.z - max.z; + squaredDistance += diff * diff; + } + + return squaredDistance <= radius; +} + void KRAABB::encapsulate(const KRAABB & b) { if(b.min.x < min.x) min.x = b.min.x; diff --git a/KREngine/kraken/KRAABB.h b/KREngine/kraken/KRAABB.h index a0fbbfd..6a03c07 100644 --- a/KREngine/kraken/KRAABB.h +++ b/KREngine/kraken/KRAABB.h @@ -35,6 +35,7 @@ public: bool intersectsLine(const KRVector3 &v1, const KRVector3 &v2) const; bool intersectsRay(const KRVector3 &v1, const KRVector3 &dir) const; + bool intersectsSphere(const KRVector3 ¢er, float radius) const; void encapsulate(const KRAABB & b); KRAABB& operator =(const KRAABB& b); diff --git a/KREngine/kraken/KRCollider.cpp b/KREngine/kraken/KRCollider.cpp index ad7eba5..e6ee63f 100644 --- a/KREngine/kraken/KRCollider.cpp +++ b/KREngine/kraken/KRCollider.cpp @@ -99,14 +99,17 @@ bool KRCollider::lineCast(const KRVector3 &v0, const KRVector3 &v1, KRHitInfo &h loadModel(); if(m_models.size()) { if(getBounds().intersectsLine(v0, v1)) { - KRHitInfo hitinfo_model_space; - if(hitinfo.didHit()) { - hitinfo_model_space = KRHitInfo(KRMat4::Dot(getInverseModelMatrix(), hitinfo.getPosition()), KRMat4::DotNoTranslate(getInverseModelMatrix(), hitinfo.getNormal()), hitinfo.getNode()); - } KRVector3 v0_model_space = KRMat4::Dot(getInverseModelMatrix(), v0); KRVector3 v1_model_space = KRMat4::Dot(getInverseModelMatrix(), v1); + KRHitInfo hitinfo_model_space; + if(hitinfo.didHit()) { + KRVector3 hit_position_model_space = KRMat4::Dot(getInverseModelMatrix(), hitinfo.getPosition()); + hitinfo_model_space = KRHitInfo(hit_position_model_space, KRMat4::DotNoTranslate(getInverseModelMatrix(), hitinfo.getNormal()), (hit_position_model_space - v0_model_space).magnitude(), hitinfo.getNode()); + } + if(m_models[0]->lineCast(v0_model_space, v1_model_space, hitinfo_model_space)) { - hitinfo = KRHitInfo(KRMat4::Dot(getModelMatrix(), hitinfo_model_space.getPosition()), KRVector3::Normalize(KRMat4::DotNoTranslate(getModelMatrix(), hitinfo_model_space.getNormal())), this); + KRVector3 hit_position_world_space = KRMat4::Dot(getModelMatrix(), hitinfo_model_space.getPosition()); + hitinfo = KRHitInfo(hit_position_world_space, KRVector3::Normalize(KRMat4::DotNoTranslate(getModelMatrix(), hitinfo_model_space.getNormal())), (hit_position_world_space - v0).magnitude(), this); return true; } } @@ -121,14 +124,38 @@ bool KRCollider::rayCast(const KRVector3 &v0, const KRVector3 &dir, KRHitInfo &h loadModel(); if(m_models.size()) { if(getBounds().intersectsRay(v0, dir)) { - KRHitInfo hitinfo_model_space; - if(hitinfo.didHit()) { - hitinfo_model_space = KRHitInfo(KRMat4::Dot(getInverseModelMatrix(), hitinfo.getPosition()), KRVector3::Normalize(KRMat4::DotNoTranslate(getInverseModelMatrix(), hitinfo.getNormal())), hitinfo.getNode()); - } KRVector3 v0_model_space = KRMat4::Dot(getInverseModelMatrix(), v0); KRVector3 dir_model_space = KRVector3::Normalize(KRMat4::DotNoTranslate(getInverseModelMatrix(), dir)); + KRHitInfo hitinfo_model_space; + if(hitinfo.didHit()) { + KRVector3 hit_position_model_space = KRMat4::Dot(getInverseModelMatrix(), hitinfo.getPosition()); + hitinfo_model_space = KRHitInfo(KRMat4::Dot(getInverseModelMatrix(), hitinfo.getPosition()), KRVector3::Normalize(KRMat4::DotNoTranslate(getInverseModelMatrix(), hitinfo.getNormal())), (hit_position_model_space - v0_model_space).magnitude(), hitinfo.getNode()); + } + if(m_models[0]->rayCast(v0_model_space, dir_model_space, hitinfo_model_space)) { - hitinfo = KRHitInfo(KRMat4::Dot(getModelMatrix(), hitinfo_model_space.getPosition()), KRVector3::Normalize(KRMat4::DotNoTranslate(getModelMatrix(), hitinfo_model_space.getNormal())), this); + KRVector3 hit_position_world_space = KRMat4::Dot(getModelMatrix(), hitinfo_model_space.getPosition()); + hitinfo = KRHitInfo(hit_position_world_space, KRVector3::Normalize(KRMat4::DotNoTranslate(getModelMatrix(), hitinfo_model_space.getNormal())), (hit_position_world_space - v0).magnitude(), this); + return true; + } + } + } + } + return false; +} + +bool KRCollider::sphereCast(const KRVector3 &v0, const KRVector3 &v1, float radius, KRHitInfo &hitinfo, unsigned int layer_mask) +{ + if(layer_mask & m_layer_mask) { // Only test if layer masks have a common bit set + loadModel(); + if(m_models.size()) { + KRAABB sphereCastBounds = KRAABB( + KRVector3(KRMIN(v0.x, v1.x) - radius, KRMIN(v0.y, v1.y) - radius, KRMIN(v0.z, v1.z) - radius), + KRVector3(KRMAX(v0.x, v1.x) + radius, KRMAX(v0.y, v1.y) + radius, KRMAX(v0.z, v1.z) + radius) + ); + + if(getBounds().intersects(sphereCastBounds)) { + if(m_models[0]->sphereCast(getModelMatrix(), v0, v1, radius, hitinfo)) { + hitinfo = KRHitInfo(hitinfo.getPosition(), hitinfo.getNormal(), hitinfo.getDistance(), this); return true; } } diff --git a/KREngine/kraken/KRCollider.h b/KREngine/kraken/KRCollider.h index cabf368..cbf106b 100644 --- a/KREngine/kraken/KRCollider.h +++ b/KREngine/kraken/KRCollider.h @@ -61,6 +61,7 @@ public: bool lineCast(const KRVector3 &v0, const KRVector3 &v1, KRHitInfo &hitinfo, unsigned int layer_mask); bool rayCast(const KRVector3 &v0, const KRVector3 &v1, KRHitInfo &hitinfo, unsigned int layer_mask); + bool sphereCast(const KRVector3 &v0, const KRVector3 &v1, float radius, KRHitInfo &hitinfo, unsigned int layer_mask); unsigned int getLayerMask(); void setLayerMask(unsigned int layer_mask); diff --git a/KREngine/kraken/KRHitInfo.cpp b/KREngine/kraken/KRHitInfo.cpp index 724dfb0..b0b376e 100644 --- a/KREngine/kraken/KRHitInfo.cpp +++ b/KREngine/kraken/KRHitInfo.cpp @@ -36,23 +36,23 @@ KRHitInfo::KRHitInfo() { m_position = KRVector3::Zero(); m_normal = KRVector3::Zero(); + m_distance = 0.0f; m_node = NULL; } -KRHitInfo::KRHitInfo(const KRVector3 &position, const KRVector3 &normal, KRNode *node) +KRHitInfo::KRHitInfo(const KRVector3 &position, const KRVector3 &normal, const float distance, KRNode *node) { m_position = position; - if(m_position == KRVector3::Zero()) { - KRContext::Log(KRContext::LOG_LEVEL_ERROR, "Zero position hitinfo"); - } m_normal = normal; + m_distance = distance; m_node = node; } -KRHitInfo::KRHitInfo(const KRVector3 &position, const KRVector3 &normal) +KRHitInfo::KRHitInfo(const KRVector3 &position, const KRVector3 &normal, const float distance) { m_position = position; m_normal = normal; + m_distance = distance; m_node = NULL; } @@ -76,6 +76,11 @@ KRVector3 KRHitInfo::getNormal() const return m_normal; } +float KRHitInfo::getDistance() const +{ + return m_distance; +} + KRNode *KRHitInfo::getNode() const { return m_node; @@ -85,6 +90,7 @@ KRHitInfo& KRHitInfo::operator =(const KRHitInfo& b) { m_position = b.m_position; m_normal = b.m_normal; + m_distance = b.m_distance; m_node = b.m_node; return *this; } diff --git a/KREngine/kraken/KRHitInfo.h b/KREngine/kraken/KRHitInfo.h index ecadc52..e02feed 100644 --- a/KREngine/kraken/KRHitInfo.h +++ b/KREngine/kraken/KRHitInfo.h @@ -39,12 +39,13 @@ class KRNode; class KRHitInfo { public: KRHitInfo(); - KRHitInfo(const KRVector3 &position, const KRVector3 &normal); - KRHitInfo(const KRVector3 &position, const KRVector3 &normal, KRNode *node); + KRHitInfo(const KRVector3 &position, const KRVector3 &normal, const float distance); + KRHitInfo(const KRVector3 &position, const KRVector3 &normal, const float distance, KRNode *node); ~KRHitInfo(); KRVector3 getPosition() const; KRVector3 getNormal() const; + float getDistance() const; KRNode *getNode() const; bool didHit() const; @@ -55,6 +56,7 @@ private: KRNode *m_node; KRVector3 m_position; KRVector3 m_normal; + float m_distance; }; #endif diff --git a/KREngine/kraken/KRMesh.cpp b/KREngine/kraken/KRMesh.cpp index f379f27..99e4bcd 100644 --- a/KREngine/kraken/KRMesh.cpp +++ b/KREngine/kraken/KRMesh.cpp @@ -35,6 +35,7 @@ #include "KRMesh.h" #include "KRVector3.h" +#include "KRTriangle3.h" #include "KRShader.h" #include "KRShaderManager.h" #include "KRContext.h" @@ -981,94 +982,41 @@ KRMesh::model_format_t KRMesh::getModelFormat() const return f; } -bool KRMesh::rayCast(const KRVector3 &line_v0, const KRVector3 &dir, const KRVector3 &tri_v0, const KRVector3 &tri_v1, const KRVector3 &tri_v2, const KRVector3 &tri_n0, const KRVector3 &tri_n1, const KRVector3 &tri_n2, KRHitInfo &hitinfo) +bool KRMesh::rayCast(const KRVector3 &start, const KRVector3 &dir, const KRTriangle3 &tri, const KRVector3 &tri_n0, const KRVector3 &tri_n1, const KRVector3 &tri_n2, KRHitInfo &hitinfo) { - // algorithm based on Dan Sunday's implementation at http://geomalgorithms.com/a06-_intersect-2.html - const float SMALL_NUM = 0.00000001; // anything that avoids division overflow - KRVector3 u, v, n; // triangle vectors - KRVector3 w0, w; // ray vectors - float r, a, b; // params to calc ray-plane intersect - - // get triangle edge vectors and plane normal - u = tri_v1 - tri_v0; - v = tri_v2 - tri_v0; - n = KRVector3::Cross(u, v); // cross product - if (n == KRVector3::Zero()) // triangle is degenerate - return false; // do not deal with this case - - w0 = line_v0 - tri_v0; - a = -KRVector3::Dot(n, w0); - b = KRVector3::Dot(n,dir); - if (fabs(b) < SMALL_NUM) { // ray is parallel to triangle plane - if (a == 0) - return false; // ray lies in triangle plane - else { - return false; // ray disjoint from plane + KRVector3 hit_point; + if(tri.rayCast(start, dir, hit_point)) { + // ---===--- hit_point is in triangle ---===--- + + float new_hit_distance = (hit_point - start).magnitude(); + if(new_hit_distance < hitinfo.getDistance() || !hitinfo.didHit()) { + // Update the hitinfo object if this hit is closer than the prior hit + + // Interpolate between the three vertex normals, performing a 3-way lerp of tri_n0, tri_n1, and tri_n2 + float distance_v0 = (tri[0] - hit_point).magnitude(); + float distance_v1 = (tri[1] - hit_point).magnitude(); + float distance_v2 = (tri[2] - hit_point).magnitude(); + float distance_total = distance_v0 + distance_v1 + distance_v2; + distance_v0 /= distance_total; + distance_v1 /= distance_total; + distance_v2 /= distance_total; + KRVector3 normal = KRVector3::Normalize(tri_n0 * (1.0 - distance_v0) + tri_n1 * (1.0 - distance_v1) + tri_n2 * (1.0 - distance_v2)); + + hitinfo = KRHitInfo(hit_point, normal, new_hit_distance); + return true; + } else { + return false; // The hit was farther than an existing hit } - } - - // get intersect point of ray with triangle plane - r = a / b; - if (r < 0.0) // ray goes away from triangle - return false; // => no intersect - // for a segment, also test if (r > 1.0) => no intersect - - - KRVector3 hit_point = line_v0 + dir * r; // intersect point of ray and plane - - // is hit_point inside triangle? - float uu, uv, vv, wu, wv, D; - uu = KRVector3::Dot(u,u); - uv = KRVector3::Dot(u,v); - vv = KRVector3::Dot(v,v); - w = hit_point - tri_v0; - wu = KRVector3::Dot(w,u); - wv = KRVector3::Dot(w,v); - D = uv * uv - uu * vv; - - // get and test parametric coords - float s, t; - s = (uv * wv - vv * wu) / D; - if (s < 0.0 || s > 1.0) // hit_point is outside triangle - return false; - t = (uv * wu - uu * wv) / D; - if (t < 0.0 || (s + t) > 1.0) // hit_point is outside triangle - return false; - - float new_hit_distance_sqr = (hit_point - line_v0).sqrMagnitude(); - float prev_hit_distance_sqr = (hitinfo.getPosition() - line_v0).sqrMagnitude(); - // ---===--- hit_point is in triangle ---===--- - - - if(new_hit_distance_sqr < prev_hit_distance_sqr || !hitinfo.didHit()) { - // Update the hitinfo object if this hit is closer than the prior hit - - // Interpolate between the three vertex normals, performing a 3-way lerp of tri_n0, tri_n1, and tri_n2 - float distance_v0 = (tri_v0 - hit_point).magnitude(); - float distance_v1 = (tri_v1 - hit_point).magnitude(); - float distance_v2 = (tri_v2 - hit_point).magnitude(); - float distance_total = distance_v0 + distance_v1 + distance_v2; - distance_v0 /= distance_total; - distance_v1 /= distance_total; - distance_v2 /= distance_total; - KRVector3 normal = KRVector3::Normalize(tri_n0 * (1.0 - distance_v0) + tri_n1 * (1.0 - distance_v1) + tri_n2 * (1.0 - distance_v2)); - - hitinfo = KRHitInfo(hit_point, normal); - return true; } else { - return false; // Either no hit, or the hit was farther than an existing hit + // Dit not hit the triangle + return false; } + } -/* -bool KRMesh::rayCast(const KRVector3 &line_v0, const KRVector3 &dir, int tri_index0, int tri_index1, int tri_index2, KRHitInfo &hitinfo) const -{ - return rayCast(line_v0, dir, getVertexPosition(tri_index0), getVertexPosition(tri_index1), getVertexPosition(tri_index2), getVertexNormal(tri_index0), getVertexNormal(tri_index1), getVertexNormal(tri_index2), hitinfo); -} - */ -bool KRMesh::rayCast(const KRVector3 &v0, const KRVector3 &dir, KRHitInfo &hitinfo) const +bool KRMesh::rayCast(const KRVector3 &start, const KRVector3 &dir, KRHitInfo &hitinfo) const { m_pData->lock(); bool hit_found = false; @@ -1084,7 +1032,9 @@ bool KRMesh::rayCast(const KRVector3 &v0, const KRVector3 &dir, KRHitInfo &hitin tri_vert_index[1] = getTriangleVertexIndex(submesh_index, triangle_index*3 + 1); tri_vert_index[2] = getTriangleVertexIndex(submesh_index, triangle_index*3 + 2); - if(rayCast(v0, dir, getVertexPosition(tri_vert_index[0]), getVertexPosition(tri_vert_index[1]), getVertexPosition(tri_vert_index[2]), getVertexNormal(tri_vert_index[0]), getVertexNormal(tri_vert_index[1]), getVertexNormal(tri_vert_index[2]), hitinfo)) hit_found = true; + KRTriangle3 tri = KRTriangle3(getVertexPosition(tri_vert_index[0]), getVertexPosition(tri_vert_index[1]), getVertexPosition(tri_vert_index[2])); + + if(rayCast(start, dir, tri, getVertexNormal(tri_vert_index[0]), getVertexNormal(tri_vert_index[1]), getVertexNormal(tri_vert_index[2]), hitinfo)) hit_found = true; } break; /* @@ -1111,6 +1061,94 @@ bool KRMesh::rayCast(const KRVector3 &v0, const KRVector3 &dir, KRHitInfo &hitin return hit_found; } + +bool KRMesh::sphereCast(const KRMat4 &model_to_world, const KRVector3 &v0, const KRVector3 &v1, float radius, KRHitInfo &hitinfo) const +{ + m_pData->lock(); + KRHitInfo new_hitinfo; + KRVector3 dir = KRVector3::Normalize(v1 - v0); + + bool hit_found = false; + for(int submesh_index=0; submesh_index < getSubmeshCount(); submesh_index++) { + int vertex_count = getVertexCount(submesh_index); + switch(getModelFormat()) { + case KRENGINE_MODEL_FORMAT_TRIANGLES: + case KRENGINE_MODEL_FORMAT_INDEXED_TRIANGLES: + for(int triangle_index=0; triangle_index < vertex_count / 3; triangle_index++) { + int tri_vert_index[3]; // FINDME, HACK! This is not very efficient for indexed collider meshes... + tri_vert_index[0] = getTriangleVertexIndex(submesh_index, triangle_index*3); + tri_vert_index[1] = getTriangleVertexIndex(submesh_index, triangle_index*3 + 1); + tri_vert_index[2] = getTriangleVertexIndex(submesh_index, triangle_index*3 + 2); + + KRTriangle3 tri = KRTriangle3(getVertexPosition(tri_vert_index[0]), getVertexPosition(tri_vert_index[1]), getVertexPosition(tri_vert_index[2])); + + if(sphereCast(model_to_world, v0, dir, radius, tri, getVertexNormal(tri_vert_index[0]), getVertexNormal(tri_vert_index[1]), getVertexNormal(tri_vert_index[2]), new_hitinfo)) hit_found = true; + } + break; + /* + + NOTE: Not yet supported: + + case KRENGINE_MODEL_FORMAT_STRIP: + case KRENGINE_MODEL_FORMAT_INDEXED_STRIP: + for(int triangle_index=0; triangle_index < vertex_count - 2; triangle_index++) { + int tri_vert_index[3]; + tri_vert_index[0] = getTriangleVertexIndex(submesh_index, vertex_start + triangle_index*3); + tri_vert_index[1] = getTriangleVertexIndex(submesh_index, vertex_start + triangle_index*3 + 1); + tri_vert_index[2] = getTriangleVertexIndex(submesh_index, vertex_start + triangle_index*3 + 2); + + if(sphereCast(model_to_world, v0, v1, getVertexPosition(vertex_start + triangle_index), getVertexPosition(vertex_start + triangle_index+1), getVertexPosition(vertex_start + triangle_index+2), getVertexNormal(vertex_start + triangle_index), getVertexNormal(vertex_start + triangle_index+1), getVertexNormal(vertex_start + triangle_index+2), new_hitinfo)) hit_found = true; + } + break; + */ + default: + break; + } + } + m_pData->unlock(); + + + if(hit_found) { + if(new_hitinfo.getDistance() <= (v1 - v0).magnitude()) { + // The hit was between v1 and v2 + hitinfo = new_hitinfo; + return true; + } + } + return false; // Either no hit, or the hit was beyond v1 +} + +bool KRMesh::sphereCast(const KRMat4 &model_to_world, const KRVector3 &start, const KRVector3 &dir, float radius, const KRTriangle3 &tri, const KRVector3 &tri_n0, const KRVector3 &tri_n1, const KRVector3 &tri_n2, KRHitInfo &hitinfo) +{ + + // bool sphereCast(const KRVector3 &start, const KRVector3 &dir, float radius, KRVector3 &hit_point, float &hit_distance) const; + + KRVector3 new_hit_point; + float new_hit_distance; + + KRTriangle3 world_tri = KRTriangle3(KRMat4::Dot(model_to_world, tri[0]), KRMat4::Dot(model_to_world, tri[1]), KRMat4::Dot(model_to_world, tri[2])); + + if(world_tri.sphereCast(start, dir, radius, new_hit_point, new_hit_distance)) { + if(!hitinfo.didHit() || hitinfo.getDistance() > new_hit_distance) { + + // Interpolate between the three vertex normals, performing a 3-way lerp of tri_n0, tri_n1, and tri_n2 + float distance_v0 = (tri[0] - new_hit_point).magnitude(); + float distance_v1 = (tri[1] - new_hit_point).magnitude(); + float distance_v2 = (tri[2] - new_hit_point).magnitude(); + float distance_total = distance_v0 + distance_v1 + distance_v2; + distance_v0 /= distance_total; + distance_v1 /= distance_total; + distance_v2 /= distance_total; + KRVector3 normal = KRVector3::Normalize(KRMat4::DotNoTranslate(model_to_world, (tri_n0 * (1.0 - distance_v0) + tri_n1 * (1.0 - distance_v1) + tri_n2 * (1.0 - distance_v2)))); + + hitinfo = KRHitInfo(new_hit_point, normal, new_hit_distance); + return true; + } + } + + return false; +} + bool KRMesh::lineCast(const KRVector3 &v0, const KRVector3 &v1, KRHitInfo &hitinfo) const { m_pData->lock(); diff --git a/KREngine/kraken/KRMesh.h b/KREngine/kraken/KRMesh.h index 92e2162..745ed3b 100644 --- a/KREngine/kraken/KRMesh.h +++ b/KREngine/kraken/KRMesh.h @@ -54,6 +54,7 @@ class KRMaterial; class KRNode; +class KRTriangle3; class KRMesh : public KRResource { @@ -208,6 +209,7 @@ public: bool lineCast(const KRVector3 &v0, const KRVector3 &v1, KRHitInfo &hitinfo) const; bool rayCast(const KRVector3 &v0, const KRVector3 &dir, KRHitInfo &hitinfo) const; + bool sphereCast(const KRMat4 &model_to_world, const KRVector3 &v0, const KRVector3 &v1, float radius, KRHitInfo &hitinfo) const; static int GetLODCoverage(const std::string &name); private: @@ -217,8 +219,8 @@ private: void getSubmeshes(); -// bool rayCast(const KRVector3 &line_v0, const KRVector3 &dir, int tri_index0, int tri_index1, int tri_index2, KRHitInfo &hitinfo) const; - static bool rayCast(const KRVector3 &line_v0, const KRVector3 &dir, const KRVector3 &tri_v0, const KRVector3 &tri_v1, const KRVector3 &tri_v2, const KRVector3 &tri_n0, const KRVector3 &tri_n1, const KRVector3 &tri_n2, KRHitInfo &hitinfo); + static bool rayCast(const KRVector3 &start, const KRVector3 &dir, const KRTriangle3 &tri, const KRVector3 &tri_n0, const KRVector3 &tri_n1, const KRVector3 &tri_n2, KRHitInfo &hitinfo); + static bool sphereCast(const KRMat4 &model_to_world, const KRVector3 &start, const KRVector3 &dir, float radius, const KRTriangle3 &tri, const KRVector3 &tri_n0, const KRVector3 &tri_n1, const KRVector3 &tri_n2, KRHitInfo &hitinfo); 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; diff --git a/KREngine/kraken/KROctree.cpp b/KREngine/kraken/KROctree.cpp index df006e5..641407f 100644 --- a/KREngine/kraken/KROctree.cpp +++ b/KREngine/kraken/KROctree.cpp @@ -132,3 +132,25 @@ bool KROctree::rayCast(const KRVector3 &v0, const KRVector3 &dir, KRHitInfo &hit } return hit_found; } + +bool KROctree::sphereCast(const KRVector3 &v0, const KRVector3 &v1, float radius, KRHitInfo &hitinfo, unsigned int layer_mask) +{ + bool hit_found = false; + std::vector outer_colliders; + + for(std::set::iterator outer_nodes_itr=m_outerSceneNodes.begin(); outer_nodes_itr != m_outerSceneNodes.end(); outer_nodes_itr++) { + KRCollider *collider = dynamic_cast(*outer_nodes_itr); + if(collider) { + outer_colliders.push_back(collider); + } + } + for(std::vector::iterator itr=outer_colliders.begin(); itr != outer_colliders.end(); itr++) { + if((*itr)->sphereCast(v0, v1, radius, hitinfo, layer_mask)) hit_found = true; + } + + if(m_pRootNode) { + if(m_pRootNode->sphereCast(v0, v1, radius, hitinfo, layer_mask)) hit_found = true; + } + return hit_found; +} + diff --git a/KREngine/kraken/KROctree.h b/KREngine/kraken/KROctree.h index 7f0cd12..7e7edff 100644 --- a/KREngine/kraken/KROctree.h +++ b/KREngine/kraken/KROctree.h @@ -30,6 +30,7 @@ public: bool lineCast(const KRVector3 &v0, const KRVector3 &v1, KRHitInfo &hitinfo, unsigned int layer_mask); bool rayCast(const KRVector3 &v0, const KRVector3 &dir, KRHitInfo &hitinfo, unsigned int layer_mask); + bool sphereCast(const KRVector3 &v0, const KRVector3 &v1, float radius, KRHitInfo &hitinfo, unsigned int layer_mask); private: KROctreeNode *m_pRootNode; diff --git a/KREngine/kraken/KROctreeNode.cpp b/KREngine/kraken/KROctreeNode.cpp index 7332275..929289f 100644 --- a/KREngine/kraken/KROctreeNode.cpp +++ b/KREngine/kraken/KROctreeNode.cpp @@ -251,3 +251,39 @@ bool KROctreeNode::rayCast(const KRVector3 &v0, const KRVector3 &dir, KRHitInfo return hit_found; } + +bool KROctreeNode::sphereCast(const KRVector3 &v0, const KRVector3 &v1, float radius, KRHitInfo &hitinfo, unsigned int layer_mask) +{ + bool hit_found = false; + /* + // FINDME, TODO - Adapt this optimization to work with sphereCasts + + if(hitinfo.didHit() && v1 != hitinfo.getPosition()) { + // Optimization: If we already have a hit, only search for hits that are closer + hit_found = sphereCast(v0, hitinfo.getPosition(), radius, hitinfo, layer_mask); + } else { + */ + KRAABB swept_bounds = KRAABB(KRVector3(KRMIN(v0.x, v1.x) - radius, KRMIN(v0.y, v1.y) - radius, KRMIN(v0.z, v1.z) - radius), KRVector3(KRMAX(v0.x, v1.x) + radius, KRMAX(v0.y, v1.y) + radius, KRMAX(v0.z, v1.z) + radius)); + // FINDME, TODO - Investigate AABB - swept sphere intersections or OBB - AABB intersections: "if(getBounds().intersectsSweptSphere(v0, v1, radius)) {" + if(getBounds().intersects(swept_bounds)) { + + for(std::set::iterator nodes_itr=m_sceneNodes.begin(); nodes_itr != m_sceneNodes.end(); nodes_itr++) { + KRCollider *collider = dynamic_cast(*nodes_itr); + if(collider) { + if(collider->sphereCast(v0, v1, radius, hitinfo, layer_mask)) hit_found = true; + } + } + + for(int i=0; i<8; i++) { + if(m_children[i]) { + if(m_children[i]->sphereCast(v0, v1, radius, hitinfo, layer_mask)) { + hit_found = true; + } + } + } + } + // } + + return hit_found; +} + diff --git a/KREngine/kraken/KROctreeNode.h b/KREngine/kraken/KROctreeNode.h index c1bb7cf..84eb549 100644 --- a/KREngine/kraken/KROctreeNode.h +++ b/KREngine/kraken/KROctreeNode.h @@ -51,6 +51,8 @@ public: bool lineCast(const KRVector3 &v0, const KRVector3 &v1, KRHitInfo &hitinfo, unsigned int layer_mask); bool rayCast(const KRVector3 &v0, const KRVector3 &dir, KRHitInfo &hitinfo, unsigned int layer_mask); + bool sphereCast(const KRVector3 &v0, const KRVector3 &v1, float radius, KRHitInfo &hitinfo, unsigned int layer_mask); + private: KRAABB m_bounds; diff --git a/KREngine/kraken/KRScene.cpp b/KREngine/kraken/KRScene.cpp index b103167..1f07bc7 100644 --- a/KREngine/kraken/KRScene.cpp +++ b/KREngine/kraken/KRScene.cpp @@ -552,3 +552,9 @@ bool KRScene::rayCast(const KRVector3 &v0, const KRVector3 &dir, KRHitInfo &hiti return m_nodeTree.rayCast(v0, dir, hitinfo, layer_mask); } +bool KRScene::sphereCast(const KRVector3 &v0, const KRVector3 &v1, float radius, KRHitInfo &hitinfo, unsigned int layer_mask) +{ + return m_nodeTree.sphereCast(v0, v1, radius, hitinfo, layer_mask); +} + + diff --git a/KREngine/kraken/KRScene.h b/KREngine/kraken/KRScene.h index 9242a03..90ef75c 100644 --- a/KREngine/kraken/KRScene.h +++ b/KREngine/kraken/KRScene.h @@ -66,6 +66,7 @@ public: bool lineCast(const KRVector3 &v0, const KRVector3 &v1, KRHitInfo &hitinfo, unsigned int layer_mask); bool rayCast(const KRVector3 &v0, const KRVector3 &dir, KRHitInfo &hitinfo, unsigned int layer_mask); + bool sphereCast(const KRVector3 &v0, const KRVector3 &v1, float radius, KRHitInfo &hitinfo, unsigned int layer_mask); void renderFrame(float deltaTime, int width, int height); void render(KRCamera *pCamera, unordered_map &visibleBounds, const KRViewport &viewport, KRNode::RenderPass renderPass, bool new_frame); diff --git a/KREngine/kraken/KRTriangle3.cpp b/KREngine/kraken/KRTriangle3.cpp new file mode 100644 index 0000000..d4d4419 --- /dev/null +++ b/KREngine/kraken/KRTriangle3.cpp @@ -0,0 +1,278 @@ +// +// KRTriangle.cpp +// Kraken +// +// Created by Kearwood Gilbert on 2/8/2014. +// Copyright (c) 2014 Kearwood Software. All rights reserved. +// + +#include "KRTriangle3.h" +#include "KRVector3.h" + +KRTriangle3::KRTriangle3(const KRVector3 &v1, const KRVector3 &v2, const KRVector3 &v3) +{ + m_c[0] = v1; + m_c[1] = v2; + m_c[2] = v3; +} + +KRTriangle3::KRTriangle3(const KRTriangle3 &tri) +{ + m_c[0] = tri[0]; + m_c[1] = tri[1]; + m_c[3] = tri[3]; +} + + +KRTriangle3::~KRTriangle3() +{ + +} + +bool KRTriangle3::operator ==(const KRTriangle3& b) const +{ + return m_c[0] == b[0] && m_c[1] == b[1] && m_c[2] == b[2]; +} + +bool KRTriangle3::operator !=(const KRTriangle3& b) const +{ + return m_c[0] != b[0] || m_c[1] != b[1] || m_c[2] != b[2]; +} + +KRTriangle3& KRTriangle3::operator =(const KRTriangle3& b) +{ + + m_c[0] = b[0]; + m_c[1] = b[1]; + m_c[3] = b[3]; + return *this; +} + +KRVector3& KRTriangle3::operator[](unsigned i) +{ + return m_c[i]; +} + +KRVector3 KRTriangle3::operator[](unsigned i) const +{ + return m_c[i]; +} + + +bool KRTriangle3::rayCast(const KRVector3 &start, const KRVector3 &dir, KRVector3 &hit_point) const +{ + // algorithm based on Dan Sunday's implementation at http://geomalgorithms.com/a06-_intersect-2.html + const float SMALL_NUM = 0.00000001; // anything that avoids division overflow + KRVector3 u, v, n; // triangle vectors + KRVector3 w0, w; // ray vectors + float r, a, b; // params to calc ray-plane intersect + + // get triangle edge vectors and plane normal + u = m_c[1] - m_c[0]; + v = m_c[2] - m_c[0]; + n = KRVector3::Cross(u, v); // cross product + if (n == KRVector3::Zero()) // triangle is degenerate + return false; // do not deal with this case + + w0 = start - m_c[0]; + a = -KRVector3::Dot(n, w0); + b = KRVector3::Dot(n,dir); + if (fabs(b) < SMALL_NUM) { // ray is parallel to triangle plane + if (a == 0) + return false; // ray lies in triangle plane + else { + return false; // ray disjoint from plane + } + } + + // get intersect point of ray with triangle plane + r = a / b; + if (r < 0.0) // ray goes away from triangle + return false; // => no intersect + // for a segment, also test if (r > 1.0) => no intersect + + + KRVector3 plane_hit_point = start + dir * r; // intersect point of ray and plane + + // is plane_hit_point inside triangle? + float uu, uv, vv, wu, wv, D; + uu = KRVector3::Dot(u,u); + uv = KRVector3::Dot(u,v); + vv = KRVector3::Dot(v,v); + w = plane_hit_point - m_c[0]; + wu = KRVector3::Dot(w,u); + wv = KRVector3::Dot(w,v); + D = uv * uv - uu * vv; + + // get and test parametric coords + float s, t; + s = (uv * wv - vv * wu) / D; + if (s < 0.0 || s > 1.0) // plane_hit_point is outside triangle + return false; + t = (uv * wu - uu * wv) / D; + if (t < 0.0 || (s + t) > 1.0) // plane_hit_point is outside triangle + return false; + + // plane_hit_point is inside the triangle + hit_point = plane_hit_point; + return true; +} + +KRVector3 KRTriangle3::calculateNormal() const +{ + KRVector3 v1 = m_c[1] - m_c[0]; + KRVector3 v2 = m_c[2] - m_c[0]; + + return KRVector3::Normalize(KRVector3::Cross(v1, v2)); +} + +bool _intersectSphere(const KRVector3 &rO, const KRVector3 &rV, const KRVector3 &sO, float sR, float &distance) +{ + // From: http://archive.gamedev.net/archive/reference/articles/article1026.html + + // TODO - Move to another class? + KRVector3 Q = sO - rO; + float c = Q.magnitude(); + float v = KRVector3::Dot(Q, rV); + float d = sR * sR - (c * c - v * v); + + // If there was no intersection, return -1 + + if(d < 0.0) return false; + + // Return the distance to the [first] intersecting point + + distance = v - sqrt(d); + return true; +} + +bool _sameSide(const KRVector3 &p1, const KRVector3 &p2, const KRVector3 &a, const KRVector3 &b) +{ + // TODO - Move to KRVector3 class? + // From: http://stackoverflow.com/questions/995445/determine-if-a-3d-point-is-within-a-triangle + + KRVector3 cp1 = KRVector3::Cross(b - a, p1 - a); + KRVector3 cp2 = KRVector3::Cross(b - a, p2 - a); + if(KRVector3::Dot(cp1, cp2) >= 0) return true; + return false; +} + +KRVector3 _closestPointOnLine(const KRVector3 &a, const KRVector3 &b, const KRVector3 &p) +{ + // From: http://stackoverflow.com/questions/995445/determine-if-a-3d-point-is-within-a-triangle + + // Determine t (the length of the vector from ‘a’ to ‘p’) + + KRVector3 c = p - a; + KRVector3 V = KRVector3::Normalize(b - a); + float d = (a - b).magnitude(); + float t = KRVector3::Dot(V, c); + + // Check to see if ‘t’ is beyond the extents of the line segment + + if (t < 0) return a; + if (t > d) return b; + + // Return the point between ‘a’ and ‘b’ + + return a + V * t; +} + +KRVector3 KRTriangle3::closestPointOnTriangle(const KRVector3 &p) const +{ + KRVector3 a = m_c[0]; + KRVector3 b = m_c[1]; + KRVector3 c = m_c[2]; + + KRVector3 Rab = _closestPointOnLine(a, b, p); + KRVector3 Rbc = _closestPointOnLine(b, c, p); + KRVector3 Rca = _closestPointOnLine(c, a, p); + + // return closest [Rab, Rbc, Rca] to p; + float sd_Rab = (p - Rab).sqrMagnitude(); + float sd_Rbc = (p - Rbc).sqrMagnitude(); + float sd_Rca = (p - Rca).sqrMagnitude(); + + if(sd_Rab < sd_Rbc && sd_Rab < sd_Rca) { + return Rab; + } else if(sd_Rbc < sd_Rab && sd_Rbc < sd_Rca) { + return sd_Rbc; + } else { + return sd_Rca; + } +} + +bool KRTriangle3::sphereCast(const KRVector3 &start, const KRVector3 &dir, float radius, KRVector3 &hit_point, float &hit_distance) const +{ + // Dir must be normalized + const float SMALL_NUM = 0.00000001f; // anything that avoids division overflow + + KRVector3 tri_normal = calculateNormal(); + + float d = KRVector3::Dot(tri_normal, m_c[0]); + float e = KRVector3::Dot(tri_normal, start) - radius; + float cotangent_distance = e - d; + + KRVector3 plane_intersect; + float plane_intersect_distance; + + // Detect an embedded plane, caused by a sphere that is already intersecting the plane. + if(cotangent_distance <= 0 && cotangent_distance >= -radius * 2.0f) { + // Embedded plane - Sphere is already intersecting the plane. + // Use the point closest to the origin of the sphere as the intersection + plane_intersect = start - tri_normal * (cotangent_distance + radius); + plane_intersect_distance = 0.0f; + } else { + // Sphere is not intersecting the plane + // Determine the first point hit by the swept sphere on the triangle's plane + + float denom = KRVector3::Dot(tri_normal, dir); + + if(fabs(denom) < SMALL_NUM) { + return false; // dir is co-planar with the triangle; no intersection + } + + plane_intersect_distance = -(cotangent_distance / denom); + plane_intersect = start + dir * plane_intersect_distance; + } + + if(containsPoint(plane_intersect)) { + // Triangle contains point + hit_point = plane_intersect; + hit_distance = plane_intersect_distance; + return true; + } else { + // Triangle does not contain point, cast ray back to sphere from closest point on triangle edge or vertice + KRVector3 closest_point = closestPointOnTriangle(hit_point); + float reverse_hit_distance; + if(_intersectSphere(closest_point, -dir, start, radius, reverse_hit_distance)) { + // Reverse cast hit sphere + hit_distance = reverse_hit_distance; + hit_point = closest_point - dir * reverse_hit_distance; + return true; + } else { + // Reverse cast did not hit sphere + return false; + } + } +} + + +bool KRTriangle3::containsPoint(const KRVector3 &p) const +{ + // From: http://stackoverflow.com/questions/995445/determine-if-a-3d-point-is-within-a-triangle + + const float SMALL_NUM = 0.00000001f; // anything that avoids division overflow + // KRVector3 A = m_c[0], B = m_c[1], C = m_c[2]; + if (_sameSide(p, m_c[0], m_c[1], m_c[2]) && _sameSide(p, m_c[1], m_c[0], m_c[2]) && _sameSide(p, m_c[2], m_c[0], m_c[1])) { + KRVector3 vc1 = KRVector3::Cross(m_c[0] - m_c[1], m_c[0] - m_c[2]); + if(fabs(KRVector3::Dot(m_c[0] - p, vc1)) <= SMALL_NUM) { + return true; + } + } + + return false; +} + + + diff --git a/KREngine/kraken/KRTriangle3.h b/KREngine/kraken/KRTriangle3.h new file mode 100644 index 0000000..25ade3e --- /dev/null +++ b/KREngine/kraken/KRTriangle3.h @@ -0,0 +1,63 @@ +// +// KRTriangle.h +// KREngine +// +// Copyright 2012 Kearwood Gilbert. All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, are +// permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of +// conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list +// of conditions and the following disclaimer in the documentation and/or other materials +// provided with the distribution. +// +// THIS SOFTWARE IS PROVIDED BY KEARWOOD GILBERT ''AS IS'' AND ANY EXPRESS OR IMPLIED +// WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND +// FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL KEARWOOD GILBERT OR +// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON +// ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF +// ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// +// The views and conclusions contained in the software and documentation are those of the +// authors and should not be interpreted as representing official policies, either expressed +// or implied, of Kearwood Gilbert. +// + +#ifndef KRTRIANGLE3_H +#define KRTRIANGLE3_H + +#include "KRVector3.h" + +class KRTriangle3 +{ +public: + KRTriangle3(const KRTriangle3 &tri); + KRTriangle3(const KRVector3 &v1, const KRVector3 &v2, const KRVector3 &v3); + ~KRTriangle3(); + + KRVector3 calculateNormal() const; + + bool operator ==(const KRTriangle3& b) const; + bool operator !=(const KRTriangle3& b) const; + KRTriangle3& operator =(const KRTriangle3& b); + KRVector3& operator[](unsigned i); + KRVector3 operator[](unsigned i) const; + + + bool rayCast(const KRVector3 &start, const KRVector3 &dir, KRVector3 &hit_point) const; + bool sphereCast(const KRVector3 &start, const KRVector3 &dir, float radius, KRVector3 &hit_point, float &hit_distance) const; + + bool containsPoint(const KRVector3 &p) const; + KRVector3 closestPointOnTriangle(const KRVector3 &p) const; +private: + + KRVector3 m_c[3]; +}; + +#endif // KRTRIANGLE3_H From be804fc3dec708dfaf05d0499e4353c78c6c1565 Mon Sep 17 00:00:00 2001 From: Kearwood Gilbert Date: Mon, 17 Feb 2014 23:36:54 -0800 Subject: [PATCH 37/84] SphereCast bug fixes, appears to be working now. More testing needed --HG-- branch : nfb --- KREngine/kraken/KRCollider.cpp | 4 +- KREngine/kraken/KRMesh.cpp | 34 +++++++------- KREngine/kraken/KRMesh.h | 2 +- KREngine/kraken/KROctreeNode.cpp | 1 + KREngine/kraken/KRTriangle3.cpp | 81 +++++++++++++++++++++++++------- 5 files changed, 85 insertions(+), 37 deletions(-) diff --git a/KREngine/kraken/KRCollider.cpp b/KREngine/kraken/KRCollider.cpp index e6ee63f..df0f9a8 100644 --- a/KREngine/kraken/KRCollider.cpp +++ b/KREngine/kraken/KRCollider.cpp @@ -129,7 +129,7 @@ bool KRCollider::rayCast(const KRVector3 &v0, const KRVector3 &dir, KRHitInfo &h KRHitInfo hitinfo_model_space; if(hitinfo.didHit()) { KRVector3 hit_position_model_space = KRMat4::Dot(getInverseModelMatrix(), hitinfo.getPosition()); - hitinfo_model_space = KRHitInfo(KRMat4::Dot(getInverseModelMatrix(), hitinfo.getPosition()), KRVector3::Normalize(KRMat4::DotNoTranslate(getInverseModelMatrix(), hitinfo.getNormal())), (hit_position_model_space - v0_model_space).magnitude(), hitinfo.getNode()); + hitinfo_model_space = KRHitInfo(hit_position_model_space, KRVector3::Normalize(KRMat4::DotNoTranslate(getInverseModelMatrix(), hitinfo.getNormal())), (hit_position_model_space - v0_model_space).magnitude(), hitinfo.getNode()); } if(m_models[0]->rayCast(v0_model_space, dir_model_space, hitinfo_model_space)) { @@ -148,7 +148,7 @@ bool KRCollider::sphereCast(const KRVector3 &v0, const KRVector3 &v1, float radi if(layer_mask & m_layer_mask) { // Only test if layer masks have a common bit set loadModel(); if(m_models.size()) { - KRAABB sphereCastBounds = KRAABB( + KRAABB sphereCastBounds = KRAABB( // TODO - Need to cache this; perhaps encasulate within a "spherecast" class to be passed through these functions KRVector3(KRMIN(v0.x, v1.x) - radius, KRMIN(v0.y, v1.y) - radius, KRMIN(v0.z, v1.z) - radius), KRVector3(KRMAX(v0.x, v1.x) + radius, KRMAX(v0.y, v1.y) + radius, KRMAX(v0.z, v1.z) + radius) ); diff --git a/KREngine/kraken/KRMesh.cpp b/KREngine/kraken/KRMesh.cpp index 99e4bcd..4be1af1 100644 --- a/KREngine/kraken/KRMesh.cpp +++ b/KREngine/kraken/KRMesh.cpp @@ -1065,9 +1065,7 @@ bool KRMesh::rayCast(const KRVector3 &start, const KRVector3 &dir, KRHitInfo &hi bool KRMesh::sphereCast(const KRMat4 &model_to_world, const KRVector3 &v0, const KRVector3 &v1, float radius, KRHitInfo &hitinfo) const { m_pData->lock(); - KRHitInfo new_hitinfo; - KRVector3 dir = KRVector3::Normalize(v1 - v0); - + bool hit_found = false; for(int submesh_index=0; submesh_index < getSubmeshCount(); submesh_index++) { int vertex_count = getVertexCount(submesh_index); @@ -1082,7 +1080,13 @@ bool KRMesh::sphereCast(const KRMat4 &model_to_world, const KRVector3 &v0, const KRTriangle3 tri = KRTriangle3(getVertexPosition(tri_vert_index[0]), getVertexPosition(tri_vert_index[1]), getVertexPosition(tri_vert_index[2])); - if(sphereCast(model_to_world, v0, dir, radius, tri, getVertexNormal(tri_vert_index[0]), getVertexNormal(tri_vert_index[1]), getVertexNormal(tri_vert_index[2]), new_hitinfo)) hit_found = true; + if(sphereCast(model_to_world, v0, v1, radius, tri, hitinfo)) hit_found = true; + + /* + KRTriangle3 tri2 = KRTriangle3(getVertexPosition(tri_vert_index[1]), getVertexPosition(tri_vert_index[0]), getVertexPosition(tri_vert_index[2])); + + if(sphereCast(model_to_world, v0, v1, radius, tri2, new_hitinfo)) hit_found = true; + */ } break; /* @@ -1107,21 +1111,14 @@ bool KRMesh::sphereCast(const KRMat4 &model_to_world, const KRVector3 &v0, const } m_pData->unlock(); - - if(hit_found) { - if(new_hitinfo.getDistance() <= (v1 - v0).magnitude()) { - // The hit was between v1 and v2 - hitinfo = new_hitinfo; - return true; - } - } - return false; // Either no hit, or the hit was beyond v1 + return hit_found; } -bool KRMesh::sphereCast(const KRMat4 &model_to_world, const KRVector3 &start, const KRVector3 &dir, float radius, const KRTriangle3 &tri, const KRVector3 &tri_n0, const KRVector3 &tri_n1, const KRVector3 &tri_n2, KRHitInfo &hitinfo) +bool KRMesh::sphereCast(const KRMat4 &model_to_world, const KRVector3 &v0, const KRVector3 &v1, float radius, const KRTriangle3 &tri, KRHitInfo &hitinfo) { - // bool sphereCast(const KRVector3 &start, const KRVector3 &dir, float radius, KRVector3 &hit_point, float &hit_distance) const; + KRVector3 dir = KRVector3::Normalize(v1 - v0); + KRVector3 start = v0; KRVector3 new_hit_point; float new_hit_distance; @@ -1129,8 +1126,9 @@ bool KRMesh::sphereCast(const KRMat4 &model_to_world, const KRVector3 &start, co KRTriangle3 world_tri = KRTriangle3(KRMat4::Dot(model_to_world, tri[0]), KRMat4::Dot(model_to_world, tri[1]), KRMat4::Dot(model_to_world, tri[2])); if(world_tri.sphereCast(start, dir, radius, new_hit_point, new_hit_distance)) { - if(!hitinfo.didHit() || hitinfo.getDistance() > new_hit_distance) { + if((!hitinfo.didHit() || hitinfo.getDistance() > new_hit_distance) && new_hit_distance <= (v1 - v0).magnitude()) { + /* // Interpolate between the three vertex normals, performing a 3-way lerp of tri_n0, tri_n1, and tri_n2 float distance_v0 = (tri[0] - new_hit_point).magnitude(); float distance_v1 = (tri[1] - new_hit_point).magnitude(); @@ -1140,8 +1138,8 @@ bool KRMesh::sphereCast(const KRMat4 &model_to_world, const KRVector3 &start, co distance_v1 /= distance_total; distance_v2 /= distance_total; KRVector3 normal = KRVector3::Normalize(KRMat4::DotNoTranslate(model_to_world, (tri_n0 * (1.0 - distance_v0) + tri_n1 * (1.0 - distance_v1) + tri_n2 * (1.0 - distance_v2)))); - - hitinfo = KRHitInfo(new_hit_point, normal, new_hit_distance); + */ + hitinfo = KRHitInfo(new_hit_point, world_tri.calculateNormal(), new_hit_distance); return true; } } diff --git a/KREngine/kraken/KRMesh.h b/KREngine/kraken/KRMesh.h index 745ed3b..b5217a8 100644 --- a/KREngine/kraken/KRMesh.h +++ b/KREngine/kraken/KRMesh.h @@ -220,7 +220,7 @@ private: void getSubmeshes(); static bool rayCast(const KRVector3 &start, const KRVector3 &dir, const KRTriangle3 &tri, const KRVector3 &tri_n0, const KRVector3 &tri_n1, const KRVector3 &tri_n2, KRHitInfo &hitinfo); - static bool sphereCast(const KRMat4 &model_to_world, const KRVector3 &start, const KRVector3 &dir, float radius, const KRTriangle3 &tri, const KRVector3 &tri_n0, const KRVector3 &tri_n1, const KRVector3 &tri_n2, KRHitInfo &hitinfo); + static bool sphereCast(const KRMat4 &model_to_world, const KRVector3 &v0, const KRVector3 &v1, float radius, const KRTriangle3 &tri, KRHitInfo &hitinfo); 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; diff --git a/KREngine/kraken/KROctreeNode.cpp b/KREngine/kraken/KROctreeNode.cpp index 929289f..2b62c8a 100644 --- a/KREngine/kraken/KROctreeNode.cpp +++ b/KREngine/kraken/KROctreeNode.cpp @@ -263,6 +263,7 @@ bool KROctreeNode::sphereCast(const KRVector3 &v0, const KRVector3 &v1, float ra hit_found = sphereCast(v0, hitinfo.getPosition(), radius, hitinfo, layer_mask); } else { */ + KRAABB swept_bounds = KRAABB(KRVector3(KRMIN(v0.x, v1.x) - radius, KRMIN(v0.y, v1.y) - radius, KRMIN(v0.z, v1.z) - radius), KRVector3(KRMAX(v0.x, v1.x) + radius, KRMAX(v0.y, v1.y) + radius, KRMAX(v0.z, v1.z) + radius)); // FINDME, TODO - Investigate AABB - swept sphere intersections or OBB - AABB intersections: "if(getBounds().intersectsSweptSphere(v0, v1, radius)) {" if(getBounds().intersects(swept_bounds)) { diff --git a/KREngine/kraken/KRTriangle3.cpp b/KREngine/kraken/KRTriangle3.cpp index d4d4419..b140802 100644 --- a/KREngine/kraken/KRTriangle3.cpp +++ b/KREngine/kraken/KRTriangle3.cpp @@ -126,24 +126,33 @@ KRVector3 KRTriangle3::calculateNormal() const return KRVector3::Normalize(KRVector3::Cross(v1, v2)); } -bool _intersectSphere(const KRVector3 &rO, const KRVector3 &rV, const KRVector3 &sO, float sR, float &distance) +bool _intersectSphere(const KRVector3 &start, const KRVector3 &dir, const KRVector3 &sphere_center, float sphere_radius, float &distance) { + // dir must be normalized + // From: http://archive.gamedev.net/archive/reference/articles/article1026.html // TODO - Move to another class? - KRVector3 Q = sO - rO; + KRVector3 Q = sphere_center - start; float c = Q.magnitude(); - float v = KRVector3::Dot(Q, rV); - float d = sR * sR - (c * c - v * v); + float v = KRVector3::Dot(Q, dir); + float d = sphere_radius * sphere_radius - (c * c - v * v); - // If there was no intersection, return -1 + - if(d < 0.0) return false; + if(d < 0.0) { + // No intersection + return false; + } // Return the distance to the [first] intersecting point distance = v - sqrt(d); + if(distance < 0.0f) { + return false; + } return true; + } bool _sameSide(const KRVector3 &p1, const KRVector3 &p2, const KRVector3 &a, const KRVector3 &b) @@ -196,9 +205,9 @@ KRVector3 KRTriangle3::closestPointOnTriangle(const KRVector3 &p) const if(sd_Rab < sd_Rbc && sd_Rab < sd_Rca) { return Rab; } else if(sd_Rbc < sd_Rab && sd_Rbc < sd_Rca) { - return sd_Rbc; + return Rbc; } else { - return sd_Rca; + return Rca; } } @@ -216,6 +225,12 @@ bool KRTriangle3::sphereCast(const KRVector3 &start, const KRVector3 &dir, float KRVector3 plane_intersect; float plane_intersect_distance; + float denom = KRVector3::Dot(tri_normal, dir); + + if(denom > -SMALL_NUM) { + return false; // dir is co-planar with the triangle or going in the direction of the normal; no intersection + } + // Detect an embedded plane, caused by a sphere that is already intersecting the plane. if(cotangent_distance <= 0 && cotangent_distance >= -radius * 2.0f) { // Embedded plane - Sphere is already intersecting the plane. @@ -225,17 +240,15 @@ bool KRTriangle3::sphereCast(const KRVector3 &start, const KRVector3 &dir, float } else { // Sphere is not intersecting the plane // Determine the first point hit by the swept sphere on the triangle's plane - - float denom = KRVector3::Dot(tri_normal, dir); - - if(fabs(denom) < SMALL_NUM) { - return false; // dir is co-planar with the triangle; no intersection - } plane_intersect_distance = -(cotangent_distance / denom); plane_intersect = start + dir * plane_intersect_distance; } + if(plane_intersect_distance < 0.0f) { + return false; + } + if(containsPoint(plane_intersect)) { // Triangle contains point hit_point = plane_intersect; @@ -243,12 +256,12 @@ bool KRTriangle3::sphereCast(const KRVector3 &start, const KRVector3 &dir, float return true; } else { // Triangle does not contain point, cast ray back to sphere from closest point on triangle edge or vertice - KRVector3 closest_point = closestPointOnTriangle(hit_point); + KRVector3 closest_point = closestPointOnTriangle(plane_intersect); float reverse_hit_distance; if(_intersectSphere(closest_point, -dir, start, radius, reverse_hit_distance)) { // Reverse cast hit sphere hit_distance = reverse_hit_distance; - hit_point = closest_point - dir * reverse_hit_distance; + hit_point = closest_point; return true; } else { // Reverse cast did not hit sphere @@ -260,6 +273,7 @@ bool KRTriangle3::sphereCast(const KRVector3 &start, const KRVector3 &dir, float bool KRTriangle3::containsPoint(const KRVector3 &p) const { + /* // From: http://stackoverflow.com/questions/995445/determine-if-a-3d-point-is-within-a-triangle const float SMALL_NUM = 0.00000001f; // anything that avoids division overflow @@ -272,6 +286,41 @@ bool KRTriangle3::containsPoint(const KRVector3 &p) const } return false; + */ + + // From: http://blogs.msdn.com/b/rezanour/archive/2011/08/07/barycentric-coordinates-and-point-in-triangle-tests.aspx + + KRVector3 A = m_c[0]; + KRVector3 B = m_c[1]; + KRVector3 C = m_c[2]; + KRVector3 P = p; + + // Prepare our barycentric variables + KRVector3 u = B - A; + KRVector3 v = C - A; + KRVector3 w = P - A; + + KRVector3 vCrossW = KRVector3::Cross(v, w); + KRVector3 vCrossU = KRVector3::Cross(v, u); + + // Test sign of r + if (KRVector3::Dot(vCrossW, vCrossU) < 0) + return false; + + KRVector3 uCrossW = KRVector3::Cross(u, w); + KRVector3 uCrossV = KRVector3::Cross(u, v); + + // Test sign of t + if (KRVector3::Dot(uCrossW, uCrossV) < 0) + return false; + + // At this point, we know that r and t and both > 0. + // Therefore, as long as their sum is <= 1, each must be less <= 1 + float denom = uCrossV.magnitude(); + float r = vCrossW.magnitude() / denom; + float t = uCrossW.magnitude() / denom; + + return (r + t <= 1); } From 3bf5cf782b7f8d488e20ed82ab22c1b1b00aefbd Mon Sep 17 00:00:00 2001 From: Kearwood Gilbert Date: Tue, 18 Feb 2014 01:51:09 -0800 Subject: [PATCH 38/84] More sphereCast bug fixes --HG-- branch : nfb --- KREngine/kraken/KRTriangle3.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/KREngine/kraken/KRTriangle3.cpp b/KREngine/kraken/KRTriangle3.cpp index b140802..c60c764 100644 --- a/KREngine/kraken/KRTriangle3.cpp +++ b/KREngine/kraken/KRTriangle3.cpp @@ -214,7 +214,7 @@ KRVector3 KRTriangle3::closestPointOnTriangle(const KRVector3 &p) const bool KRTriangle3::sphereCast(const KRVector3 &start, const KRVector3 &dir, float radius, KRVector3 &hit_point, float &hit_distance) const { // Dir must be normalized - const float SMALL_NUM = 0.00000001f; // anything that avoids division overflow + const float SMALL_NUM = 0.001f; // anything that avoids division overflow KRVector3 tri_normal = calculateNormal(); @@ -242,7 +242,7 @@ bool KRTriangle3::sphereCast(const KRVector3 &start, const KRVector3 &dir, float // Determine the first point hit by the swept sphere on the triangle's plane plane_intersect_distance = -(cotangent_distance / denom); - plane_intersect = start + dir * plane_intersect_distance; + plane_intersect = start + dir * plane_intersect_distance - tri_normal * radius; } if(plane_intersect_distance < 0.0f) { From 22747d507d77cec2ae7a8f58b8abe97ac49510c3 Mon Sep 17 00:00:00 2001 From: Kearwood Gilbert Date: Sat, 1 Mar 2014 21:47:58 -0800 Subject: [PATCH 39/84] Added swizzled accessors to KRVector2 and KRVector3 --HG-- branch : nfb --- KREngine/kraken/KRVector2.cpp | 14 ++++++ KREngine/kraken/KRVector2.h | 6 +++ KREngine/kraken/KRVector3.cpp | 81 +++++++++++++++++++++++++++++++++++ KREngine/kraken/KRVector3.h | 20 ++++++++- 4 files changed, 120 insertions(+), 1 deletion(-) diff --git a/KREngine/kraken/KRVector2.cpp b/KREngine/kraken/KRVector2.cpp index 27f675e..6219db4 100644 --- a/KREngine/kraken/KRVector2.cpp +++ b/KREngine/kraken/KRVector2.cpp @@ -35,6 +35,20 @@ KRVector2::KRVector2(const KRVector2 &v) { y = v.y; } + +// KRVector2 swizzle getters +KRVector2 KRVector2::yx() const +{ + return KRVector2(y,x); +} + +// KRVector2 swizzle setters +void KRVector2::yx(const KRVector2 &v) +{ + y = v.x; + x = v.y; +} + KRVector2 KRVector2::Min() { return KRVector2(-std::numeric_limits::max()); } diff --git a/KREngine/kraken/KRVector2.h b/KREngine/kraken/KRVector2.h index ded1071..397833e 100644 --- a/KREngine/kraken/KRVector2.h +++ b/KREngine/kraken/KRVector2.h @@ -51,6 +51,12 @@ public: KRVector2(const KRVector2 &v); ~KRVector2(); + // KRVector2 swizzle getters + KRVector2 yx() const; + + // KRVector2 swizzle setters + void yx(const KRVector2 &v); + KRVector2& operator =(const KRVector2& b); KRVector2 operator +(const KRVector2& b) const; KRVector2 operator -(const KRVector2& b) const; diff --git a/KREngine/kraken/KRVector3.cpp b/KREngine/kraken/KRVector3.cpp index 6505716..1a6b55d 100644 --- a/KREngine/kraken/KRVector3.cpp +++ b/KREngine/kraken/KRVector3.cpp @@ -65,6 +65,87 @@ KRVector3::KRVector3(double *v) { z = (float)v[2]; } +KRVector2 KRVector3::xx() const +{ + return KRVector2(x,x); +} + +KRVector2 KRVector3::xy() const +{ + return KRVector2(x,y); +} + +KRVector2 KRVector3::xz() const +{ + return KRVector2(x,z); +} + +KRVector2 KRVector3::yx() const +{ + return KRVector2(y,x); +} + +KRVector2 KRVector3::yy() const +{ + return KRVector2(y,y); +} + +KRVector2 KRVector3::yz() const +{ + return KRVector2(y,z); +} + +KRVector2 KRVector3::zx() const +{ + return KRVector2(z,x); +} + +KRVector2 KRVector3::zy() const +{ + return KRVector2(z,y); +} + +KRVector2 KRVector3::zz() const +{ + return KRVector2(z,z); +} + +void KRVector3::xy(const KRVector2 &v) +{ + x = v.x; + y = v.y; +} + +void KRVector3::xz(const KRVector2 &v) +{ + x = v.x; + z = v.y; +} + +void KRVector3::yx(const KRVector2 &v) +{ + y = v.x; + x = v.y; +} + +void KRVector3::yz(const KRVector2 &v) +{ + y = v.x; + z = v.y; +} + +void KRVector3::zx(const KRVector2 &v) +{ + z = v.x; + x = v.y; +} + +void KRVector3::zy(const KRVector2 &v) +{ + z = v.x; + y = v.y; +} + KRVector3 KRVector3::Min() { return KRVector3(-std::numeric_limits::max()); } diff --git a/KREngine/kraken/KRVector3.h b/KREngine/kraken/KRVector3.h index dc14294..82aa60f 100644 --- a/KREngine/kraken/KRVector3.h +++ b/KREngine/kraken/KRVector3.h @@ -34,7 +34,7 @@ #include "KREngine-common.h" - +class KRVector2; class KRVector4; class KRVector3 { @@ -56,6 +56,24 @@ public: KRVector3(const KRVector4 &v); ~KRVector3(); + // KRVector2 swizzle getters + KRVector2 xx() const; + KRVector2 xy() const; + KRVector2 xz() const; + KRVector2 yx() const; + KRVector2 yy() const; + KRVector2 yz() const; + KRVector2 zx() const; + KRVector2 zy() const; + KRVector2 zz() const; + + // KRVector2 swizzle setters + void xy(const KRVector2 &v); + void xz(const KRVector2 &v); + void yx(const KRVector2 &v); + void yz(const KRVector2 &v); + void zx(const KRVector2 &v); + void zy(const KRVector2 &v); KRVector3& operator =(const KRVector3& b); KRVector3& operator =(const KRVector4& b); From 094a89a3791c92f82c9508a7d2e593fee218f8b9 Mon Sep 17 00:00:00 2001 From: Kearwood Gilbert Date: Sat, 1 Mar 2014 21:48:46 -0800 Subject: [PATCH 40/84] KTX compression pipeline fixes and sanity-check asserts --HG-- branch : nfb --- KREngine/kraken/KRTextureManager.cpp | 4 +++- KREngine/kraken/KRTextureTGA.cpp | 31 +++++++++++++++++++++------- 2 files changed, 26 insertions(+), 9 deletions(-) diff --git a/KREngine/kraken/KRTextureManager.cpp b/KREngine/kraken/KRTextureManager.cpp index 6abd66e..a250e40 100644 --- a/KREngine/kraken/KRTextureManager.cpp +++ b/KREngine/kraken/KRTextureManager.cpp @@ -171,7 +171,7 @@ KRTexture *KRTextureManager::getTexture(const std::string &name) { return pTexture; } else { // Not found - //fprintf(stderr, "ERROR: Texture not found: %s\n", szName); + // fprintf(stderr, "ERROR: Texture not found: %s\n", name.c_str()); return NULL; } } else { @@ -400,6 +400,8 @@ void KRTextureManager::compress(bool premultiply_alpha) if(compressed_texture) { textures_to_remove.push_back(texture); textures_to_add.push_back(compressed_texture); + } else { + assert(false); } } diff --git a/KREngine/kraken/KRTextureTGA.cpp b/KREngine/kraken/KRTextureTGA.cpp index 05b1352..ca75b8e 100644 --- a/KREngine/kraken/KRTextureTGA.cpp +++ b/KREngine/kraken/KRTextureTGA.cpp @@ -119,8 +119,10 @@ bool KRTextureTGA::uploadTexture(GLenum target, int lod_max_dim, int ¤t_lo *pDest++ = 0xff; pSource += 3; } + assert(pSource <= m_pData->getEnd()); //#endif GLDEBUG(glTexImage2D(target, 0, internal_format, pHeader->width, pHeader->height, 0, GL_BGRA, GL_UNSIGNED_BYTE, (GLvoid *)converted_image)); + GLDEBUG(glFinish()); free(converted_image); current_lod_max_dim = m_max_lod_max_dim; @@ -141,11 +143,14 @@ bool KRTextureTGA::uploadTexture(GLenum target, int lod_max_dim, int ¤t_lo *pDest++ = pSource[3]; pSource += 4; } + assert(pSource <= m_pData->getEnd()); GLDEBUG(glTexImage2D(target, 0, internal_format, pHeader->width, pHeader->height, 0, GL_BGRA, GL_UNSIGNED_BYTE, (GLvoid *)converted_image)); + GLDEBUG(glFinish()); free(converted_image); } else { GLDEBUG(glTexImage2D(target, 0, internal_format, pHeader->width, pHeader->height, 0, GL_BGRA, GL_UNSIGNED_BYTE, (GLvoid *)pData)); + GLDEBUG(glFinish()); } current_lod_max_dim = m_max_lod_max_dim; @@ -189,6 +194,8 @@ bool KRTextureTGA::uploadTexture(GLenum target, int lod_max_dim, int ¤t_lo } } } + assert(pSource <= m_pData->getEnd()); + assert(pDest == pEnd); } else { while(pDest < pEnd) { int count = (*pSource & 0x7f) + 1; @@ -214,8 +221,11 @@ bool KRTextureTGA::uploadTexture(GLenum target, int lod_max_dim, int ¤t_lo } } } + assert(pSource <= m_pData->getEnd()); + assert(pDest == pEnd); } GLDEBUG(glTexImage2D(target, 0, internal_format, pHeader->width, pHeader->height, 0, GL_BGRA, GL_UNSIGNED_BYTE, (GLvoid *)converted_image)); + GLDEBUG(glFinish()); free(converted_image); current_lod_max_dim = m_max_lod_max_dim; } @@ -250,7 +260,10 @@ bool KRTextureTGA::uploadTexture(GLenum target, int lod_max_dim, int ¤t_lo } } } + assert(pSource <= m_pData->getEnd()); + assert(pDest == pEnd); GLDEBUG(glTexImage2D(target, 0, internal_format, pHeader->width, pHeader->height, 0, GL_BGRA, GL_UNSIGNED_BYTE, (GLvoid *)converted_image)); + GLDEBUG(glFinish()); free(converted_image); current_lod_max_dim = m_max_lod_max_dim; } @@ -297,6 +310,12 @@ KRTexture *KRTextureTGA::compress(bool premultiply_alpha) GLDEBUG(glGetTexLevelParameteriv(GL_TEXTURE_2D, 0, GL_TEXTURE_HEIGHT, &height)); GLDEBUG(glGetTexLevelParameteriv(GL_TEXTURE_2D, 0, GL_TEXTURE_INTERNAL_FORMAT, &internal_format)); + /* + int texture_base_level = 0; + int texture_max_level = 0; + GLDEBUG(glGetTexParameteriv(GL_TEXTURE_2D, GL_TEXTURE_MIN_LOD, &texture_base_level)); + GLDEBUG(glGetTexParameteriv(GL_TEXTURE_2D, GL_TEXTURE_MAX_LOD, &texture_max_level)); + */ switch(internal_format) { case GL_COMPRESSED_RGB_S3TC_DXT1_EXT: @@ -312,8 +331,9 @@ KRTexture *KRTextureTGA::compress(bool premultiply_alpha) GLuint lod_level = 0; GLint compressed_size = 0; - GLenum err = GL_NO_ERROR; - while(err == GL_NO_ERROR) { + int lod_width = width; + while(lod_width > 1) { + GLDEBUG(glGetTexLevelParameteriv(GL_TEXTURE_2D, lod_level, GL_TEXTURE_WIDTH, &lod_width)); GLDEBUG(glGetTexLevelParameteriv(GL_TEXTURE_2D, lod_level, GL_TEXTURE_COMPRESSED_IMAGE_SIZE, &compressed_size)); KRDataBlock *new_block = new KRDataBlock(); new_block->expand(compressed_size); @@ -324,10 +344,7 @@ KRTexture *KRTextureTGA::compress(bool premultiply_alpha) lod_level++; } - if(err != GL_INVALID_VALUE) { - // err will equal GL_INVALID_VALUE when - // assert(false); // Unexpected error - } + assert(lod_width == 1); GLDEBUG(glBindTexture(GL_TEXTURE_2D, 0)); getContext().getTextureManager()->selectTexture(0, NULL); @@ -335,8 +352,6 @@ KRTexture *KRTextureTGA::compress(bool premultiply_alpha) KRTextureKTX *new_texture = new KRTextureKTX(getContext(), getName(), internal_format, base_internal_format, width, height, blocks); - KRResource *test_resource = new_texture; - m_pData->unlock(); for(auto block_itr = blocks.begin(); block_itr != blocks.end(); block_itr++) { From 748adc050f417e89a3053be6f20483a794a18913 Mon Sep 17 00:00:00 2001 From: Manjit Bedi Date: Tue, 4 Mar 2014 16:47:16 -0800 Subject: [PATCH 41/84] removed duplicate const --HG-- branch : nfb --- KREngine/kraken/KRModel.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/KREngine/kraken/KRModel.h b/KREngine/kraken/KRModel.h index 3f8df27..c688f70 100644 --- a/KREngine/kraken/KRModel.h +++ b/KREngine/kraken/KRModel.h @@ -61,7 +61,7 @@ public: virtual KRAABB getBounds(); - void setRimColor(const const KRVector3 &rim_color); + void setRimColor(const KRVector3 &rim_color); void setRimPower(float rim_power); KRVector3 getRimColor(); float getRimPower(); From 6f7359baf6bc0d88e8ec67857deda18fa88e7906 Mon Sep 17 00:00:00 2001 From: "admin8onf@admin8onfs-pro.nfbonf.nfb.ca" Date: Tue, 4 Mar 2014 23:40:31 -0800 Subject: [PATCH 42/84] increased the near clipping plane to 0.8f --HG-- branch : nfb --- KREngine/kraken/KRRenderSettings.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/KREngine/kraken/KRRenderSettings.cpp b/KREngine/kraken/KRRenderSettings.cpp index 6c9efe8..fcaa595 100644 --- a/KREngine/kraken/KRRenderSettings.cpp +++ b/KREngine/kraken/KRRenderSettings.cpp @@ -37,7 +37,7 @@ KRRenderSettings::KRRenderSettings() light_intensity = KRVector3::One(); perspective_fov = 45.0 * D2R; - perspective_nearz = 0.05f; + perspective_nearz = 0.8f; // was 0.05f perspective_farz = 1000.0f; dof_quality = 0; From e6bed1265b1a46dcf1bfdce079fde57b70c41208 Mon Sep 17 00:00:00 2001 From: Kearwood Gilbert Date: Wed, 5 Mar 2014 22:47:48 -0800 Subject: [PATCH 43/84] Fixed KRAABB::Intersect bug, which returned false negatives due to an invalid z-axis test. --HG-- branch : nfb --- KREngine/kraken/KRAABB.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/KREngine/kraken/KRAABB.cpp b/KREngine/kraken/KRAABB.cpp index 9998775..1144dd2 100644 --- a/KREngine/kraken/KRAABB.cpp +++ b/KREngine/kraken/KRAABB.cpp @@ -131,7 +131,7 @@ bool KRAABB::operator <(const KRAABB& b) const bool KRAABB::intersects(const KRAABB& b) const { // Return true if the two volumes intersect - return min.x <= b.max.x && min.y <= b.max.y && min.z <= b.max.z && max.x >= b.min.x && max.y >= b.min.y && max.z >= b.max.z; + return min.x <= b.max.x && min.y <= b.max.y && min.z <= b.max.z && max.x >= b.min.x && max.y >= b.min.y && max.z >= b.min.z; } bool KRAABB::contains(const KRAABB &b) const From 143e37e8801cf5e7c98ab79922060dcb5234f23b Mon Sep 17 00:00:00 2001 From: Kelly Date: Fri, 7 Mar 2014 13:16:45 -0800 Subject: [PATCH 44/84] Pulled back the Nearfield clip plane... last adjustment was too aggressive --HG-- branch : nfb --- KREngine/kraken/KRRenderSettings.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/KREngine/kraken/KRRenderSettings.cpp b/KREngine/kraken/KRRenderSettings.cpp index fcaa595..86bfaed 100644 --- a/KREngine/kraken/KRRenderSettings.cpp +++ b/KREngine/kraken/KRRenderSettings.cpp @@ -37,7 +37,7 @@ KRRenderSettings::KRRenderSettings() light_intensity = KRVector3::One(); perspective_fov = 45.0 * D2R; - perspective_nearz = 0.8f; // was 0.05f + perspective_nearz = 0.3f; // was 0.05f perspective_farz = 1000.0f; dof_quality = 0; From 64f1b705456ce8ea9c67381759f8b98ebc5eb768 Mon Sep 17 00:00:00 2001 From: "admin8onf@admin8onfs-pro.nfbonf.nfb.ca" Date: Sat, 8 Mar 2014 23:14:11 -0800 Subject: [PATCH 45/84] Changes to admin8onf profiles --HG-- branch : nfb --- .../xcschemes/Kraken - ios.xcscheme | 59 ------------------- .../xcschemes/Kraken - osx.xcscheme | 59 ------------------- .../Kraken Standard Assets - OSX.xcscheme | 59 ------------------- .../Kraken Standard Assets - iOS.xcscheme | 59 ------------------- .../xcschemes/xcschememanagement.plist | 31 +--------- 5 files changed, 1 insertion(+), 266 deletions(-) delete mode 100644 KREngine/Kraken.xcodeproj/xcuserdata/admin8onf.xcuserdatad/xcschemes/Kraken - ios.xcscheme delete mode 100644 KREngine/Kraken.xcodeproj/xcuserdata/admin8onf.xcuserdatad/xcschemes/Kraken - osx.xcscheme delete mode 100644 KREngine/Kraken.xcodeproj/xcuserdata/admin8onf.xcuserdatad/xcschemes/Kraken Standard Assets - OSX.xcscheme delete mode 100644 KREngine/Kraken.xcodeproj/xcuserdata/admin8onf.xcuserdatad/xcschemes/Kraken Standard Assets - iOS.xcscheme diff --git a/KREngine/Kraken.xcodeproj/xcuserdata/admin8onf.xcuserdatad/xcschemes/Kraken - ios.xcscheme b/KREngine/Kraken.xcodeproj/xcuserdata/admin8onf.xcuserdatad/xcschemes/Kraken - ios.xcscheme deleted file mode 100644 index 832c872..0000000 --- a/KREngine/Kraken.xcodeproj/xcuserdata/admin8onf.xcuserdatad/xcschemes/Kraken - ios.xcscheme +++ /dev/null @@ -1,59 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/KREngine/Kraken.xcodeproj/xcuserdata/admin8onf.xcuserdatad/xcschemes/Kraken - osx.xcscheme b/KREngine/Kraken.xcodeproj/xcuserdata/admin8onf.xcuserdatad/xcschemes/Kraken - osx.xcscheme deleted file mode 100644 index 03db38e..0000000 --- a/KREngine/Kraken.xcodeproj/xcuserdata/admin8onf.xcuserdatad/xcschemes/Kraken - osx.xcscheme +++ /dev/null @@ -1,59 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/KREngine/Kraken.xcodeproj/xcuserdata/admin8onf.xcuserdatad/xcschemes/Kraken Standard Assets - OSX.xcscheme b/KREngine/Kraken.xcodeproj/xcuserdata/admin8onf.xcuserdatad/xcschemes/Kraken Standard Assets - OSX.xcscheme deleted file mode 100644 index a5f6a90..0000000 --- a/KREngine/Kraken.xcodeproj/xcuserdata/admin8onf.xcuserdatad/xcschemes/Kraken Standard Assets - OSX.xcscheme +++ /dev/null @@ -1,59 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/KREngine/Kraken.xcodeproj/xcuserdata/admin8onf.xcuserdatad/xcschemes/Kraken Standard Assets - iOS.xcscheme b/KREngine/Kraken.xcodeproj/xcuserdata/admin8onf.xcuserdatad/xcschemes/Kraken Standard Assets - iOS.xcscheme deleted file mode 100644 index b95caa0..0000000 --- a/KREngine/Kraken.xcodeproj/xcuserdata/admin8onf.xcuserdatad/xcschemes/Kraken Standard Assets - iOS.xcscheme +++ /dev/null @@ -1,59 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/KREngine/Kraken.xcodeproj/xcuserdata/admin8onf.xcuserdatad/xcschemes/xcschememanagement.plist b/KREngine/Kraken.xcodeproj/xcuserdata/admin8onf.xcuserdatad/xcschemes/xcschememanagement.plist index 287911f..cc8ace3 100644 --- a/KREngine/Kraken.xcodeproj/xcuserdata/admin8onf.xcuserdatad/xcschemes/xcschememanagement.plist +++ b/KREngine/Kraken.xcodeproj/xcuserdata/admin8onf.xcuserdatad/xcschemes/xcschememanagement.plist @@ -3,36 +3,7 @@ SchemeUserState - - Kraken - ios.xcscheme - - isShown - - orderHint - 0 - - Kraken - osx.xcscheme - - isShown - - orderHint - 1 - - Kraken Standard Assets - OSX.xcscheme - - isShown - - orderHint - 3 - - Kraken Standard Assets - iOS.xcscheme - - isShown - - orderHint - 2 - - + SuppressBuildableAutocreation E491016013C99B9E0098455B From 8a1164c44f7216ce4e61502f3c0418cb4778f81b Mon Sep 17 00:00:00 2001 From: Kearwood Gilbert Date: Mon, 10 Mar 2014 22:32:49 -0700 Subject: [PATCH 46/84] - Implemented methods for determining amount of scene that has streamed in. (KRScene::getStreamLevel, KRNode::getStreamLevel, KRModel::getStreamLevel, KRMaterial::getStreamLevel, and KRTexture::getStreamLevel - Implemented connection between LOD groups and texture streaming, which delays the switch to a new LOD group until the required textures have completed streaming in. - Corrected bug in KRMaterial that resulted in reflection cube texture names being formatted incorrectly in the mtl file - Scene graph now requires that lod_group nodes only be contained within lod_set nodes. - Scene graph group nodes that do not have a LOD minimum or maximum distance are now stored as "node" rather than as "lod_group" nodes. - IMPORTANT! Scenes exported with this version will not be backwards compatible with earlier versions due to the requirement of lod_set nodes. --HG-- branch : nfb extra : rebase_source : 1ff640b85338a794841ebbb2bf0087306ff59143 --- KREngine/kraken/KRCamera.cpp | 2 +- KREngine/kraken/KRCamera.h | 2 +- KREngine/kraken/KRContext.h | 1 - KREngine/kraken/KREngine-common.h | 6 +++ KREngine/kraken/KRLODGroup.cpp | 17 +------ KREngine/kraken/KRLODGroup.h | 5 +- KREngine/kraken/KRLODSet.cpp | 82 +++++++++++++++++++++++++++++- KREngine/kraken/KRLODSet.h | 14 +++++ KREngine/kraken/KRMaterial.cpp | 44 ++++++++++++++-- KREngine/kraken/KRMaterial.h | 4 ++ KREngine/kraken/KRMesh.cpp | 60 ++++++++++++++-------- KREngine/kraken/KRMesh.h | 3 ++ KREngine/kraken/KRModel.cpp | 16 +++++- KREngine/kraken/KRModel.h | 2 + KREngine/kraken/KRNode.cpp | 39 ++++++++++---- KREngine/kraken/KRNode.h | 12 +++-- KREngine/kraken/KRResource+fbx.cpp | 20 +++----- KREngine/kraken/KRScene.cpp | 27 +++++++++- KREngine/kraken/KRScene.h | 4 ++ KREngine/kraken/KRTexture.cpp | 17 ++++++- KREngine/kraken/KRTexture.h | 3 +- KREngine/kraken/KRVector3.cpp | 10 ++-- KREngine/kraken/KRVector3.h | 2 +- 23 files changed, 305 insertions(+), 87 deletions(-) diff --git a/KREngine/kraken/KRCamera.cpp b/KREngine/kraken/KRCamera.cpp index 03c0845..7a4abf1 100644 --- a/KREngine/kraken/KRCamera.cpp +++ b/KREngine/kraken/KRCamera.cpp @@ -1102,7 +1102,7 @@ std::string KRCamera::getDebugText() } -const KRViewport &KRCamera::getViewport() +const KRViewport &KRCamera::getViewport() const { return m_viewport; } diff --git a/KREngine/kraken/KRCamera.h b/KREngine/kraken/KRCamera.h index 560dd4e..4978c00 100644 --- a/KREngine/kraken/KRCamera.h +++ b/KREngine/kraken/KRCamera.h @@ -59,7 +59,7 @@ public: KRRenderSettings settings; - const KRViewport &getViewport(); + const KRViewport &getViewport() const; virtual std::string getElementName(); diff --git a/KREngine/kraken/KRContext.h b/KREngine/kraken/KRContext.h index 4f91d40..06402e2 100644 --- a/KREngine/kraken/KRContext.h +++ b/KREngine/kraken/KRContext.h @@ -88,7 +88,6 @@ public: static void SetLogCallback(log_callback *log_callback, void *user_data); static void Log(log_level level, const std::string &message_format, ...); - private: KRBundleManager *m_pBundleManager; KRSceneManager *m_pSceneManager; diff --git a/KREngine/kraken/KREngine-common.h b/KREngine/kraken/KREngine-common.h index 5dcf36b..c02b537 100644 --- a/KREngine/kraken/KREngine-common.h +++ b/KREngine/kraken/KREngine-common.h @@ -183,6 +183,12 @@ fprintf(stderr, "Error at line number %d, in file %s. Returned %d for call %s\n" #define KRCLAMP(x, min, max) (KRMAX(KRMIN(x, max), min)) #define KRALIGN(x) ((x + 3) & ~0x03) +typedef enum { + STREAM_LEVEL_OUT, + STREAM_LEVEL_IN_LQ, + STREAM_LEVEL_IN_HQ +} kraken_stream_level; + #include "KRVector4.h" #include "KRVector3.h" #include "KRVector2.h" diff --git a/KREngine/kraken/KRLODGroup.cpp b/KREngine/kraken/KRLODGroup.cpp index 9df0a4f..907427a 100644 --- a/KREngine/kraken/KRLODGroup.cpp +++ b/KREngine/kraken/KRLODGroup.cpp @@ -7,6 +7,7 @@ // #include "KRLODGroup.h" +#include "KRLODSet.h" #include "KRContext.h" KRLODGroup::KRLODGroup(KRScene &scene, std::string name) : KRNode(scene, name) @@ -128,22 +129,6 @@ bool KRLODGroup::getLODVisibility(const KRViewport &viewport) } } -void KRLODGroup::updateLODVisibility(const KRViewport &viewport) -{ - bool new_visibility = getLODVisibility(viewport); - if(!new_visibility) { - hideLOD(); - } else { - if(!m_lod_visible) { - getScene().notify_sceneGraphCreate(this); - m_lod_visible = true; - } - for(std::set::iterator itr=m_childNodes.begin(); itr != m_childNodes.end(); ++itr) { - (*itr)->updateLODVisibility(viewport); - } - } -} - float KRLODGroup::getMinDistance() { return m_min_distance; diff --git a/KREngine/kraken/KRLODGroup.h b/KREngine/kraken/KRLODGroup.h index c394fb4..e86a9c8 100644 --- a/KREngine/kraken/KRLODGroup.h +++ b/KREngine/kraken/KRLODGroup.h @@ -20,8 +20,6 @@ public: virtual tinyxml2::XMLElement *saveXML( tinyxml2::XMLNode *parent); virtual void loadXML(tinyxml2::XMLElement *e); - virtual void updateLODVisibility(const KRViewport &viewport); - float getMinDistance(); float getMaxDistance(); void setMinDistance(float min_distance); @@ -32,8 +30,9 @@ public: void setUseWorldUnits(bool use_world_units); bool getUseWorldUnits() const; -private: bool getLODVisibility(const KRViewport &viewport); + +private: float m_min_distance; float m_max_distance; KRAABB m_reference; // Point of reference, used for distance calculation. Usually set to the bounding box center diff --git a/KREngine/kraken/KRLODSet.cpp b/KREngine/kraken/KRLODSet.cpp index 93fe90d..5b3a10b 100644 --- a/KREngine/kraken/KRLODSet.cpp +++ b/KREngine/kraken/KRLODSet.cpp @@ -7,11 +7,12 @@ // #include "KRLODSet.h" +#include "KRLODGroup.h" #include "KRContext.h" KRLODSet::KRLODSet(KRScene &scene, std::string name) : KRNode(scene, name) { - + m_activeLODGroup = NULL; } KRLODSet::~KRLODSet() @@ -33,3 +34,82 @@ void KRLODSet::loadXML(tinyxml2::XMLElement *e) { KRNode::loadXML(e); } + + +void KRLODSet::updateLODVisibility(const KRViewport &viewport) +{ + if(m_lod_visible) { + KRLODGroup *new_active_lod_group = NULL; + + // Upgrade and downgrade LOD groups as needed + for(std::set::iterator itr=m_childNodes.begin(); itr != m_childNodes.end(); ++itr) { + KRLODGroup *lod_group = dynamic_cast(*itr); + assert(lod_group != NULL); + if(lod_group->getLODVisibility(viewport)) { + new_active_lod_group = lod_group; + } + } + + if(new_active_lod_group == NULL) { + m_activeLODGroup = NULL; + } else if(m_activeLODGroup == NULL) { + m_activeLODGroup = new_active_lod_group; + } else if(new_active_lod_group != m_activeLODGroup) { + if(new_active_lod_group->getStreamLevel(true) >= kraken_stream_level::STREAM_LEVEL_IN_LQ) { + fprintf(stderr, "LOD %s -> %s\n", m_activeLODGroup->getName().c_str(), new_active_lod_group->getName().c_str()); + m_activeLODGroup = new_active_lod_group; + } else { + fprintf(stderr, "LOD %s -> %s - waiting for streaming...\n", m_activeLODGroup->getName().c_str(), new_active_lod_group->getName().c_str()); + } + } + + for(std::set::iterator itr=m_childNodes.begin(); itr != m_childNodes.end(); ++itr) { + KRNode *child = *itr; + if(child == m_activeLODGroup) { + child->showLOD(); + } else { + child->hideLOD(); + } + } + + KRNode::updateLODVisibility(viewport); + } +} + +KRLODGroup *KRLODSet::getActiveLODGroup() const +{ + return m_activeLODGroup; +} + +void KRLODSet::childDeleted(KRNode *child_node) +{ + KRNode::childDeleted(child_node); + if(m_activeLODGroup == child_node) { + m_activeLODGroup = NULL; + } +} + +void KRLODSet::hideLOD() +{ + KRNode::hideLOD(); + m_activeLODGroup = NULL; // Ensure that the streamer will wait for the group to load in next time +} + +void KRLODSet::showLOD() +{ + // Don't automatically recurse into our children, as only one of those will be activated, by updateLODVisibility + if(!m_lod_visible) { + getScene().notify_sceneGraphCreate(this); + m_lod_visible = true; + } +} + +kraken_stream_level KRLODSet::getStreamLevel(bool prime) +{ + if(m_activeLODGroup) { + return m_activeLODGroup->getStreamLevel(prime); + } else { + return kraken_stream_level::STREAM_LEVEL_IN_HQ; + } +} + diff --git a/KREngine/kraken/KRLODSet.h b/KREngine/kraken/KRLODSet.h index cbb5337..a66544f 100644 --- a/KREngine/kraken/KRLODSet.h +++ b/KREngine/kraken/KRLODSet.h @@ -12,6 +12,8 @@ #include "KRResource.h" #include "KRNode.h" +class KRLODGroup; + class KRLODSet : public KRNode { public: KRLODSet(KRScene &scene, std::string name); @@ -20,6 +22,18 @@ public: virtual tinyxml2::XMLElement *saveXML( tinyxml2::XMLNode *parent); virtual void loadXML(tinyxml2::XMLElement *e); + virtual void updateLODVisibility(const KRViewport &viewport); + + KRLODGroup *getActiveLODGroup() const; + + virtual void showLOD(); + virtual void hideLOD(); + virtual void childDeleted(KRNode *child_node); + + virtual kraken_stream_level getStreamLevel(bool prime = true); + +private: + KRLODGroup *m_activeLODGroup; }; diff --git a/KREngine/kraken/KRMaterial.cpp b/KREngine/kraken/KRMaterial.cpp index 141f648..bbe4730 100644 --- a/KREngine/kraken/KRMaterial.cpp +++ b/KREngine/kraken/KRMaterial.cpp @@ -118,7 +118,7 @@ bool KRMaterial::save(KRDataBlock &data) { stream << "\n# map_Reflection filename.pvr -s 1.0 1.0 -o 0.0 0.0"; } if(m_reflectionCube.size()) { - stream << "map_ReflectionCube " << m_reflectionCube << ".pvr"; + stream << "\nmap_ReflectionCube " << m_reflectionCube << ".pvr"; } else { stream << "\n# map_ReflectionCube cubemapname"; } @@ -217,9 +217,41 @@ bool KRMaterial::isTransparent() { return m_tr < 1.0 || m_alpha_mode == KRMATERIAL_ALPHA_MODE_BLENDONESIDE || m_alpha_mode == KRMATERIAL_ALPHA_MODE_BLENDTWOSIDE; } -bool KRMaterial::bind(KRCamera *pCamera, std::vector &point_lights, std::vector &directional_lights, std::vector&spot_lights, const std::vector &bones, const std::vector &bind_poses, const KRViewport &viewport, const KRMat4 &matModel, KRTexture *pLightMap, KRNode::RenderPass renderPass, const KRVector3 &rim_color, float rim_power) { - bool bLightMap = pLightMap && pCamera->settings.bEnableLightMap; +kraken_stream_level KRMaterial::getStreamLevel(bool prime) +{ + kraken_stream_level stream_level = kraken_stream_level::STREAM_LEVEL_IN_HQ; + getTextures(); + + if(m_pAmbientMap) { + stream_level = KRMIN(stream_level, m_pNormalMap->getStreamLevel(prime)); + } + + if(m_pDiffuseMap) { + stream_level = KRMIN(stream_level, m_pDiffuseMap->getStreamLevel(prime)); + } + + if(m_pNormalMap) { + stream_level = KRMIN(stream_level, m_pNormalMap->getStreamLevel(prime)); + } + + if(m_pSpecularMap) { + stream_level = KRMIN(stream_level, m_pSpecularMap->getStreamLevel(prime)); + } + + if(m_pReflectionMap) { + stream_level = KRMIN(stream_level, m_pReflectionMap->getStreamLevel(prime)); + } + + if(m_pReflectionCube) { + stream_level = KRMIN(stream_level, m_pReflectionCube->getStreamLevel(prime)); + } + + return stream_level; +} + +void KRMaterial::getTextures() +{ if(!m_pAmbientMap && m_ambientMap.size()) { m_pAmbientMap = getContext().getTextureManager()->getTexture(m_ambientMap); } @@ -238,6 +270,12 @@ bool KRMaterial::bind(KRCamera *pCamera, std::vector &point_ligh if(!m_pReflectionCube && m_reflectionCube.size()) { m_pReflectionCube = getContext().getTextureManager()->getTextureCube(m_reflectionCube.c_str()); } +} + +bool KRMaterial::bind(KRCamera *pCamera, std::vector &point_lights, std::vector &directional_lights, std::vector&spot_lights, const std::vector &bones, const std::vector &bind_poses, const KRViewport &viewport, const KRMat4 &matModel, KRTexture *pLightMap, KRNode::RenderPass renderPass, const KRVector3 &rim_color, float rim_power) { + bool bLightMap = pLightMap && pCamera->settings.bEnableLightMap; + + getTextures(); KRVector2 default_scale = KRVector2::One(); KRVector2 default_offset = KRVector2::Zero(); diff --git a/KREngine/kraken/KRMaterial.h b/KREngine/kraken/KRMaterial.h index ef6d9ee..503a045 100644 --- a/KREngine/kraken/KRMaterial.h +++ b/KREngine/kraken/KRMaterial.h @@ -88,6 +88,8 @@ public: bool needsVertexTangents(); + kraken_stream_level getStreamLevel(bool prime = true); + private: std::string m_name; @@ -129,6 +131,8 @@ private: GLfloat m_ns; // Shininess alpha_mode_type m_alpha_mode; + + void getTextures(); }; #endif diff --git a/KREngine/kraken/KRMesh.cpp b/KREngine/kraken/KRMesh.cpp index 4be1af1..36acf44 100644 --- a/KREngine/kraken/KRMesh.cpp +++ b/KREngine/kraken/KRMesh.cpp @@ -161,33 +161,51 @@ void KRMesh::loadPack(KRDataBlock *data) { updateAttributeOffsets(); } +void KRMesh::getMaterials() +{ + if(m_materials.size() == 0) { + + for(std::vector::iterator itr = m_submeshes.begin(); itr != m_submeshes.end(); itr++) { + const char *szMaterialName = (*itr)->szMaterialName; + KRMaterial *pMaterial = getContext().getMaterialManager()->getMaterial(szMaterialName); + m_materials.push_back(pMaterial); + if(pMaterial) { + m_uniqueMaterials.insert(pMaterial); + } else { + KRContext::Log(KRContext::LOG_LEVEL_WARNING, "Missing material: %s", szMaterialName); + } + } + + m_hasTransparency = false; + for(std::set::iterator mat_itr = m_uniqueMaterials.begin(); mat_itr != m_uniqueMaterials.end(); mat_itr++) { + if((*mat_itr)->isTransparent()) { + m_hasTransparency = true; + break; + } + } + } +} + +kraken_stream_level KRMesh::getStreamLevel(bool prime) +{ + kraken_stream_level stream_level = kraken_stream_level::STREAM_LEVEL_IN_HQ; + getSubmeshes(); + getMaterials(); + + for(std::set::iterator mat_itr = m_uniqueMaterials.begin(); mat_itr != m_uniqueMaterials.end(); mat_itr++) { + stream_level = KRMIN(stream_level, (*mat_itr)->getStreamLevel(prime)); + } + + return stream_level; +} + void KRMesh::render(const std::string &object_name, KRCamera *pCamera, std::vector &point_lights, std::vector &directional_lights, std::vector&spot_lights, const KRViewport &viewport, const KRMat4 &matModel, KRTexture *pLightMap, KRNode::RenderPass renderPass, const std::vector &bones, const KRVector3 &rim_color, float rim_power) { //fprintf(stderr, "Rendering model: %s\n", m_name.c_str()); if(renderPass != KRNode::RENDER_PASS_ADDITIVE_PARTICLES && renderPass != KRNode::RENDER_PASS_PARTICLE_OCCLUSION && renderPass != KRNode::RENDER_PASS_VOLUMETRIC_EFFECTS_ADDITIVE) { getSubmeshes(); - if(m_materials.size() == 0) { - - for(std::vector::iterator itr = m_submeshes.begin(); itr != m_submeshes.end(); itr++) { - const char *szMaterialName = (*itr)->szMaterialName; - KRMaterial *pMaterial = getContext().getMaterialManager()->getMaterial(szMaterialName); - m_materials.push_back(pMaterial); - if(pMaterial) { - m_uniqueMaterials.insert(pMaterial); - } else { - KRContext::Log(KRContext::LOG_LEVEL_WARNING, "Missing material: %s", szMaterialName); - } - } - - m_hasTransparency = false; - for(std::set::iterator mat_itr = m_uniqueMaterials.begin(); mat_itr != m_uniqueMaterials.end(); mat_itr++) { - if((*mat_itr)->isTransparent()) { - m_hasTransparency = true; - break; - } - } - } + getMaterials(); int cSubmeshes = m_submeshes.size(); if(renderPass == KRNode::RENDER_PASS_SHADOWMAP) { diff --git a/KREngine/kraken/KRMesh.h b/KREngine/kraken/KRMesh.h index b5217a8..891ddeb 100644 --- a/KREngine/kraken/KRMesh.h +++ b/KREngine/kraken/KRMesh.h @@ -67,6 +67,8 @@ public: KRMesh(KRContext &context, std::string name); virtual ~KRMesh(); + kraken_stream_level getStreamLevel(bool prime = true); + bool hasTransparency(); typedef enum { @@ -218,6 +220,7 @@ private: KRDataBlock *m_pIndexBaseData; void getSubmeshes(); + void getMaterials(); static bool rayCast(const KRVector3 &start, const KRVector3 &dir, const KRTriangle3 &tri, const KRVector3 &tri_n0, const KRVector3 &tri_n1, const KRVector3 &tri_n2, KRHitInfo &hitinfo); static bool sphereCast(const KRMat4 &model_to_world, const KRVector3 &v0, const KRVector3 &v1, float radius, const KRTriangle3 &tri, KRHitInfo &hitinfo); diff --git a/KREngine/kraken/KRModel.cpp b/KREngine/kraken/KRModel.cpp index f337aba..aa931b1 100644 --- a/KREngine/kraken/KRModel.cpp +++ b/KREngine/kraken/KRModel.cpp @@ -80,7 +80,7 @@ tinyxml2::XMLElement *KRModel::saveXML( tinyxml2::XMLNode *parent) e->SetAttribute("lod_min_coverage", m_min_lod_coverage); e->SetAttribute("receives_shadow", m_receivesShadow ? "true" : "false"); e->SetAttribute("faces_camera", m_faces_camera ? "true" : "false"); - m_rim_color.setXMLAttribute("rim_color", e); + m_rim_color.setXMLAttribute("rim_color", e, KRVector3::Zero()); e->SetAttribute("rim_power", m_rim_power); return e; } @@ -199,6 +199,20 @@ void KRModel::render(KRCamera *pCamera, std::vector &point_light } } + +kraken_stream_level KRModel::getStreamLevel(bool prime) +{ + kraken_stream_level stream_level = KRNode::getStreamLevel(prime); + + loadModel(); + + for(auto itr = m_models.begin(); itr != m_models.end(); itr++) { + stream_level = KRMIN(stream_level, (*itr)->getStreamLevel(prime)); + } + + return stream_level; +} + KRAABB KRModel::getBounds() { loadModel(); if(m_models.size() > 0) { diff --git a/KREngine/kraken/KRModel.h b/KREngine/kraken/KRModel.h index c688f70..a048071 100644 --- a/KREngine/kraken/KRModel.h +++ b/KREngine/kraken/KRModel.h @@ -69,6 +69,8 @@ public: void setLightMap(const std::string &name); std::string getLightMap(); + virtual kraken_stream_level getStreamLevel(bool prime = true); + private: std::vector m_models; unordered_map > m_bones; // Outer std::map connects model to set of bones diff --git a/KREngine/kraken/KRNode.cpp b/KREngine/kraken/KRNode.cpp index ebbbeb2..1cd4eda 100644 --- a/KREngine/kraken/KRNode.cpp +++ b/KREngine/kraken/KRNode.cpp @@ -10,6 +10,7 @@ #include "KRNode.h" #include "KRLODGroup.h" +#include "KRLODSet.h" #include "KRPointLight.h" #include "KRSpotLight.h" #include "KRDirectionalLight.h" @@ -126,17 +127,17 @@ tinyxml2::XMLElement *KRNode::saveXML(tinyxml2::XMLNode *parent) { tinyxml2::XMLElement *e = doc->NewElement(getElementName().c_str()); tinyxml2::XMLNode *n = parent->InsertEndChild(e); e->SetAttribute("name", m_name.c_str()); - m_localTranslation.setXMLAttribute("translate", e); - m_localScale.setXMLAttribute("scale", e); - (m_localRotation * (180.0f / M_PI)).setXMLAttribute("rotate", e); + m_localTranslation.setXMLAttribute("translate", e, KRVector3::Zero()); + m_localScale.setXMLAttribute("scale", e, KRVector3::One()); + (m_localRotation * (180.0f / M_PI)).setXMLAttribute("rotate", e, KRVector3::Zero()); - m_rotationOffset.setXMLAttribute("rotate_offset", e); - m_scalingOffset.setXMLAttribute("scale_offset", e); - m_rotationPivot.setXMLAttribute("rotate_pivot", e); - m_scalingPivot.setXMLAttribute("scale_pivot", e); - (m_preRotation * (180.0f / M_PI)).setXMLAttribute("pre_rotate", e); - (m_postRotation * (180.0f / M_PI)).setXMLAttribute("post_rotate", e); + m_rotationOffset.setXMLAttribute("rotate_offset", e, KRVector3::Zero()); + m_scalingOffset.setXMLAttribute("scale_offset", e, KRVector3::Zero()); + m_rotationPivot.setXMLAttribute("rotate_pivot", e, KRVector3::Zero()); + m_scalingPivot.setXMLAttribute("scale_pivot", e, KRVector3::Zero()); + (m_preRotation * (180.0f / M_PI)).setXMLAttribute("pre_rotate", e, KRVector3::Zero()); + (m_postRotation * (180.0f / M_PI)).setXMLAttribute("post_rotate", e, KRVector3::Zero()); for(std::set::iterator itr=m_childNodes.begin(); itr != m_childNodes.end(); ++itr) { KRNode *child = (*itr); @@ -395,6 +396,8 @@ KRNode *KRNode::LoadXML(KRScene &scene, tinyxml2::XMLElement *e) { const char *szName = e->Attribute("name"); if(strcmp(szElementName, "node") == 0) { new_node = new KRNode(scene, szName); + } if(strcmp(szElementName, "lod_set") == 0) { + new_node = new KRLODSet(scene, szName); } if(strcmp(szElementName, "lod_group") == 0) { new_node = new KRLODGroup(scene, szName); } else if(strcmp(szElementName, "point_light") == 0) { @@ -879,8 +882,11 @@ void KRNode::addToOctreeNode(KROctreeNode *octree_node) void KRNode::updateLODVisibility(const KRViewport &viewport) { - // If we aren't an LOD group node, then we just add ourselves and all our children to the octree - showLOD(); + if(m_lod_visible) { + for(std::set::iterator itr=m_childNodes.begin(); itr != m_childNodes.end(); ++itr) { + (*itr)->updateLODVisibility(viewport); + } + } } void KRNode::hideLOD() @@ -932,3 +938,14 @@ std::set &KRNode::getBehaviors() { return m_behaviors; } + +kraken_stream_level KRNode::getStreamLevel(bool prime) +{ + kraken_stream_level stream_level = kraken_stream_level::STREAM_LEVEL_IN_HQ; + + for(std::set::iterator itr=m_childNodes.begin(); itr != m_childNodes.end(); ++itr) { + stream_level = KRMIN(stream_level, (*itr)->getStreamLevel(prime)); + } + + return stream_level; +} diff --git a/KREngine/kraken/KRNode.h b/KREngine/kraken/KRNode.h index eb074fa..e4bb849 100644 --- a/KREngine/kraken/KRNode.h +++ b/KREngine/kraken/KRNode.h @@ -165,6 +165,12 @@ public: void setAnimationEnabled(node_attribute_type attrib, bool enable); bool getAnimationEnabled(node_attribute_type attrib) const; + + virtual kraken_stream_level getStreamLevel(bool prime = true); + + virtual void hideLOD(); + virtual void showLOD(); + protected: KRVector3 m_localTranslation; KRVector3 m_localScale; @@ -189,10 +195,6 @@ protected: KRVector3 m_initialPostRotation; bool m_lod_visible; - void hideLOD(); - void showLOD(); - float m_lod_min_coverage; - float m_lod_max_coverage; KRNode *m_parentNode; std::set m_childNodes; @@ -240,7 +242,7 @@ public: } void removeFromOctreeNodes(); void addToOctreeNode(KROctreeNode *octree_node); - void childDeleted(KRNode *child_node); + virtual void childDeleted(KRNode *child_node); template T *find() { diff --git a/KREngine/kraken/KRResource+fbx.cpp b/KREngine/kraken/KRResource+fbx.cpp index 697da3e..0c87e14 100644 --- a/KREngine/kraken/KRResource+fbx.cpp +++ b/KREngine/kraken/KRResource+fbx.cpp @@ -28,6 +28,7 @@ #include "KRBundle.h" #include "KRModel.h" #include "KRLODGroup.h" +#include "KRLODSet.h" #include "KRCollider.h" #ifdef IOS_REF @@ -947,6 +948,9 @@ void LoadNode(FbxScene* pFbxScene, KRNode *parent_node, FbxGeometryConverter *pG group_max_distance = fbx_lod_group->MinDistance.Get(); } + KRLODSet *lod_set = new KRLODSet(parent_node->getScene(), name); + parent_node->addChild(lod_set); + KRAABB reference_bounds; // Create a lod_group node for each fbx child node int child_count = pNode->GetChildCount(); @@ -1006,7 +1010,7 @@ void LoadNode(FbxScene* pFbxScene, KRNode *parent_node, FbxGeometryConverter *pG new_node->setPostRotation(node_post_rotation); new_node->setUseWorldUnits(use_world_space_units); - parent_node->addChild(new_node); + lod_set->addChild(new_node); LoadNode(pFbxScene, new_node, pGeometryConverter, pNode->GetChild(i)); @@ -1049,19 +1053,7 @@ void LoadNode(FbxScene* pFbxScene, KRNode *parent_node, FbxGeometryConverter *pG if(pNode->GetChildCount() > 0) { // Create an empty node, for inheritence of transforms std::string name = GetFbxObjectName(pNode); - - - /* - if(min_distance == 0.0f && max_distance == 0.0f) { - // Regular node for grouping children together under one transform - new_node = new KRNode(parent_node->getScene(), name); - } else { - */ - // LOD Enabled group node - KRLODGroup *lod_group = new KRLODGroup(parent_node->getScene(), name); - lod_group->setMinDistance(0.0f); - lod_group->setMaxDistance(0.0f); - new_node = lod_group; + new_node = new KRNode(parent_node->getScene(), name); } } } diff --git a/KREngine/kraken/KRScene.cpp b/KREngine/kraken/KRScene.cpp index 1f07bc7..816a4ca 100644 --- a/KREngine/kraken/KRScene.cpp +++ b/KREngine/kraken/KRScene.cpp @@ -38,7 +38,6 @@ #include "KRScene.h" #include "KRNode.h" -#include "KRLODGroup.h" #include "KRStockGeometry.h" #include "KRDirectionalLight.h" #include "KRSpotLight.h" @@ -49,7 +48,7 @@ const long KRENGINE_OCCLUSION_TEST_EXPIRY = 10; KRScene::KRScene(KRContext &context, std::string name) : KRResource(context, name) { m_pFirstLight = NULL; - m_pRootNode = new KRLODGroup(*this, "scene_root"); + m_pRootNode = new KRNode(*this, "scene_root"); notify_sceneGraphCreate(m_pRootNode); m_skyBoxName = name + "_skybox"; @@ -98,6 +97,11 @@ std::set &KRScene::getLocators() return m_locatorNodes; } +std::set &KRScene::getLights() +{ + return m_lights; +} + void KRScene::render(KRCamera *pCamera, unordered_map &visibleBounds, const KRViewport &viewport, KRNode::RenderPass renderPass, bool new_frame) { if(new_frame) { // Expire cached occlusion test results. @@ -469,6 +473,10 @@ void KRScene::notify_sceneGraphDelete(KRNode *pNode) if(locator) { m_locatorNodes.erase(locator); } + KRLight *light = dynamic_cast(pNode); + if(light) { + m_lights.erase(light); + } m_modifiedNodes.erase(pNode); if(!m_newNodes.erase(pNode)) { m_nodeTree.remove(pNode); @@ -477,6 +485,7 @@ void KRScene::notify_sceneGraphDelete(KRNode *pNode) void KRScene::updateOctree(const KRViewport &viewport) { + m_pRootNode->showLOD(); m_pRootNode->updateLODVisibility(viewport); std::set newNodes = std::move(m_newNodes); @@ -502,6 +511,10 @@ void KRScene::updateOctree(const KRViewport &viewport) if(locatorNode) { m_locatorNodes.insert(locatorNode); } + KRLight *light = dynamic_cast(node); + if(light) { + m_lights.insert(light); + } } for(std::set::iterator itr=modifiedNodes.begin(); itr != modifiedNodes.end(); itr++) { @@ -558,3 +571,13 @@ bool KRScene::sphereCast(const KRVector3 &v0, const KRVector3 &v1, float radius, } +kraken_stream_level KRScene::getStreamLevel(bool prime) +{ + kraken_stream_level stream_level = kraken_stream_level::STREAM_LEVEL_IN_HQ; + + if(m_pRootNode) { + stream_level = KRMIN(stream_level, m_pRootNode->getStreamLevel(prime)); + } + + return stream_level; +} diff --git a/KREngine/kraken/KRScene.h b/KREngine/kraken/KRScene.h index 90ef75c..7050dd4 100644 --- a/KREngine/kraken/KRScene.h +++ b/KREngine/kraken/KRScene.h @@ -64,6 +64,8 @@ public: KRNode *getRootNode(); KRLight *getFirstLight(); + kraken_stream_level getStreamLevel(bool prime = true); + bool lineCast(const KRVector3 &v0, const KRVector3 &v1, KRHitInfo &hitinfo, unsigned int layer_mask); bool rayCast(const KRVector3 &v0, const KRVector3 &dir, KRHitInfo &hitinfo, unsigned int layer_mask); bool sphereCast(const KRVector3 &v0, const KRVector3 &v1, float radius, KRHitInfo &hitinfo, unsigned int layer_mask); @@ -87,6 +89,7 @@ public: std::set &getAmbientZones(); std::set &getReverbZones(); std::set &getLocators(); + std::set &getLights(); private: @@ -102,6 +105,7 @@ private: std::set m_ambientZoneNodes; std::set m_reverbZoneNodes; std::set m_locatorNodes; + std::set m_lights; KROctree m_nodeTree; diff --git a/KREngine/kraken/KRTexture.cpp b/KREngine/kraken/KRTexture.cpp index 4bc425f..f0207aa 100644 --- a/KREngine/kraken/KRTexture.cpp +++ b/KREngine/kraken/KRTexture.cpp @@ -99,11 +99,26 @@ void KRTexture::resetPoolExpiry() m_last_frame_used = getContext().getCurrentFrame(); } + +kraken_stream_level KRTexture::getStreamLevel(bool prime) +{ + if(prime) { + resetPoolExpiry(); + } + + if(m_current_lod_max_dim == 0) { + return kraken_stream_level::STREAM_LEVEL_OUT; + } else if(m_current_lod_max_dim == m_max_lod_max_dim) { + return kraken_stream_level::STREAM_LEVEL_IN_HQ; + } else { + return kraken_stream_level::STREAM_LEVEL_IN_LQ; + } +} + long KRTexture::getLastFrameUsed() { return m_last_frame_used; } - bool KRTexture::isAnimated() { return false; diff --git a/KREngine/kraken/KRTexture.h b/KREngine/kraken/KRTexture.h index dccdd72..dde342f 100644 --- a/KREngine/kraken/KRTexture.h +++ b/KREngine/kraken/KRTexture.h @@ -38,8 +38,8 @@ #include "KRContextObject.h" #include "KRResource.h" - class KRDataBlock; +class KRCamera; class KRTexture : public KRResource { public: @@ -66,6 +66,7 @@ public: bool hasMipmaps(); bool canStreamOut() const; + kraken_stream_level getStreamLevel(bool prime = true); void _swapHandles(); protected: diff --git a/KREngine/kraken/KRVector3.cpp b/KREngine/kraken/KRVector3.cpp index 1a6b55d..9074b68 100644 --- a/KREngine/kraken/KRVector3.cpp +++ b/KREngine/kraken/KRVector3.cpp @@ -417,13 +417,15 @@ void KRVector3::setUniform(GLint location) const if(location != -1) GLDEBUG(glUniform3f(location, x, y, z)); } -void KRVector3::setXMLAttribute(const std::string &base_name, tinyxml2::XMLElement *e) +void KRVector3::setXMLAttribute(const std::string &base_name, tinyxml2::XMLElement *e, const KRVector3 &default_value) { // TODO - Increase number of digits after the decimal in floating point format (6 -> 12?) // FINDME, TODO - This needs optimization... - e->SetAttribute((base_name + "_x").c_str(), x); - e->SetAttribute((base_name + "_y").c_str(), y); - e->SetAttribute((base_name + "_z").c_str(), z); + if(*this != default_value) { + e->SetAttribute((base_name + "_x").c_str(), x); + e->SetAttribute((base_name + "_y").c_str(), y); + e->SetAttribute((base_name + "_z").c_str(), z); + } } void KRVector3::getXMLAttribute(const std::string &base_name, tinyxml2::XMLElement *e, const KRVector3 &default_value) diff --git a/KREngine/kraken/KRVector3.h b/KREngine/kraken/KRVector3.h index 82aa60f..072843e 100644 --- a/KREngine/kraken/KRVector3.h +++ b/KREngine/kraken/KRVector3.h @@ -126,7 +126,7 @@ public: void setUniform(GLint location) const; - void setXMLAttribute(const std::string &base_name, tinyxml2::XMLElement *e); + void setXMLAttribute(const std::string &base_name, tinyxml2::XMLElement *e, const KRVector3 &default_value); void getXMLAttribute(const std::string &base_name, tinyxml2::XMLElement *e, const KRVector3 &default_value); }; From e463359405dfa9321ca415d6de786ff650b45718 Mon Sep 17 00:00:00 2001 From: Kearwood Gilbert Date: Tue, 11 Mar 2014 00:27:27 -0700 Subject: [PATCH 47/84] Removed TTY spam --HG-- branch : nfb --- KREngine/kraken/KRLODSet.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/KREngine/kraken/KRLODSet.cpp b/KREngine/kraken/KRLODSet.cpp index 5b3a10b..b3577c7 100644 --- a/KREngine/kraken/KRLODSet.cpp +++ b/KREngine/kraken/KRLODSet.cpp @@ -56,10 +56,10 @@ void KRLODSet::updateLODVisibility(const KRViewport &viewport) m_activeLODGroup = new_active_lod_group; } else if(new_active_lod_group != m_activeLODGroup) { if(new_active_lod_group->getStreamLevel(true) >= kraken_stream_level::STREAM_LEVEL_IN_LQ) { - fprintf(stderr, "LOD %s -> %s\n", m_activeLODGroup->getName().c_str(), new_active_lod_group->getName().c_str()); + // fprintf(stderr, "LOD %s -> %s\n", m_activeLODGroup->getName().c_str(), new_active_lod_group->getName().c_str()); m_activeLODGroup = new_active_lod_group; } else { - fprintf(stderr, "LOD %s -> %s - waiting for streaming...\n", m_activeLODGroup->getName().c_str(), new_active_lod_group->getName().c_str()); + // fprintf(stderr, "LOD %s -> %s - waiting for streaming...\n", m_activeLODGroup->getName().c_str(), new_active_lod_group->getName().c_str()); } } From 215349d3422be25b9767462369933686f6a5729b Mon Sep 17 00:00:00 2001 From: Kearwood Gilbert Date: Tue, 11 Mar 2014 23:43:02 -0700 Subject: [PATCH 48/84] Enabled multithreaded driver in iOS 7.1 Disabled lod_group switch deferral until it can be fixed (objects were failing to up-lod or appear) --HG-- branch : nfb --- KREngine/kraken/KRLODSet.cpp | 2 +- KREngine/kraken/KRMeshStreamer.mm | 1 + KREngine/kraken/KRTextureStreamer.mm | 1 + 3 files changed, 3 insertions(+), 1 deletion(-) diff --git a/KREngine/kraken/KRLODSet.cpp b/KREngine/kraken/KRLODSet.cpp index b3577c7..01a7cd0 100644 --- a/KREngine/kraken/KRLODSet.cpp +++ b/KREngine/kraken/KRLODSet.cpp @@ -55,7 +55,7 @@ void KRLODSet::updateLODVisibility(const KRViewport &viewport) } else if(m_activeLODGroup == NULL) { m_activeLODGroup = new_active_lod_group; } else if(new_active_lod_group != m_activeLODGroup) { - if(new_active_lod_group->getStreamLevel(true) >= kraken_stream_level::STREAM_LEVEL_IN_LQ) { + if(true || new_active_lod_group->getStreamLevel(true) >= kraken_stream_level::STREAM_LEVEL_IN_LQ) { // fprintf(stderr, "LOD %s -> %s\n", m_activeLODGroup->getName().c_str(), new_active_lod_group->getName().c_str()); m_activeLODGroup = new_active_lod_group; } else { diff --git a/KREngine/kraken/KRMeshStreamer.mm b/KREngine/kraken/KRMeshStreamer.mm index 82f101c..e1fb85f 100644 --- a/KREngine/kraken/KRMeshStreamer.mm +++ b/KREngine/kraken/KRMeshStreamer.mm @@ -39,6 +39,7 @@ void KRMeshStreamer::startStreamer() #if TARGET_OS_IPHONE gMeshStreamerContext = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES2 sharegroup: [EAGLContext currentContext].sharegroup]; + gMeshStreamerContext.multiThreaded = TRUE; #elif TARGET_OS_MAC NSOpenGLPixelFormatAttribute pixelFormatAttributes[] = { diff --git a/KREngine/kraken/KRTextureStreamer.mm b/KREngine/kraken/KRTextureStreamer.mm index 4aa1230..320a34f 100644 --- a/KREngine/kraken/KRTextureStreamer.mm +++ b/KREngine/kraken/KRTextureStreamer.mm @@ -41,6 +41,7 @@ void KRTextureStreamer::startStreamer() #if TARGET_OS_IPHONE gTextureStreamerContext = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES2 sharegroup: [EAGLContext currentContext].sharegroup]; + gTextureStreamerContext.multiThreaded = TRUE; #elif TARGET_OS_MAC From b702b5901fff71fb957edc755dc166feac368e35 Mon Sep 17 00:00:00 2001 From: Manjit Bedi Date: Wed, 12 Mar 2014 18:37:10 -0700 Subject: [PATCH 49/84] 1) calling the multi threading selector in iOS 7 causes a crash 2) it appears to also cause problems at run-time in the release build with crashing --HG-- branch : nfb --- KREngine/kraken/KRMeshStreamer.mm | 3 ++- KREngine/kraken/KRTextureStreamer.mm | 4 +++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/KREngine/kraken/KRMeshStreamer.mm b/KREngine/kraken/KRMeshStreamer.mm index e1fb85f..96e2a7b 100644 --- a/KREngine/kraken/KRMeshStreamer.mm +++ b/KREngine/kraken/KRMeshStreamer.mm @@ -38,8 +38,9 @@ void KRMeshStreamer::startStreamer() m_running = true; #if TARGET_OS_IPHONE + // FIXME: need to add code check for iOS 7 and also this appears to cause crashing gMeshStreamerContext = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES2 sharegroup: [EAGLContext currentContext].sharegroup]; - gMeshStreamerContext.multiThreaded = TRUE; + //gMeshStreamerContext.multiThreaded = TRUE; #elif TARGET_OS_MAC NSOpenGLPixelFormatAttribute pixelFormatAttributes[] = { diff --git a/KREngine/kraken/KRTextureStreamer.mm b/KREngine/kraken/KRTextureStreamer.mm index 320a34f..81dd5df 100644 --- a/KREngine/kraken/KRTextureStreamer.mm +++ b/KREngine/kraken/KRTextureStreamer.mm @@ -41,7 +41,9 @@ void KRTextureStreamer::startStreamer() #if TARGET_OS_IPHONE gTextureStreamerContext = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES2 sharegroup: [EAGLContext currentContext].sharegroup]; - gTextureStreamerContext.multiThreaded = TRUE; + // FIXME: need to add code check for iOS 7 and also this appears to cause crashing + + //gTextureStreamerContext.multiThreaded = TRUE; #elif TARGET_OS_MAC From 0d7bbfb2d7c12324a683f9f8bfd6966ebdf84f9e Mon Sep 17 00:00:00 2001 From: kelly <> Date: Fri, 14 Mar 2014 03:23:05 -0700 Subject: [PATCH 50/84] flare shaders added to the bundle --HG-- branch : nfb --- KREngine/Kraken.xcodeproj/project.pbxproj | 4 ++++ .../xcschemes/xcschememanagement.plist | 23 ++++++++++++++++++- 2 files changed, 26 insertions(+), 1 deletion(-) diff --git a/KREngine/Kraken.xcodeproj/project.pbxproj b/KREngine/Kraken.xcodeproj/project.pbxproj index 5708595..3c787f3 100644 --- a/KREngine/Kraken.xcodeproj/project.pbxproj +++ b/KREngine/Kraken.xcodeproj/project.pbxproj @@ -10,6 +10,8 @@ 104A335E1672D31C001C8BA6 /* KRCollider.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 104A335C1672D31B001C8BA6 /* KRCollider.cpp */; }; 104A335F1672D31C001C8BA6 /* KRCollider.h in Headers */ = {isa = PBXBuildFile; fileRef = 104A335D1672D31C001C8BA6 /* KRCollider.h */; settings = {ATTRIBUTES = (); }; }; 10CC33A5168534F000BB9846 /* KRCamera.cpp in Sources */ = {isa = PBXBuildFile; fileRef = E48B3CBF14393E2F000C50E2 /* KRCamera.cpp */; }; + 5035BF3E18D2BBDD00252924 /* flare.fsh in Resources */ = {isa = PBXBuildFile; fileRef = E42559AA184DD4490081BB20 /* flare.fsh */; }; + 5035BF3F18D2BBDD00252924 /* flare.vsh in Resources */ = {isa = PBXBuildFile; fileRef = E42559AC184DD45A0081BB20 /* flare.vsh */; }; E4030E4C160A3CF000592648 /* KRStockGeometry.h in Headers */ = {isa = PBXBuildFile; fileRef = E4030E4B160A3CF000592648 /* KRStockGeometry.h */; settings = {ATTRIBUTES = (); }; }; E4030E4D160A3CF000592648 /* KRStockGeometry.h in Headers */ = {isa = PBXBuildFile; fileRef = E4030E4B160A3CF000592648 /* KRStockGeometry.h */; settings = {ATTRIBUTES = (Public, ); }; }; E404702018695DD200F01F42 /* KRTextureKTX.cpp in Sources */ = {isa = PBXBuildFile; fileRef = E404701E18695DD200F01F42 /* KRTextureKTX.cpp */; }; @@ -1610,6 +1612,8 @@ isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( + 5035BF3E18D2BBDD00252924 /* flare.fsh in Resources */, + 5035BF3F18D2BBDD00252924 /* flare.vsh in Resources */, E4409D2916FA748700310F76 /* font.tga in Resources */, E437849816C4884F0037FD43 /* hrtf_kemar.krbundle in Resources */, E4E6F68516BA5DF700E410F8 /* sky_box.fsh in Resources */, diff --git a/KREngine/Kraken.xcodeproj/xcuserdata/admin8onf.xcuserdatad/xcschemes/xcschememanagement.plist b/KREngine/Kraken.xcodeproj/xcuserdata/admin8onf.xcuserdatad/xcschemes/xcschememanagement.plist index cc8ace3..300bfdd 100644 --- a/KREngine/Kraken.xcodeproj/xcuserdata/admin8onf.xcuserdatad/xcschemes/xcschememanagement.plist +++ b/KREngine/Kraken.xcodeproj/xcuserdata/admin8onf.xcuserdatad/xcschemes/xcschememanagement.plist @@ -3,7 +3,28 @@ SchemeUserState - + + Kraken - ios.xcscheme + + isShown + + + Kraken - osx.xcscheme + + isShown + + + Kraken Standard Assets - OSX.xcscheme + + isShown + + + Kraken Standard Assets - iOS.xcscheme + + isShown + + + SuppressBuildableAutocreation E491016013C99B9E0098455B From a70df621b29eccb622ca0dd8e3a5e74a43317bd7 Mon Sep 17 00:00:00 2001 From: Kelly Date: Mon, 17 Mar 2014 23:44:51 -0700 Subject: [PATCH 51/84] fixed the Point Light Flare positioning --HG-- branch : nfb --- KREngine/kraken/KRPointLight.cpp | 4 ++-- KREngine/kraken_standard_assets_ios/Shaders/flare.vsh | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/KREngine/kraken/KRPointLight.cpp b/KREngine/kraken/KRPointLight.cpp index 8ce2f07..f998ae6 100644 --- a/KREngine/kraken/KRPointLight.cpp +++ b/KREngine/kraken/KRPointLight.cpp @@ -57,10 +57,10 @@ void KRPointLight::render(KRCamera *pCamera, std::vector &point_ KRVector3 light_position = getLocalTranslation(); float influence_radius = m_decayStart - sqrt(m_intensity * 0.01f) / sqrt(KRLIGHT_MIN_INFLUENCE); - + KRMat4 sphereModelMatrix = KRMat4(); - sphereModelMatrix.scale(influence_radius); sphereModelMatrix.translate(light_position.x, light_position.y, light_position.z); + sphereModelMatrix.scale(influence_radius); if(viewport.visible(getBounds())) { // Cull out any lights not within the view frustrum diff --git a/KREngine/kraken_standard_assets_ios/Shaders/flare.vsh b/KREngine/kraken_standard_assets_ios/Shaders/flare.vsh index fc0c4d4..b52bc75 100644 --- a/KREngine/kraken_standard_assets_ios/Shaders/flare.vsh +++ b/KREngine/kraken_standard_assets_ios/Shaders/flare.vsh @@ -38,5 +38,5 @@ varying mediump vec2 texCoord; void main() { texCoord = vertex_uv; - gl_Position = mvp_matrix * vec4(0.0, 0.0, 0.0, 1.0) + vec4(vertex_uv.x * viewport.w / viewport.z * 2.0 - 1.0, vertex_uv.y * 2.0 - 1.0, 0.0, 0.0) * flare_size; + gl_Position = mvp_matrix * vec4(0.0, 0.0, 0.0, 1.0) + vec4(vertex_uv.x * viewport.w / viewport.z * 2.0 - 0.75, vertex_uv.y * 2.0 - 1.0, 0.0, 0.0) * flare_size; } From e5959799571c6b2cb7db2f06eeeadb67e24db802 Mon Sep 17 00:00:00 2001 From: "admin8onf@admin8onfs-pro.nfbonf.nfb.ca" Date: Mon, 17 Mar 2014 23:47:18 -0700 Subject: [PATCH 52/84] A minor hack to prevent the Directional light from incorrectly inheriting the Post_rotation from the FBX scene. (if we can figure out where the post rotation is exported from, the hack can be removed) --HG-- branch : nfb --- KREngine/kraken/KRResource+fbx.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/KREngine/kraken/KRResource+fbx.cpp b/KREngine/kraken/KRResource+fbx.cpp index 0c87e14..95b66e5 100644 --- a/KREngine/kraken/KRResource+fbx.cpp +++ b/KREngine/kraken/KRResource+fbx.cpp @@ -926,7 +926,8 @@ void LoadNode(FbxScene* pFbxScene, KRNode *parent_node, FbxGeometryConverter *pG KRVector3 node_pre_rotation, node_post_rotation; if(rotation_active) { node_pre_rotation = KRVector3(pre_rotation[0], pre_rotation[1], pre_rotation[2]) / 180.0 * M_PI; - node_post_rotation = KRVector3(post_rotation[0], post_rotation[1], post_rotation[2]) / 180.0 * M_PI; + // node_post_rotation = KRVector3(post_rotation[0], post_rotation[1], post_rotation[2]) / 180.0 * M_PI; + //&KRF HACK removing this line (above) to prevent the post rotation from corrupting the default light values; the FBX is importing a post rotation and setting it to -90 degrees } else { node_pre_rotation = KRVector3::Zero(); node_post_rotation = KRVector3::Zero(); From d06003ca38cb947a59e4997e1a4f257482ba7502 Mon Sep 17 00:00:00 2001 From: Peter Date: Tue, 18 Mar 2014 12:27:08 -0700 Subject: [PATCH 53/84] Changes required for skybox switching and spam control --HG-- branch : nfb --- KREngine/kraken/KRCamera.cpp | 7 +++++++ KREngine/kraken/KRCamera.h | 2 ++ KREngine/kraken/KREngine.mm | 4 ++-- KREngine/kraken/KRScene.cpp | 4 ++-- 4 files changed, 13 insertions(+), 4 deletions(-) diff --git a/KREngine/kraken/KRCamera.cpp b/KREngine/kraken/KRCamera.cpp index 7a4abf1..f729137 100644 --- a/KREngine/kraken/KRCamera.cpp +++ b/KREngine/kraken/KRCamera.cpp @@ -77,6 +77,13 @@ void KRCamera::loadXML(tinyxml2::XMLElement *e) KRNode::loadXML(e); } +void KRCamera::flushSkybox() +{ + KRTexture *tobedeleted = m_pSkyBoxTexture; + m_pSkyBoxTexture = NULL; +// delete tobedeleted; // FINDME - this delete is not thread safe .. it crashes the texture streamer +} + void KRCamera::renderFrame(float deltaTime, GLint renderBufferWidth, GLint renderBufferHeight) { // ----====---- Record timing information for measuring FPS ----====---- diff --git a/KREngine/kraken/KRCamera.h b/KREngine/kraken/KRCamera.h index 4978c00..d84b589 100644 --- a/KREngine/kraken/KRCamera.h +++ b/KREngine/kraken/KRCamera.h @@ -67,6 +67,8 @@ public: virtual void loadXML(tinyxml2::XMLElement *e); std::string getDebugText(); + + void flushSkybox(); // this will delete the skybox and cause the camera to reload a new skybox based on the settings private: void createBuffers(GLint renderBufferWidth, GLint renderBufferHeight); diff --git a/KREngine/kraken/KREngine.mm b/KREngine/kraken/KREngine.mm index 991fba0..b570d3e 100644 --- a/KREngine/kraken/KREngine.mm +++ b/KREngine/kraken/KREngine.mm @@ -237,14 +237,14 @@ void kraken::set_debug_text(const std::string &print_text) NSString *bundlePath = [[NSBundle mainBundle] pathForResource:bundleName ofType:@"bundle"]; NSBundle *bundle = [NSBundle bundleWithPath:bundlePath]; if(bundle == nil) { - NSLog(@"ERROR - Standard asset bundle could not be found."); + KRContext::Log(KRContext::LOG_LEVEL_ERROR, "%s", "ERROR - Standard asset bundle could not be found."); } else { NSEnumerator *bundleEnumerator = [[bundle pathsForResourcesOfType: nil inDirectory: nil] objectEnumerator]; NSString * p = nil; while (p = [bundleEnumerator nextObject]) { NSString *file_name = [p lastPathComponent]; if([file_name hasSuffix: @".vsh"] || [file_name hasSuffix: @".fsh"] || [file_name hasSuffix: @".krbundle"] ||[file_name hasPrefix:@"font."]) { - NSLog(@" %@\n", file_name); + KRContext::Log(KRContext::LOG_LEVEL_INFORMATION, "%s", [file_name UTF8String]); [self loadResource:p]; } } diff --git a/KREngine/kraken/KRScene.cpp b/KREngine/kraken/KRScene.cpp index 816a4ca..f2ae199 100644 --- a/KREngine/kraken/KRScene.cpp +++ b/KREngine/kraken/KRScene.cpp @@ -126,8 +126,8 @@ void KRScene::render(KRCamera *pCamera, unordered_map &visibleBound std::vectordirectional_lights; std::vectorspot_lights; - pCamera->settings.setSkyBox(m_skyBoxName); // This is temporary until the camera is moved into the scene graph - +// pCamera->settings.setSkyBox(m_skyBoxName); // This is temporary until the camera is moved into the scene graph +// NOTE: the skybox is now selected in the CircaViewController std::set outerNodes = std::set(m_nodeTree.getOuterSceneNodes()); // HACK - Copying the std::set as it is potentially modified as KRNode's update their bounds during the iteration. This is very expensive and will be eliminated in the future. From b5a8c2ae939c767b6fddcf1329b964626429f61e Mon Sep 17 00:00:00 2001 From: Peter Date: Tue, 18 Mar 2014 15:55:31 -0700 Subject: [PATCH 54/84] Added a method to KRAudioManager to allow the CircaAppDelegate to turn off the audio when the app is deactivated --HG-- branch : nfb --- KREngine/kraken/KRAudioManager.cpp | 5 +++++ KREngine/kraken/KRAudioManager.h | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/KREngine/kraken/KRAudioManager.cpp b/KREngine/kraken/KRAudioManager.cpp index 9f8449a..d71079f 100644 --- a/KREngine/kraken/KRAudioManager.cpp +++ b/KREngine/kraken/KRAudioManager.cpp @@ -2057,3 +2057,8 @@ void KRAudioManager::renderLimiter() unsigned long numframes = KRENGINE_AUDIO_BLOCK_LENGTH; audioLimit_Stereo(output, numframes); } + +void KRAudioManager::goToSleep() +{ + cleanupAudio(); +} diff --git a/KREngine/kraken/KRAudioManager.h b/KREngine/kraken/KRAudioManager.h index e829380..33266f8 100644 --- a/KREngine/kraken/KRAudioManager.h +++ b/KREngine/kraken/KRAudioManager.h @@ -144,6 +144,7 @@ public: KRAudioBuffer *getBuffer(KRAudioSample &audio_sample, int buffer_index); static void mute(bool onNotOff); + void goToSleep(); void startFrame(float deltaTime); @@ -191,7 +192,6 @@ private: void cleanupAudio(); void cleanupOpenAL(); void cleanupSiren(); - audio_engine_t m_audio_engine; From a6712c250a677c191ab9836db880bad0af9cd82e Mon Sep 17 00:00:00 2001 From: Kelly Fennig Date: Wed, 19 Mar 2014 13:52:52 -0700 Subject: [PATCH 55/84] Created a 'buildOctreeForTheFirstTime()' function .. which adds the nodes but doesn't deal with LOD --HG-- branch : nfb --- KREngine/kraken/KRScene.cpp | 30 +++++++++++++++++++++++++++++- KREngine/kraken/KRScene.h | 1 + 2 files changed, 30 insertions(+), 1 deletion(-) diff --git a/KREngine/kraken/KRScene.cpp b/KREngine/kraken/KRScene.cpp index f2ae199..8cd20f4 100644 --- a/KREngine/kraken/KRScene.cpp +++ b/KREngine/kraken/KRScene.cpp @@ -515,7 +515,6 @@ void KRScene::updateOctree(const KRViewport &viewport) if(light) { m_lights.insert(light); } - } for(std::set::iterator itr=modifiedNodes.begin(); itr != modifiedNodes.end(); itr++) { KRNode *node = *itr; @@ -530,6 +529,35 @@ void KRScene::updateOctree(const KRViewport &viewport) } } +void KRScene::buildOctreeForTheFirstTime() +{ + std::set newNodes = std::move(m_newNodes); + m_newNodes.clear(); + for(std::set::iterator itr=newNodes.begin(); itr != newNodes.end(); itr++) { + KRNode *node = *itr; + m_nodeTree.add(node); + if(node->hasPhysics()) { + m_physicsNodes.insert(node); + } + KRAmbientZone *ambientZoneNode = dynamic_cast(node); + if(ambientZoneNode) { + m_ambientZoneNodes.insert(ambientZoneNode); + } + KRReverbZone *reverbZoneNode = dynamic_cast(node); + if(reverbZoneNode) { + m_reverbZoneNodes.insert(reverbZoneNode); + } + KRLocator *locatorNode = dynamic_cast(node); + if(locatorNode) { + m_locatorNodes.insert(locatorNode); + } + KRLight *light = dynamic_cast(node); + if(light) { + m_lights.insert(light); + } + } +} + void KRScene::physicsUpdate(float deltaTime) { for(std::set::iterator itr=m_physicsNodes.begin(); itr != m_physicsNodes.end(); itr++) { diff --git a/KREngine/kraken/KRScene.h b/KREngine/kraken/KRScene.h index 7050dd4..799eab2 100644 --- a/KREngine/kraken/KRScene.h +++ b/KREngine/kraken/KRScene.h @@ -76,6 +76,7 @@ public: void render(KROctreeNode *pOctreeNode, unordered_map &visibleBounds, KRCamera *pCamera, std::vector &point_lights, std::vector &directional_lights, std::vector&spot_lights, const KRViewport &viewport, KRNode::RenderPass renderPass, std::vector &remainingOctrees, std::vector &remainingOctreesTestResults, std::vector &remainingOctreesTestResultsOnly, bool bOcclusionResultsPass, bool bOcclusionTestResultsOnly); void updateOctree(const KRViewport &viewport); + void buildOctreeForTheFirstTime(); void notify_sceneGraphCreate(KRNode *pNode); void notify_sceneGraphDelete(KRNode *pNode); From 6d772967f16b64d5b27d39cf460f6e9d6db8b882 Mon Sep 17 00:00:00 2001 From: Peter Date: Fri, 21 Mar 2014 11:45:23 -0700 Subject: [PATCH 56/84] Fixed the audio mute method so we can mute and un-mute even when we are not rendering. --HG-- branch : nfb --- KREngine/kraken/KRAudioManager.cpp | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/KREngine/kraken/KRAudioManager.cpp b/KREngine/kraken/KRAudioManager.cpp index d71079f..eecaec2 100644 --- a/KREngine/kraken/KRAudioManager.cpp +++ b/KREngine/kraken/KRAudioManager.cpp @@ -1954,12 +1954,20 @@ static bool audioShouldBecomeUnmuted = false; void audioLimit_Mute(bool onNotOff) { if (onNotOff) { - if (audioIsMuted) return; + if (audioIsMuted) { + audioShouldBecomeMuted = false; + audioShouldBecomeUnmuted = false; + return; + } audioShouldBecomeMuted = true; audioShouldBecomeUnmuted = false; } else { - if (!audioIsMuted) return; + if (!audioIsMuted) { + audioShouldBecomeMuted = false; + audioShouldBecomeUnmuted = false; + return; + } audioShouldBecomeMuted = false; audioShouldBecomeUnmuted = true; } From c6f6260df4313b3bd24072b88f7a9ed1c6eb669a Mon Sep 17 00:00:00 2001 From: Kearwood Gilbert Date: Tue, 25 Mar 2014 21:44:49 -0700 Subject: [PATCH 57/84] Reversed commit that caused point lights to be broken in the deferred rendering path. --HG-- branch : nfb --- KREngine/kraken/KRPointLight.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/KREngine/kraken/KRPointLight.cpp b/KREngine/kraken/KRPointLight.cpp index f998ae6..8ce2f07 100644 --- a/KREngine/kraken/KRPointLight.cpp +++ b/KREngine/kraken/KRPointLight.cpp @@ -57,10 +57,10 @@ void KRPointLight::render(KRCamera *pCamera, std::vector &point_ KRVector3 light_position = getLocalTranslation(); float influence_radius = m_decayStart - sqrt(m_intensity * 0.01f) / sqrt(KRLIGHT_MIN_INFLUENCE); - + KRMat4 sphereModelMatrix = KRMat4(); - sphereModelMatrix.translate(light_position.x, light_position.y, light_position.z); sphereModelMatrix.scale(influence_radius); + sphereModelMatrix.translate(light_position.x, light_position.y, light_position.z); if(viewport.visible(getBounds())) { // Cull out any lights not within the view frustrum From 827ad1eb7be46c503c76f620bdba93a52bdb8ee5 Mon Sep 17 00:00:00 2001 From: Kearwood Gilbert Date: Tue, 25 Mar 2014 22:40:31 -0700 Subject: [PATCH 58/84] KRNode::getBounds() now caches the calculated bounds and invalidates the cache as needed. This improves speed of Octree generation. --HG-- branch : nfb --- KREngine/kraken/KRModel.cpp | 2 ++ KREngine/kraken/KRNode.cpp | 41 ++++++++++++++++++++++++------------- KREngine/kraken/KRNode.h | 4 ++++ 3 files changed, 33 insertions(+), 14 deletions(-) diff --git a/KREngine/kraken/KRModel.cpp b/KREngine/kraken/KRModel.cpp index aa931b1..eb01799 100644 --- a/KREngine/kraken/KRModel.cpp +++ b/KREngine/kraken/KRModel.cpp @@ -141,6 +141,8 @@ void KRModel::loadModel() { m_bones = bones; getScene().notify_sceneGraphModify(this); } + + invalidateBounds(); } } } diff --git a/KREngine/kraken/KRNode.cpp b/KREngine/kraken/KRNode.cpp index 1cd4eda..868cc87 100644 --- a/KREngine/kraken/KRNode.cpp +++ b/KREngine/kraken/KRNode.cpp @@ -65,6 +65,7 @@ KRNode::KRNode(KRScene &scene, std::string name) : KRContextObject(scene.getCont m_activePoseMatrix = KRMat4(); m_lod_visible = false; m_scale_compensation = false; + m_boundsValid = false; m_lastRenderFrame = -1000; for(int i=0; i < KRENGINE_NODE_ATTRIBUTE_COUNT; i++) { @@ -108,7 +109,7 @@ bool KRNode::getScaleCompensation() void KRNode::childDeleted(KRNode *child_node) { m_childNodes.erase(child_node); - // InvalidateBounds(); + invalidateBounds(); getScene().notify_sceneGraphModify(this); } @@ -475,22 +476,26 @@ KRScene &KRNode::getScene() { } KRAABB KRNode::getBounds() { - KRAABB bounds = KRAABB::Zero(); + if(!m_boundsValid) { + KRAABB bounds = KRAABB::Zero(); - bool first_child = true; - for(std::set::iterator itr=m_childNodes.begin(); itr != m_childNodes.end(); ++itr) { - KRNode *child = (*itr); - if(child->getBounds() != KRAABB::Zero()) { - if(first_child) { - first_child = false; - bounds = child->getBounds(); - } else { - bounds.encapsulate(child->getBounds()); + bool first_child = true; + for(std::set::iterator itr=m_childNodes.begin(); itr != m_childNodes.end(); ++itr) { + KRNode *child = (*itr); + if(child->getBounds() != KRAABB::Zero()) { + if(first_child) { + first_child = false; + bounds = child->getBounds(); + } else { + bounds.encapsulate(child->getBounds()); + } } } + + m_bounds = bounds; + m_boundsValid = true; } - - return bounds; + return m_bounds; } void KRNode::invalidateModelMatrix() @@ -503,7 +508,7 @@ void KRNode::invalidateModelMatrix() child->invalidateModelMatrix(); } - // InvalidateBounds + invalidateBounds(); getScene().notify_sceneGraphModify(this); } @@ -949,3 +954,11 @@ kraken_stream_level KRNode::getStreamLevel(bool prime) return stream_level; } + +void KRNode::invalidateBounds() const +{ + m_boundsValid = false; + if(m_parentNode) { + m_parentNode->invalidateBounds(); + } +} diff --git a/KREngine/kraken/KRNode.h b/KREngine/kraken/KRNode.h index e4bb849..f9503b6 100644 --- a/KREngine/kraken/KRNode.h +++ b/KREngine/kraken/KRNode.h @@ -110,6 +110,7 @@ public: void setWorldRotation(const KRVector3 &v); virtual KRAABB getBounds(); + void invalidateBounds() const; const KRMat4 &getModelMatrix(); const KRMat4 &getInverseModelMatrix(); const KRMat4 &getBindPoseMatrix(); @@ -216,6 +217,9 @@ private: bool m_activePoseMatrixValid; bool m_inverseBindPoseMatrixValid; + mutable KRAABB m_bounds; + mutable bool m_boundsValid; + std::string m_name; From db3c4993d98b503988210152c379851585366fc1 Mon Sep 17 00:00:00 2001 From: Peter Date: Tue, 1 Apr 2014 11:50:45 -0700 Subject: [PATCH 59/84] Fixed Max Distance bug in FBX Importer --HG-- branch : nfb --- KREngine/kraken/KRResource+fbx.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/KREngine/kraken/KRResource+fbx.cpp b/KREngine/kraken/KRResource+fbx.cpp index 95b66e5..8640e90 100644 --- a/KREngine/kraken/KRResource+fbx.cpp +++ b/KREngine/kraken/KRResource+fbx.cpp @@ -946,7 +946,7 @@ void LoadNode(FbxScene* pFbxScene, KRNode *parent_node, FbxGeometryConverter *pG float group_max_distance = 0.0f; if(fbx_lod_group->MinMaxDistance.Get()) { group_min_distance = fbx_lod_group->MinDistance.Get(); - group_max_distance = fbx_lod_group->MinDistance.Get(); + group_max_distance = fbx_lod_group->MaxDistance.Get(); } KRLODSet *lod_set = new KRLODSet(parent_node->getScene(), name); From ff11c6d4031a19c85045aaf333aff8638aaf30a6 Mon Sep 17 00:00:00 2001 From: peter Date: Tue, 1 Apr 2014 14:21:01 -0700 Subject: [PATCH 60/84] a little bit of cleanup to flushSkybox() --HG-- branch : nfb --- KREngine/kraken/KRCamera.cpp | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/KREngine/kraken/KRCamera.cpp b/KREngine/kraken/KRCamera.cpp index f729137..dca0ea4 100644 --- a/KREngine/kraken/KRCamera.cpp +++ b/KREngine/kraken/KRCamera.cpp @@ -79,9 +79,7 @@ void KRCamera::loadXML(tinyxml2::XMLElement *e) void KRCamera::flushSkybox() { - KRTexture *tobedeleted = m_pSkyBoxTexture; - m_pSkyBoxTexture = NULL; -// delete tobedeleted; // FINDME - this delete is not thread safe .. it crashes the texture streamer + m_pSkyBoxTexture = NULL; // NOTE: the streamer manages the loading and unloading of the skybox textures } void KRCamera::renderFrame(float deltaTime, GLint renderBufferWidth, GLint renderBufferHeight) From 389548bd42186e7a9b411e6a323dd7149c0d140d Mon Sep 17 00:00:00 2001 From: Kelly Fennig Date: Fri, 4 Apr 2014 00:46:36 -0700 Subject: [PATCH 61/84] reverting hack to remove post rotation. --HG-- branch : nfb --- KREngine/kraken/KRResource+fbx.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/KREngine/kraken/KRResource+fbx.cpp b/KREngine/kraken/KRResource+fbx.cpp index 8640e90..09e3ab1 100644 --- a/KREngine/kraken/KRResource+fbx.cpp +++ b/KREngine/kraken/KRResource+fbx.cpp @@ -926,8 +926,8 @@ void LoadNode(FbxScene* pFbxScene, KRNode *parent_node, FbxGeometryConverter *pG KRVector3 node_pre_rotation, node_post_rotation; if(rotation_active) { node_pre_rotation = KRVector3(pre_rotation[0], pre_rotation[1], pre_rotation[2]) / 180.0 * M_PI; - // node_post_rotation = KRVector3(post_rotation[0], post_rotation[1], post_rotation[2]) / 180.0 * M_PI; - //&KRF HACK removing this line (above) to prevent the post rotation from corrupting the default light values; the FBX is importing a post rotation and setting it to -90 degrees + node_post_rotation = KRVector3(post_rotation[0], post_rotation[1], post_rotation[2]) / 180.0 * M_PI; + //&KRF HACK removing this line (above) to prevent the post rotation from corrupting the default light values; the FBX is importing a post rotation and setting it to -90 degrees } else { node_pre_rotation = KRVector3::Zero(); node_post_rotation = KRVector3::Zero(); From ecf2ea2be4a1002f36240cd2efd2c802594b330f Mon Sep 17 00:00:00 2001 From: Kelly Fennig <> Date: Fri, 4 Apr 2014 15:03:45 -0700 Subject: [PATCH 62/84] Range changes to make Ambient and Sunlight tweaks easier in the Debug Menu --HG-- branch : nfb --- KREngine/kraken/KREngine.mm | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/KREngine/kraken/KREngine.mm b/KREngine/kraken/KREngine.mm index b570d3e..a45570c 100644 --- a/KREngine/kraken/KREngine.mm +++ b/KREngine/kraken/KREngine.mm @@ -689,8 +689,8 @@ void kraken::set_debug_text(const std::string &print_text) -(float)getParameterMaxWithIndex: (int)i { float maxValues[54] = { - PI, 3.0f, 1.0f, 1.0, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 10.0f, - 1.0f, 10.0f, 2.0f, 1.0f, 1.0f, 1.0f, 5.0f, 1.0f, 0.5f, 1.0f, + PI, 3.0f, 1.0f, 1.0, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 2.0f, + 1.0f, 5.0f, 2.0f, 1.0f, 1.0f, 1.0f, 5.0f, 1.0f, 0.5f, 1.0f, 2.0f, 2.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 10.0f, 1000.0f, 1.0f, 5.0f, 1000.0f, 1.0f, 5.0f, 3.0f, 1000.0f, 1000.0f, 0.01f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 10.0f, 1.0f, (float)(KRRenderSettings::KRENGINE_DEBUG_DISPLAY_NUMBER - 1), @@ -760,6 +760,9 @@ void kraken::set_debug_text(const std::string &print_text) (t < 0.5f ? t * 2.0f : (1.0f - t) * 2.0f) * i, (t < 0.5f ? 1.0f : (1.0f - t) * 2.0f) * i ); +#ifdef TEST4REL + printf("Sun Intensity = %f \n", i); +#endif } -(float) getSunIntensity @@ -803,6 +806,9 @@ void kraken::set_debug_text(const std::string &print_text) (t < 0.5f ? t * 2.0f : (1.0f - t) * 2.0f) * i, (t < 0.5f ? 1.0f : (1.0f - t) * 2.0f) * i ); +#ifdef TEST4REL + printf("ambient Intensity = %f \n", i); +#endif } -(float) getAmbientIntensity From 56a43bb4f4f4b7aa08c7e708eafd13dae142ab17 Mon Sep 17 00:00:00 2001 From: Kearwood Gilbert Date: Wed, 9 Apr 2014 22:15:01 -0700 Subject: [PATCH 63/84] Fixed parsing of mesh lod levels from mesh names --HG-- branch : nfb --- KREngine/kraken/KRMesh.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/KREngine/kraken/KRMesh.cpp b/KREngine/kraken/KRMesh.cpp index 36acf44..b6ba1b3 100644 --- a/KREngine/kraken/KRMesh.cpp +++ b/KREngine/kraken/KRMesh.cpp @@ -71,7 +71,7 @@ void KRMesh::setName(const std::string name) { 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) { + if(suffix.find("lod") == 0) { std::string lod_level_string = suffix.substr(3); char *end = NULL; int c = (int)strtol(lod_level_string.c_str(), &end, 10); @@ -91,7 +91,7 @@ int KRMesh::GetLODCoverage(const std::string &name) 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) { + if(suffix.find("lod") == 0) { std::string lod_level_string = suffix.substr(3); char *end = NULL; int c = (int)strtol(lod_level_string.c_str(), &end, 10); From 39fc3b21ee23ff6b10cce77610801ad22d29b4fa Mon Sep 17 00:00:00 2001 From: Kearwood Gilbert Date: Wed, 9 Apr 2014 22:15:29 -0700 Subject: [PATCH 64/84] New texture streaming algorithm in progress --HG-- branch : nfb --- KREngine/kraken/KRTextureManager.cpp | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/KREngine/kraken/KRTextureManager.cpp b/KREngine/kraken/KRTextureManager.cpp index a250e40..878d254 100644 --- a/KREngine/kraken/KRTextureManager.cpp +++ b/KREngine/kraken/KRTextureManager.cpp @@ -266,6 +266,21 @@ void KRTextureManager::balanceTextureMemory() // Balance texture memory by reducing and increasing the maximum mip-map level of both active and inactive textures // Favour performance over maximum texture resolution when memory is insufficient for textures at full resolution. + /* + NEW ALGORITHM: + + The “fixed” textures will be assigned to the skybox and the animated character flares + The rest of the textures are assigned a “weight” by tuneable criteria: + - Area of screen coverage taken by objects containing material (more accurate and generic than distance) + - Type of texture (separate weight for normal, diffuse, spec maps) + - Last used time (to keep textures loaded for recently seen objects that are outside of the view frustum) + Those factors combine together to give a “weight”, which represents a proportion relative to all other textures weights + Mipmap levels are stripped off of each texture until they occupy the amount of memory they should proportionally have + This is in contrast to the global ceiling of texture resolution that slowly drops until the textures fit + + */ + + // Determine the additional amount of memory required in order to resize all active textures to the maximum size long wantedTextureMem = 0; for(std::set::iterator itr=m_activeTextures_streamer.begin(); itr != m_activeTextures_streamer.end(); itr++) { From fa71fe6041fa250aa37427d50bbb355ac4832a72 Mon Sep 17 00:00:00 2001 From: "admin8onf@admin8onfs-pro.nfbonf.nfb.ca" Date: Thu, 10 Apr 2014 18:48:44 -0700 Subject: [PATCH 65/84] putting the hack back to remove the post rotation for the directional light.... obviously not fixed. --HG-- branch : nfb --- KREngine/kraken/KRResource+fbx.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/KREngine/kraken/KRResource+fbx.cpp b/KREngine/kraken/KRResource+fbx.cpp index 09e3ab1..66f1efe 100644 --- a/KREngine/kraken/KRResource+fbx.cpp +++ b/KREngine/kraken/KRResource+fbx.cpp @@ -926,7 +926,7 @@ void LoadNode(FbxScene* pFbxScene, KRNode *parent_node, FbxGeometryConverter *pG KRVector3 node_pre_rotation, node_post_rotation; if(rotation_active) { node_pre_rotation = KRVector3(pre_rotation[0], pre_rotation[1], pre_rotation[2]) / 180.0 * M_PI; - node_post_rotation = KRVector3(post_rotation[0], post_rotation[1], post_rotation[2]) / 180.0 * M_PI; + //node_post_rotation = KRVector3(post_rotation[0], post_rotation[1], post_rotation[2]) / 180.0 * M_PI; //&KRF HACK removing this line (above) to prevent the post rotation from corrupting the default light values; the FBX is importing a post rotation and setting it to -90 degrees } else { node_pre_rotation = KRVector3::Zero(); From 6135178ccc4c32a2191dd9f92fb38463fad2156d Mon Sep 17 00:00:00 2001 From: Kelly Date: Thu, 10 Apr 2014 19:12:19 -0700 Subject: [PATCH 66/84] Instead of removing post rotation completely, setting the Directional light to return KRVector3::Up() --HG-- branch : nfb --- KREngine/kraken/KRDirectionalLight.cpp | 2 +- KREngine/kraken/KRResource+fbx.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/KREngine/kraken/KRDirectionalLight.cpp b/KREngine/kraken/KRDirectionalLight.cpp index 8e2e01b..532e6d0 100644 --- a/KREngine/kraken/KRDirectionalLight.cpp +++ b/KREngine/kraken/KRDirectionalLight.cpp @@ -35,7 +35,7 @@ KRVector3 KRDirectionalLight::getWorldLightDirection() { } KRVector3 KRDirectionalLight::getLocalLightDirection() { - return KRVector3::Forward(); + return KRVector3::Up(); //&KRF HACK changed from KRVector3::Forward(); - to compensate for the way Maya handles post rotation. } diff --git a/KREngine/kraken/KRResource+fbx.cpp b/KREngine/kraken/KRResource+fbx.cpp index 66f1efe..09e3ab1 100644 --- a/KREngine/kraken/KRResource+fbx.cpp +++ b/KREngine/kraken/KRResource+fbx.cpp @@ -926,7 +926,7 @@ void LoadNode(FbxScene* pFbxScene, KRNode *parent_node, FbxGeometryConverter *pG KRVector3 node_pre_rotation, node_post_rotation; if(rotation_active) { node_pre_rotation = KRVector3(pre_rotation[0], pre_rotation[1], pre_rotation[2]) / 180.0 * M_PI; - //node_post_rotation = KRVector3(post_rotation[0], post_rotation[1], post_rotation[2]) / 180.0 * M_PI; + node_post_rotation = KRVector3(post_rotation[0], post_rotation[1], post_rotation[2]) / 180.0 * M_PI; //&KRF HACK removing this line (above) to prevent the post rotation from corrupting the default light values; the FBX is importing a post rotation and setting it to -90 degrees } else { node_pre_rotation = KRVector3::Zero(); From e8f9652e42c1af6769c3fe99055c1a3b0f955ffc Mon Sep 17 00:00:00 2001 From: Kearwood Gilbert Date: Fri, 11 Apr 2014 01:15:40 -0700 Subject: [PATCH 67/84] Implemented new texture streaming algorithm: MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Textures are assigned a “weight” by tuneable criteria: - Area of screen coverage taken by objects containing material (more accurate and generic than distance) - Type of texture (separate weight for normal, diffuse, spec maps) - Last used time (to keep textures loaded for recently seen objects that are outside of the view frustum) Those factors combine together to give a “weight”, which represents a proportion relative to all other textures weights Mipmap levels are stripped off of each texture until they occupy the amount of memory they should proportionally have This is in contrast to the global ceiling of texture resolution that slowly drops until the textures fit --HG-- branch : nfb extra : rebase_source : 0710cebf76f196f5fea20b6df51be539fafbd834 --- KREngine/kraken/KRCamera.cpp | 31 +++-- KREngine/kraken/KRLODSet.cpp | 6 +- KREngine/kraken/KRLODSet.h | 2 +- KREngine/kraken/KRLight.cpp | 2 +- KREngine/kraken/KRMaterial.cpp | 26 ++-- KREngine/kraken/KRMaterial.h | 4 +- KREngine/kraken/KRMesh.cpp | 8 +- KREngine/kraken/KRMesh.h | 4 +- KREngine/kraken/KRModel.cpp | 20 +++- KREngine/kraken/KRModel.h | 2 +- KREngine/kraken/KRNode.cpp | 4 +- KREngine/kraken/KRNode.h | 2 +- KREngine/kraken/KRParticleSystemNewtonian.cpp | 2 +- KREngine/kraken/KRScene.cpp | 5 +- KREngine/kraken/KRScene.h | 2 +- KREngine/kraken/KRShader.cpp | 6 +- KREngine/kraken/KRSprite.cpp | 2 +- KREngine/kraken/KRTexture.cpp | 56 +++++++-- KREngine/kraken/KRTexture.h | 27 ++++- KREngine/kraken/KRTextureAnimated.cpp | 9 +- KREngine/kraken/KRTextureAnimated.h | 2 +- KREngine/kraken/KRTextureCube.cpp | 6 +- KREngine/kraken/KRTextureCube.h | 2 +- KREngine/kraken/KRTextureManager.cpp | 112 ++++++++++++------ KREngine/kraken/KRTextureManager.h | 8 +- KREngine/kraken/KRViewport.cpp | 14 ++- 26 files changed, 241 insertions(+), 123 deletions(-) diff --git a/KREngine/kraken/KRCamera.cpp b/KREngine/kraken/KRCamera.cpp index dca0ea4..2ee541a 100644 --- a/KREngine/kraken/KRCamera.cpp +++ b/KREngine/kraken/KRCamera.cpp @@ -79,7 +79,7 @@ void KRCamera::loadXML(tinyxml2::XMLElement *e) void KRCamera::flushSkybox() { - m_pSkyBoxTexture = NULL; // NOTE: the streamer manages the loading and unloading of the skybox textures + m_pSkyBoxTexture = NULL; // NOTE: the texture manager manages the loading and unloading of the skybox textures } void KRCamera::renderFrame(float deltaTime, GLint renderBufferWidth, GLint renderBufferHeight) @@ -183,10 +183,10 @@ void KRCamera::renderFrame(float deltaTime, GLint renderBufferWidth, GLint rende GLDEBUG(glDepthMask(GL_FALSE)); // Set source to buffers from pass 1 - m_pContext->getTextureManager()->selectTexture(6, NULL); + m_pContext->getTextureManager()->selectTexture(6, NULL, 0.0f, KRTexture::TEXTURE_USAGE_NONE); m_pContext->getTextureManager()->_setActiveTexture(6); GLDEBUG(glBindTexture(GL_TEXTURE_2D, compositeColorTexture)); - m_pContext->getTextureManager()->selectTexture(7, NULL); + m_pContext->getTextureManager()->selectTexture(7, NULL, 0.0f, KRTexture::TEXTURE_USAGE_NONE); m_pContext->getTextureManager()->_setActiveTexture(7); GLDEBUG(glBindTexture(GL_TEXTURE_2D, compositeDepthTexture)); @@ -211,7 +211,7 @@ void KRCamera::renderFrame(float deltaTime, GLint renderBufferWidth, GLint rende GLDEBUG(glClear(GL_COLOR_BUFFER_BIT)); // Set source to buffers from pass 2 - m_pContext->getTextureManager()->selectTexture(6, NULL); + m_pContext->getTextureManager()->selectTexture(6, NULL, 0.0f, KRTexture::TEXTURE_USAGE_NONE); m_pContext->getTextureManager()->_setActiveTexture(6); GLDEBUG(glBindTexture(GL_TEXTURE_2D, lightAccumulationTexture)); @@ -232,10 +232,10 @@ void KRCamera::renderFrame(float deltaTime, GLint renderBufferWidth, GLint rende scene.render(this, m_viewport.getVisibleBounds(), m_viewport, KRNode::RENDER_PASS_DEFERRED_OPAQUE, false); // Deactivate source buffer texture units - m_pContext->getTextureManager()->selectTexture(6, NULL); + m_pContext->getTextureManager()->selectTexture(6, NULL, 0.0f, KRTexture::TEXTURE_USAGE_NONE); m_pContext->getTextureManager()->_setActiveTexture(6); GLDEBUG(glBindTexture(GL_TEXTURE_2D, 0)); - m_pContext->getTextureManager()->selectTexture(7, NULL); + m_pContext->getTextureManager()->selectTexture(7, NULL, 0.0f, KRTexture::TEXTURE_USAGE_NONE); m_pContext->getTextureManager()->_setActiveTexture(7); GLDEBUG(glBindTexture(GL_TEXTURE_2D, 0)); @@ -313,7 +313,7 @@ void KRCamera::renderFrame(float deltaTime, GLint renderBufferWidth, GLint rende KRVector3 rim_color; getContext().getShaderManager()->selectShader("sky_box", *this, std::vector(), std::vector(), std::vector(), 0, m_viewport, KRMat4(), false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, KRNode::RENDER_PASS_FORWARD_OPAQUE, rim_color, 0.0f); - getContext().getTextureManager()->selectTexture(0, m_pSkyBoxTexture); + getContext().getTextureManager()->selectTexture(0, m_pSkyBoxTexture, 0.0f, KRTexture::TEXTURE_USAGE_SKY_CUBE); // Render a full screen quad m_pContext->getModelManager()->bindVBO(getContext().getModelManager()->KRENGINE_VBO_2D_SQUARE_VERTICES, getContext().getModelManager()->KRENGINE_VBO_2D_SQUARE_INDEXES, getContext().getModelManager()->KRENGINE_VBO_2D_SQUARE_ATTRIBS, true); @@ -425,7 +425,7 @@ void KRCamera::renderFrame(float deltaTime, GLint renderBufferWidth, GLint rende // Disable z-buffer test GLDEBUG(glDisable(GL_DEPTH_TEST)); - m_pContext->getTextureManager()->selectTexture(0, NULL); + m_pContext->getTextureManager()->selectTexture(0, NULL, 0.0f, KRTexture::TEXTURE_USAGE_NONE); m_pContext->getTextureManager()->_setActiveTexture(0); GLDEBUG(glBindTexture(GL_TEXTURE_2D, compositeDepthTexture)); @@ -692,16 +692,16 @@ void KRCamera::renderPost() KRVector3 rim_color; getContext().getShaderManager()->selectShader(*this, postShader, m_viewport, KRMat4(), std::vector(), std::vector(), std::vector(), 0, KRNode::RENDER_PASS_FORWARD_TRANSPARENT, rim_color, 0.0f); - m_pContext->getTextureManager()->selectTexture(0, NULL); + m_pContext->getTextureManager()->selectTexture(0, NULL, 0.0f, KRTexture::TEXTURE_USAGE_NONE); m_pContext->getTextureManager()->_setActiveTexture(0); GLDEBUG(glBindTexture(GL_TEXTURE_2D, compositeDepthTexture)); - m_pContext->getTextureManager()->selectTexture(1, NULL); + m_pContext->getTextureManager()->selectTexture(1, NULL, 0.0f, KRTexture::TEXTURE_USAGE_NONE); m_pContext->getTextureManager()->_setActiveTexture(1); GLDEBUG(glBindTexture(GL_TEXTURE_2D, compositeColorTexture)); if(settings.volumetric_environment_enable) { - m_pContext->getTextureManager()->selectTexture(2, NULL); + m_pContext->getTextureManager()->selectTexture(2, NULL, 0.0f, KRTexture::TEXTURE_USAGE_NONE); m_pContext->getTextureManager()->_setActiveTexture(2); GLDEBUG(glBindTexture(GL_TEXTURE_2D, volumetricLightAccumulationTexture)); } @@ -711,11 +711,11 @@ void KRCamera::renderPost() GLDEBUG(glDrawArrays(GL_TRIANGLE_STRIP, 0, 4)); - m_pContext->getTextureManager()->selectTexture(0, NULL); + m_pContext->getTextureManager()->selectTexture(0, NULL, 0.0f, KRTexture::TEXTURE_USAGE_NONE); m_pContext->getTextureManager()->_setActiveTexture(0); GLDEBUG(glBindTexture(GL_TEXTURE_2D, 0)); - m_pContext->getTextureManager()->selectTexture(1, NULL); + m_pContext->getTextureManager()->selectTexture(1, NULL, 0.0f, KRTexture::TEXTURE_USAGE_NONE); m_pContext->getTextureManager()->_setActiveTexture(1); GLDEBUG(glBindTexture(GL_TEXTURE_2D, 0)); @@ -895,7 +895,7 @@ void KRCamera::renderPost() KRVector3 rim_color; getContext().getShaderManager()->selectShader(*this, fontShader, m_viewport, KRMat4(), std::vector(), std::vector(), std::vector(), 0, KRNode::RENDER_PASS_FORWARD_TRANSPARENT, rim_color, 0.0f); - m_pContext->getTextureManager()->selectTexture(0, m_pContext->getTextureManager()->getTexture("font")); + m_pContext->getTextureManager()->selectTexture(0, m_pContext->getTextureManager()->getTexture("font"), 0.0f, KRTexture::TEXTURE_USAGE_UI); KRDataBlock index_data; //m_pContext->getModelManager()->bindVBO((void *)m_debug_text_vertices, vertex_count * sizeof(DebugTextVertexData), NULL, 0, (1 << KRMesh::KRENGINE_ATTRIB_VERTEX) | (1 << KRMesh::KRENGINE_ATTRIB_TEXUVA), true); @@ -983,8 +983,7 @@ std::string KRCamera::getDebugText() // ---- GPU Memory ---- int texture_count_active = m_pContext->getTextureManager()->getActiveTextures().size(); - int texture_count_pooled = m_pContext->getTextureManager()->getPoolTextures().size(); - int texture_count = texture_count_active + texture_count_pooled; + int texture_count = texture_count_active; long texture_mem_active = m_pContext->getTextureManager()->getMemActive(); long texture_mem_used = m_pContext->getTextureManager()->getMemUsed(); long texture_mem_throughput = m_pContext->getTextureManager()->getMemoryTransferedThisFrame(); diff --git a/KREngine/kraken/KRLODSet.cpp b/KREngine/kraken/KRLODSet.cpp index 01a7cd0..80e288f 100644 --- a/KREngine/kraken/KRLODSet.cpp +++ b/KREngine/kraken/KRLODSet.cpp @@ -55,7 +55,7 @@ void KRLODSet::updateLODVisibility(const KRViewport &viewport) } else if(m_activeLODGroup == NULL) { m_activeLODGroup = new_active_lod_group; } else if(new_active_lod_group != m_activeLODGroup) { - if(true || new_active_lod_group->getStreamLevel(true) >= kraken_stream_level::STREAM_LEVEL_IN_LQ) { + if(new_active_lod_group->getStreamLevel(true, viewport) >= kraken_stream_level::STREAM_LEVEL_IN_LQ) { // fprintf(stderr, "LOD %s -> %s\n", m_activeLODGroup->getName().c_str(), new_active_lod_group->getName().c_str()); m_activeLODGroup = new_active_lod_group; } else { @@ -104,10 +104,10 @@ void KRLODSet::showLOD() } } -kraken_stream_level KRLODSet::getStreamLevel(bool prime) +kraken_stream_level KRLODSet::getStreamLevel(bool prime, const KRViewport &viewport) { if(m_activeLODGroup) { - return m_activeLODGroup->getStreamLevel(prime); + return m_activeLODGroup->getStreamLevel(prime, viewport); } else { return kraken_stream_level::STREAM_LEVEL_IN_HQ; } diff --git a/KREngine/kraken/KRLODSet.h b/KREngine/kraken/KRLODSet.h index a66544f..3e3c433 100644 --- a/KREngine/kraken/KRLODSet.h +++ b/KREngine/kraken/KRLODSet.h @@ -30,7 +30,7 @@ public: virtual void hideLOD(); virtual void childDeleted(KRNode *child_node); - virtual kraken_stream_level getStreamLevel(bool prime = true); + virtual kraken_stream_level getStreamLevel(bool prime, const KRViewport &viewport); private: KRLODGroup *m_activeLODGroup; diff --git a/KREngine/kraken/KRLight.cpp b/KREngine/kraken/KRLight.cpp index 79a303e..6f44976 100644 --- a/KREngine/kraken/KRLight.cpp +++ b/KREngine/kraken/KRLight.cpp @@ -338,7 +338,7 @@ void KRLight::render(KRCamera *pCamera, std::vector &point_light if(getContext().getShaderManager()->selectShader(*pCamera, pShader, viewport, getModelMatrix(), point_lights, directional_lights, spot_lights, 0, renderPass, rim_light, 0.0f)) { pShader->setUniform(KRShader::KRENGINE_UNIFORM_MATERIAL_ALPHA, 1.0f); pShader->setUniform(KRShader::KRENGINE_UNIFORM_FLARE_SIZE, m_flareSize); - m_pContext->getTextureManager()->selectTexture(0, m_pFlareTexture); + m_pContext->getTextureManager()->selectTexture(0, m_pFlareTexture, 0.0f, KRTexture::TEXTURE_USAGE_LIGHT_FLARE); m_pContext->getModelManager()->bindVBO(getContext().getModelManager()->KRENGINE_VBO_2D_SQUARE_VERTICES, getContext().getModelManager()->KRENGINE_VBO_2D_SQUARE_INDEXES, getContext().getModelManager()->KRENGINE_VBO_2D_SQUARE_ATTRIBS, true); GLDEBUG(glDrawArrays(GL_TRIANGLE_STRIP, 0, 4)); } diff --git a/KREngine/kraken/KRMaterial.cpp b/KREngine/kraken/KRMaterial.cpp index bbe4730..f938e02 100644 --- a/KREngine/kraken/KRMaterial.cpp +++ b/KREngine/kraken/KRMaterial.cpp @@ -217,34 +217,34 @@ bool KRMaterial::isTransparent() { return m_tr < 1.0 || m_alpha_mode == KRMATERIAL_ALPHA_MODE_BLENDONESIDE || m_alpha_mode == KRMATERIAL_ALPHA_MODE_BLENDTWOSIDE; } -kraken_stream_level KRMaterial::getStreamLevel(bool prime) +kraken_stream_level KRMaterial::getStreamLevel(bool prime, float lodCoverage) { kraken_stream_level stream_level = kraken_stream_level::STREAM_LEVEL_IN_HQ; getTextures(); if(m_pAmbientMap) { - stream_level = KRMIN(stream_level, m_pNormalMap->getStreamLevel(prime)); + stream_level = KRMIN(stream_level, m_pNormalMap->getStreamLevel(prime, lodCoverage, KRTexture::TEXTURE_USAGE_AMBIENT_MAP)); } if(m_pDiffuseMap) { - stream_level = KRMIN(stream_level, m_pDiffuseMap->getStreamLevel(prime)); + stream_level = KRMIN(stream_level, m_pDiffuseMap->getStreamLevel(prime, lodCoverage, KRTexture::TEXTURE_USAGE_DIFFUSE_MAP)); } if(m_pNormalMap) { - stream_level = KRMIN(stream_level, m_pNormalMap->getStreamLevel(prime)); + stream_level = KRMIN(stream_level, m_pNormalMap->getStreamLevel(prime, lodCoverage, KRTexture::TEXTURE_USAGE_NORMAL_MAP)); } if(m_pSpecularMap) { - stream_level = KRMIN(stream_level, m_pSpecularMap->getStreamLevel(prime)); + stream_level = KRMIN(stream_level, m_pSpecularMap->getStreamLevel(prime, lodCoverage, KRTexture::TEXTURE_USAGE_SPECULAR_MAP)); } if(m_pReflectionMap) { - stream_level = KRMIN(stream_level, m_pReflectionMap->getStreamLevel(prime)); + stream_level = KRMIN(stream_level, m_pReflectionMap->getStreamLevel(prime, lodCoverage, KRTexture::TEXTURE_USAGE_REFLECTION_MAP)); } if(m_pReflectionCube) { - stream_level = KRMIN(stream_level, m_pReflectionCube->getStreamLevel(prime)); + stream_level = KRMIN(stream_level, m_pReflectionCube->getStreamLevel(prime, lodCoverage, KRTexture::TEXTURE_USAGE_REFECTION_CUBE)); } return stream_level; @@ -272,7 +272,7 @@ void KRMaterial::getTextures() } } -bool KRMaterial::bind(KRCamera *pCamera, std::vector &point_lights, std::vector &directional_lights, std::vector&spot_lights, const std::vector &bones, const std::vector &bind_poses, const KRViewport &viewport, const KRMat4 &matModel, KRTexture *pLightMap, KRNode::RenderPass renderPass, const KRVector3 &rim_color, float rim_power) { +bool KRMaterial::bind(KRCamera *pCamera, std::vector &point_lights, std::vector &directional_lights, std::vector&spot_lights, const std::vector &bones, const std::vector &bind_poses, const KRViewport &viewport, const KRMat4 &matModel, KRTexture *pLightMap, KRNode::RenderPass renderPass, const KRVector3 &rim_color, float rim_power, float lod_coverage) { bool bLightMap = pLightMap && pCamera->settings.bEnableLightMap; getTextures(); @@ -359,24 +359,24 @@ bool KRMaterial::bind(KRCamera *pCamera, std::vector &point_ligh pShader->setUniform(KRShader::KRENGINE_UNIFORM_MATERIAL_ALPHA, m_tr); if(bDiffuseMap) { - m_pContext->getTextureManager()->selectTexture(0, m_pDiffuseMap); + m_pContext->getTextureManager()->selectTexture(0, m_pDiffuseMap, lod_coverage, KRTexture::TEXTURE_USAGE_DIFFUSE_MAP); } if(bSpecMap) { - m_pContext->getTextureManager()->selectTexture(1, m_pSpecularMap); + m_pContext->getTextureManager()->selectTexture(1, m_pSpecularMap, lod_coverage, KRTexture::TEXTURE_USAGE_SPECULAR_MAP); } if(bNormalMap) { - m_pContext->getTextureManager()->selectTexture(2, m_pNormalMap); + m_pContext->getTextureManager()->selectTexture(2, m_pNormalMap, lod_coverage, KRTexture::TEXTURE_USAGE_NORMAL_MAP); } if(bReflectionCubeMap && (renderPass == KRNode::RENDER_PASS_FORWARD_OPAQUE || renderPass == KRNode::RENDER_PASS_DEFERRED_OPAQUE)) { - m_pContext->getTextureManager()->selectTexture(4, m_pReflectionCube); + m_pContext->getTextureManager()->selectTexture(4, m_pReflectionCube, lod_coverage, KRTexture::TEXTURE_USAGE_REFECTION_CUBE); } if(bReflectionMap && (renderPass == KRNode::RENDER_PASS_FORWARD_OPAQUE || renderPass == KRNode::RENDER_PASS_DEFERRED_OPAQUE)) { // GL_TEXTURE7 is used for reading the depth buffer in gBuffer pass 2 and re-used for the reflection map in gBuffer Pass 3 and in forward rendering - m_pContext->getTextureManager()->selectTexture(7, m_pReflectionMap); + m_pContext->getTextureManager()->selectTexture(7, m_pReflectionMap, lod_coverage, KRTexture::TEXTURE_USAGE_REFLECTION_MAP); } diff --git a/KREngine/kraken/KRMaterial.h b/KREngine/kraken/KRMaterial.h index 503a045..31a09a5 100644 --- a/KREngine/kraken/KRMaterial.h +++ b/KREngine/kraken/KRMaterial.h @@ -84,11 +84,11 @@ public: bool isTransparent(); const std::string &getName() const; - bool bind(KRCamera *pCamera, std::vector &point_lights, std::vector &directional_lights, std::vector&spot_lights, const std::vector &bones, const std::vector &bind_poses, const KRViewport &viewport, const KRMat4 &matModel, KRTexture *pLightMap, KRNode::RenderPass renderPass, const KRVector3 &rim_color, float rim_power); + bool bind(KRCamera *pCamera, std::vector &point_lights, std::vector &directional_lights, std::vector&spot_lights, const std::vector &bones, const std::vector &bind_poses, const KRViewport &viewport, const KRMat4 &matModel, KRTexture *pLightMap, KRNode::RenderPass renderPass, const KRVector3 &rim_color, float rim_power, float lod_coverage = 0.0f); bool needsVertexTangents(); - kraken_stream_level getStreamLevel(bool prime = true); + kraken_stream_level getStreamLevel(bool prime, float lodCoverage); private: std::string m_name; diff --git a/KREngine/kraken/KRMesh.cpp b/KREngine/kraken/KRMesh.cpp index b6ba1b3..c180b3c 100644 --- a/KREngine/kraken/KRMesh.cpp +++ b/KREngine/kraken/KRMesh.cpp @@ -186,20 +186,20 @@ void KRMesh::getMaterials() } } -kraken_stream_level KRMesh::getStreamLevel(bool prime) +kraken_stream_level KRMesh::getStreamLevel(bool prime, float lodCoverage) { kraken_stream_level stream_level = kraken_stream_level::STREAM_LEVEL_IN_HQ; getSubmeshes(); getMaterials(); for(std::set::iterator mat_itr = m_uniqueMaterials.begin(); mat_itr != m_uniqueMaterials.end(); mat_itr++) { - stream_level = KRMIN(stream_level, (*mat_itr)->getStreamLevel(prime)); + stream_level = KRMIN(stream_level, (*mat_itr)->getStreamLevel(prime, lodCoverage)); } return stream_level; } -void KRMesh::render(const std::string &object_name, KRCamera *pCamera, std::vector &point_lights, std::vector &directional_lights, std::vector&spot_lights, const KRViewport &viewport, const KRMat4 &matModel, KRTexture *pLightMap, KRNode::RenderPass renderPass, const std::vector &bones, const KRVector3 &rim_color, float rim_power) { +void KRMesh::render(const std::string &object_name, KRCamera *pCamera, std::vector &point_lights, std::vector &directional_lights, std::vector&spot_lights, const KRViewport &viewport, const KRMat4 &matModel, KRTexture *pLightMap, KRNode::RenderPass renderPass, const std::vector &bones, const KRVector3 &rim_color, float rim_power, float lod_coverage) { //fprintf(stderr, "Rendering model: %s\n", m_name.c_str()); @@ -233,7 +233,7 @@ void KRMesh::render(const std::string &object_name, KRCamera *pCamera, std::vect for(int i=0; i < bones.size(); i++) { bone_bind_poses.push_back(getBoneBindPose(i)); } - if(pMaterial->bind(pCamera, point_lights, directional_lights, spot_lights, bones, bone_bind_poses, viewport, matModel, pLightMap, renderPass, rim_color, rim_power)) { + if(pMaterial->bind(pCamera, point_lights, directional_lights, spot_lights, bones, bone_bind_poses, viewport, matModel, pLightMap, renderPass, rim_color, rim_power, lod_coverage)) { switch(pMaterial->getAlphaMode()) { case KRMaterial::KRMATERIAL_ALPHA_MODE_OPAQUE: // Non-transparent materials diff --git a/KREngine/kraken/KRMesh.h b/KREngine/kraken/KRMesh.h index 891ddeb..c2ab5ea 100644 --- a/KREngine/kraken/KRMesh.h +++ b/KREngine/kraken/KRMesh.h @@ -67,7 +67,7 @@ public: KRMesh(KRContext &context, std::string name); virtual ~KRMesh(); - kraken_stream_level getStreamLevel(bool prime = true); + kraken_stream_level getStreamLevel(bool prime, float lodCoverage); bool hasTransparency(); @@ -112,7 +112,7 @@ public: std::vector > bone_weights; } mesh_info; - void render(const std::string &object_name, KRCamera *pCamera, std::vector &point_lights, std::vector &directional_lights, std::vector&spot_lights, const KRViewport &viewport, const KRMat4 &matModel, KRTexture *pLightMap, KRNode::RenderPass renderPass, const std::vector &bones, const KRVector3 &rim_color, float rim_power); + void render(const std::string &object_name, KRCamera *pCamera, std::vector &point_lights, std::vector &directional_lights, std::vector&spot_lights, const KRViewport &viewport, const KRMat4 &matModel, KRTexture *pLightMap, KRNode::RenderPass renderPass, const std::vector &bones, const KRVector3 &rim_color, float rim_power, float lod_coverage = 0.0f); std::string m_lodBaseName; diff --git a/KREngine/kraken/KRModel.cpp b/KREngine/kraken/KRModel.cpp index eb01799..731baea 100644 --- a/KREngine/kraken/KRModel.cpp +++ b/KREngine/kraken/KRModel.cpp @@ -157,13 +157,17 @@ void KRModel::render(KRCamera *pCamera, std::vector &point_light if(m_models.size() > 0) { // Don't render meshes on second pass of the deferred lighting renderer, as only lights will be applied - + + /* float lod_coverage = 0.0f; if(m_models.size() > 1) { lod_coverage = viewport.coverage(getBounds()); // This also checks the view frustrum culling } else if(viewport.visible(getBounds())) { lod_coverage = 1.0f; } + */ + + float lod_coverage = viewport.coverage(getBounds()); // This also checks the view frustrum culling if(lod_coverage > m_min_lod_coverage) { @@ -185,7 +189,7 @@ void KRModel::render(KRCamera *pCamera, std::vector &point_light } if(m_pLightMap && pCamera->settings.bEnableLightMap && renderPass != RENDER_PASS_SHADOWMAP && renderPass != RENDER_PASS_GENERATE_SHADOWMAPS) { - m_pContext->getTextureManager()->selectTexture(5, m_pLightMap); + m_pContext->getTextureManager()->selectTexture(5, m_pLightMap, lod_coverage, KRTexture::TEXTURE_USAGE_LIGHT_MAP); } KRMat4 matModel = getModelMatrix(); @@ -195,21 +199,25 @@ void KRModel::render(KRCamera *pCamera, std::vector &point_light matModel = KRQuaternion(KRVector3::Forward(), KRVector3::Normalize(camera_pos - model_center)).rotationMatrix() * matModel; } - pModel->render(getName(), pCamera, point_lights, directional_lights, spot_lights, viewport, matModel, m_pLightMap, renderPass, m_bones[pModel], m_rim_color, m_rim_power); + pModel->render(getName(), pCamera, point_lights, directional_lights, spot_lights, viewport, matModel, m_pLightMap, renderPass, m_bones[pModel], m_rim_color, m_rim_power, lod_coverage); } } } } -kraken_stream_level KRModel::getStreamLevel(bool prime) +kraken_stream_level KRModel::getStreamLevel(bool prime, const KRViewport &viewport) { - kraken_stream_level stream_level = KRNode::getStreamLevel(prime); + kraken_stream_level stream_level = KRNode::getStreamLevel(prime, viewport); loadModel(); + float lod_coverage = 0.0f; + if(prime) { + lod_coverage = viewport.coverage(getBounds()); // This is only used when prime is true + } for(auto itr = m_models.begin(); itr != m_models.end(); itr++) { - stream_level = KRMIN(stream_level, (*itr)->getStreamLevel(prime)); + stream_level = KRMIN(stream_level, (*itr)->getStreamLevel(prime, lod_coverage)); } return stream_level; diff --git a/KREngine/kraken/KRModel.h b/KREngine/kraken/KRModel.h index a048071..f771a4b 100644 --- a/KREngine/kraken/KRModel.h +++ b/KREngine/kraken/KRModel.h @@ -69,7 +69,7 @@ public: void setLightMap(const std::string &name); std::string getLightMap(); - virtual kraken_stream_level getStreamLevel(bool prime = true); + virtual kraken_stream_level getStreamLevel(bool prime, const KRViewport &viewport); private: std::vector m_models; diff --git a/KREngine/kraken/KRNode.cpp b/KREngine/kraken/KRNode.cpp index 868cc87..4f5b07f 100644 --- a/KREngine/kraken/KRNode.cpp +++ b/KREngine/kraken/KRNode.cpp @@ -944,12 +944,12 @@ std::set &KRNode::getBehaviors() return m_behaviors; } -kraken_stream_level KRNode::getStreamLevel(bool prime) +kraken_stream_level KRNode::getStreamLevel(bool prime, const KRViewport &viewport) { kraken_stream_level stream_level = kraken_stream_level::STREAM_LEVEL_IN_HQ; for(std::set::iterator itr=m_childNodes.begin(); itr != m_childNodes.end(); ++itr) { - stream_level = KRMIN(stream_level, (*itr)->getStreamLevel(prime)); + stream_level = KRMIN(stream_level, (*itr)->getStreamLevel(prime, viewport)); } return stream_level; diff --git a/KREngine/kraken/KRNode.h b/KREngine/kraken/KRNode.h index f9503b6..f542d1a 100644 --- a/KREngine/kraken/KRNode.h +++ b/KREngine/kraken/KRNode.h @@ -167,7 +167,7 @@ public: bool getAnimationEnabled(node_attribute_type attrib) const; - virtual kraken_stream_level getStreamLevel(bool prime = true); + virtual kraken_stream_level getStreamLevel(bool prime, const KRViewport &viewport); virtual void hideLOD(); virtual void showLOD(); diff --git a/KREngine/kraken/KRParticleSystemNewtonian.cpp b/KREngine/kraken/KRParticleSystemNewtonian.cpp index 89e9dc5..7431e75 100644 --- a/KREngine/kraken/KRParticleSystemNewtonian.cpp +++ b/KREngine/kraken/KRParticleSystemNewtonian.cpp @@ -68,7 +68,7 @@ void KRParticleSystemNewtonian::render(KRCamera *pCamera, std::vectorgetTextureManager()->getTexture("flare"); - m_pContext->getTextureManager()->selectTexture(0, pParticleTexture); + m_pContext->getTextureManager()->selectTexture(0, pParticleTexture, 0.0f, KRTexture::TEXTURE_USAGE_PARTICLE); int particle_count = 10000; diff --git a/KREngine/kraken/KRScene.cpp b/KREngine/kraken/KRScene.cpp index 8cd20f4..576d8a8 100644 --- a/KREngine/kraken/KRScene.cpp +++ b/KREngine/kraken/KRScene.cpp @@ -599,12 +599,13 @@ bool KRScene::sphereCast(const KRVector3 &v0, const KRVector3 &v1, float radius, } -kraken_stream_level KRScene::getStreamLevel(bool prime) +kraken_stream_level KRScene::getStreamLevel() { kraken_stream_level stream_level = kraken_stream_level::STREAM_LEVEL_IN_HQ; if(m_pRootNode) { - stream_level = KRMIN(stream_level, m_pRootNode->getStreamLevel(prime)); + KRViewport viewport; // This isn't used when prime is false + stream_level = KRMIN(stream_level, m_pRootNode->getStreamLevel(false, viewport)); } return stream_level; diff --git a/KREngine/kraken/KRScene.h b/KREngine/kraken/KRScene.h index 799eab2..0ba470c 100644 --- a/KREngine/kraken/KRScene.h +++ b/KREngine/kraken/KRScene.h @@ -64,7 +64,7 @@ public: KRNode *getRootNode(); KRLight *getFirstLight(); - kraken_stream_level getStreamLevel(bool prime = true); + kraken_stream_level getStreamLevel(); bool lineCast(const KRVector3 &v0, const KRVector3 &v1, KRHitInfo &hitinfo, unsigned int layer_mask); bool rayCast(const KRVector3 &v0, const KRVector3 &dir, KRHitInfo &hitinfo, unsigned int layer_mask); diff --git a/KREngine/kraken/KRShader.cpp b/KREngine/kraken/KRShader.cpp index b1423e0..a2fd09a 100644 --- a/KREngine/kraken/KRShader.cpp +++ b/KREngine/kraken/KRShader.cpp @@ -372,7 +372,7 @@ bool KRShader::bind(KRCamera &camera, const KRViewport &viewport, const KRMat4 & if(light_directional_count == 0) { int cShadowBuffers = directional_light->getShadowBufferCount(); if(m_uniforms[KRENGINE_UNIFORM_SHADOWTEXTURE1] != -1 && cShadowBuffers > 0) { - m_pContext->getTextureManager()->selectTexture(3, NULL); + m_pContext->getTextureManager()->selectTexture(3, NULL, 0.0f, KRTexture::TEXTURE_USAGE_SHADOW_DEPTH); m_pContext->getTextureManager()->_setActiveTexture(3); GLDEBUG(glBindTexture(GL_TEXTURE_2D, directional_light->getShadowTextures()[0])); GLDEBUG(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR)); @@ -383,7 +383,7 @@ bool KRShader::bind(KRCamera &camera, const KRViewport &viewport, const KRMat4 & } if(m_uniforms[KRENGINE_UNIFORM_SHADOWTEXTURE2] != -1 && cShadowBuffers > 1 && camera.settings.m_cShadowBuffers > 1) { - m_pContext->getTextureManager()->selectTexture(4, NULL); + m_pContext->getTextureManager()->selectTexture(4, NULL, 0.0f, KRTexture::TEXTURE_USAGE_SHADOW_DEPTH); m_pContext->getTextureManager()->_setActiveTexture(4); GLDEBUG(glBindTexture(GL_TEXTURE_2D, directional_light->getShadowTextures()[1])); GLDEBUG(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR)); @@ -393,7 +393,7 @@ bool KRShader::bind(KRCamera &camera, const KRViewport &viewport, const KRMat4 & } if(m_uniforms[KRENGINE_UNIFORM_SHADOWTEXTURE3] != -1 && cShadowBuffers > 2 && camera.settings.m_cShadowBuffers > 2) { - m_pContext->getTextureManager()->selectTexture(5, NULL); + m_pContext->getTextureManager()->selectTexture(5, NULL, 0.0f, KRTexture::TEXTURE_USAGE_SHADOW_DEPTH); m_pContext->getTextureManager()->_setActiveTexture(5); GLDEBUG(glActiveTexture(GL_TEXTURE5)); GLDEBUG(glBindTexture(GL_TEXTURE_2D, directional_light->getShadowTextures()[2])); diff --git a/KREngine/kraken/KRSprite.cpp b/KREngine/kraken/KRSprite.cpp index c9f63c6..62fbe5f 100644 --- a/KREngine/kraken/KRSprite.cpp +++ b/KREngine/kraken/KRSprite.cpp @@ -118,7 +118,7 @@ void KRSprite::render(KRCamera *pCamera, std::vector &point_ligh KRVector3 rim_color; if(getContext().getShaderManager()->selectShader(*pCamera, pShader, viewport, getModelMatrix(), point_lights, directional_lights, spot_lights, 0, renderPass, rim_color, 0.0f)) { pShader->setUniform(KRShader::KRENGINE_UNIFORM_MATERIAL_ALPHA, m_spriteAlpha); - m_pContext->getTextureManager()->selectTexture(0, m_pSpriteTexture); + m_pContext->getTextureManager()->selectTexture(0, m_pSpriteTexture, 0.0f, KRTexture::TEXTURE_USAGE_SPRITE); m_pContext->getModelManager()->bindVBO(getContext().getModelManager()->KRENGINE_VBO_2D_SQUARE_VERTICES, getContext().getModelManager()->KRENGINE_VBO_2D_SQUARE_INDEXES, getContext().getModelManager()->KRENGINE_VBO_2D_SQUARE_ATTRIBS, true); GLDEBUG(glDrawArrays(GL_TRIANGLE_STRIP, 0, 4)); } diff --git a/KREngine/kraken/KRTexture.cpp b/KREngine/kraken/KRTexture.cpp index f0207aa..0789815 100644 --- a/KREngine/kraken/KRTexture.cpp +++ b/KREngine/kraken/KRTexture.cpp @@ -20,6 +20,8 @@ KRTexture::KRTexture(KRContext &context, std::string name) : KRResource(context, m_newTextureMemUsed = 0; m_last_frame_used = 0; m_last_frame_bound = 0; + m_last_frame_max_lod_coverage = 0.0f; + m_last_frame_usage = TEXTURE_USAGE_NONE; m_handle_lock.clear(); } @@ -90,31 +92,71 @@ void KRTexture::resize(int max_dim) } GLuint KRTexture::getHandle() { - resetPoolExpiry(); + resetPoolExpiry(0.0f, KRTexture::TEXTURE_USAGE_NONE); // TODO - Pass through getHandle() arguements to replace extraneous resetPoolExpiry calls? return m_iHandle; } -void KRTexture::resetPoolExpiry() +void KRTexture::resetPoolExpiry(float lodCoverage, KRTexture::texture_usage_t textureUsage) { - m_last_frame_used = getContext().getCurrentFrame(); + long current_frame = getContext().getCurrentFrame(); + if(current_frame != m_last_frame_used) { + m_last_frame_used = current_frame; + m_last_frame_max_lod_coverage = 0.0f; + m_last_frame_usage = TEXTURE_USAGE_NONE; + + getContext().getTextureManager()->primeTexture(this); + } + m_last_frame_max_lod_coverage = KRMAX(lodCoverage, m_last_frame_max_lod_coverage); + m_last_frame_usage = static_cast(static_cast(m_last_frame_usage) | static_cast(textureUsage)); } - -kraken_stream_level KRTexture::getStreamLevel(bool prime) +kraken_stream_level KRTexture::getStreamLevel(bool prime, float lodCoverage, KRTexture::texture_usage_t textureUsage) { if(prime) { - resetPoolExpiry(); + resetPoolExpiry(lodCoverage, textureUsage); } if(m_current_lod_max_dim == 0) { return kraken_stream_level::STREAM_LEVEL_OUT; - } else if(m_current_lod_max_dim == m_max_lod_max_dim) { + } else if(m_current_lod_max_dim == KRMIN(getContext().KRENGINE_MAX_TEXTURE_DIM, m_max_lod_max_dim)) { return kraken_stream_level::STREAM_LEVEL_IN_HQ; } else { return kraken_stream_level::STREAM_LEVEL_IN_LQ; } } +float KRTexture::getStreamPriority() +{ + long current_frame = getContext().getCurrentFrame(); + if(current_frame > m_last_frame_used + 5) { + return 1.0f - KRCLAMP((float)(current_frame - m_last_frame_used) / 60.0f, 0.0f, 1.0f); + } else { + float priority = 100.0f; + if(m_last_frame_usage & (TEXTURE_USAGE_UI | TEXTURE_USAGE_SHADOW_DEPTH)) { + priority += 10000000.0f; + } + if(m_last_frame_usage & (TEXTURE_USAGE_SKY_CUBE | TEXTURE_USAGE_PARTICLE | TEXTURE_USAGE_SPRITE | TEXTURE_USAGE_LIGHT_FLARE)) { + priority += 1000000.0f; + } + if(m_last_frame_usage & (TEXTURE_USAGE_DIFFUSE_MAP | TEXTURE_USAGE_AMBIENT_MAP | TEXTURE_USAGE_SPECULAR_MAP | TEXTURE_USAGE_NORMAL_MAP | TEXTURE_USAGE_REFLECTION_MAP)) { + priority += 100000.0f; + } + if(m_last_frame_usage & (TEXTURE_USAGE_LIGHT_MAP)) { + priority += 100000.0f; + } + if(m_last_frame_usage & (TEXTURE_USAGE_REFECTION_CUBE)) { + priority += 1000.0f; + } + priority += m_last_frame_max_lod_coverage * 10.0f; + return priority; + } +} + +float KRTexture::getLastFrameLodCoverage() const +{ + return m_last_frame_max_lod_coverage; +} + long KRTexture::getLastFrameUsed() { return m_last_frame_used; diff --git a/KREngine/kraken/KRTexture.h b/KREngine/kraken/KRTexture.h index dde342f..cc741fb 100644 --- a/KREngine/kraken/KRTexture.h +++ b/KREngine/kraken/KRTexture.h @@ -56,7 +56,26 @@ public: long getLastFrameUsed(); - virtual void resetPoolExpiry(); + typedef enum { + TEXTURE_USAGE_NONE = 0x00, + TEXTURE_USAGE_UI = 0x01, + TEXTURE_USAGE_SKY_CUBE = 0x02, + TEXTURE_USAGE_LIGHT_MAP = 0x04, + TEXTURE_USAGE_DIFFUSE_MAP = 0x08, + TEXTURE_USAGE_AMBIENT_MAP = 0x10, + TEXTURE_USAGE_SPECULAR_MAP = 0x20, + TEXTURE_USAGE_NORMAL_MAP = 0x40, + TEXTURE_USAGE_REFLECTION_MAP = 0x80, + TEXTURE_USAGE_REFECTION_CUBE = 0x100, + TEXTURE_USAGE_LIGHT_FLARE = 0x200, + TEXTURE_USAGE_SHADOW_DEPTH = 0x400, + TEXTURE_USAGE_PARTICLE = 0x800, + TEXTURE_USAGE_SPRITE = 0x1000 + } texture_usage_t; + + float getStreamPriority(); + + virtual void resetPoolExpiry(float lodCoverage, texture_usage_t textureUsage); virtual bool isAnimated(); virtual KRTexture *compress(bool premultiply_alpha = false); @@ -66,9 +85,11 @@ public: bool hasMipmaps(); bool canStreamOut() const; - kraken_stream_level getStreamLevel(bool prime = true); + kraken_stream_level getStreamLevel(bool prime, float lodCoverage, KRTexture::texture_usage_t textureUsage); + float getLastFrameLodCoverage() const; void _swapHandles(); + protected: virtual bool createGLTexture(int lod_max_dim) = 0; GLuint getHandle(); @@ -85,6 +106,8 @@ protected: long m_last_frame_used; long m_last_frame_bound; + float m_last_frame_max_lod_coverage; + texture_usage_t m_last_frame_usage; private: std::atomic m_textureMemUsed; diff --git a/KREngine/kraken/KRTextureAnimated.cpp b/KREngine/kraken/KRTextureAnimated.cpp index c25028a..f3e80c8 100644 --- a/KREngine/kraken/KRTextureAnimated.cpp +++ b/KREngine/kraken/KRTextureAnimated.cpp @@ -91,21 +91,20 @@ long KRTextureAnimated::getMemRequiredForSize(int max_dim) } -void KRTextureAnimated::resetPoolExpiry() +void KRTextureAnimated::resetPoolExpiry(float lodCoverage, texture_usage_t textureUsage) { - KRTexture::resetPoolExpiry(); + KRTexture::resetPoolExpiry(lodCoverage, textureUsage); for(int i=0; iresetPoolExpiry(); // Ensure that frames of animated textures do not expire from the texture pool prematurely, as they are referenced indirectly - getContext().getTextureManager()->primeTexture(frame_texture); + frame_texture->resetPoolExpiry(lodCoverage, textureUsage); // Ensure that frames of animated textures do not expire from the texture pool prematurely, as they are referenced indirectly } } } void KRTextureAnimated::bind(GLuint texture_unit) { - resetPoolExpiry(); + resetPoolExpiry(0.0f, TEXTURE_USAGE_NONE); // TODO - Need to set parameters here for streaming priority? KRTexture::bind(texture_unit); int frame_number = (int)floor(fmodf(getContext().getAbsoluteTime() * m_frame_rate,m_frame_count)); KRTexture2D *frame_texture = textureForFrame(frame_number); diff --git a/KREngine/kraken/KRTextureAnimated.h b/KREngine/kraken/KRTextureAnimated.h index 9e685b6..94ae16c 100644 --- a/KREngine/kraken/KRTextureAnimated.h +++ b/KREngine/kraken/KRTextureAnimated.h @@ -45,7 +45,7 @@ public: virtual void bind(GLuint texture_unit); virtual long getMemRequiredForSize(int max_dim); - virtual void resetPoolExpiry(); + virtual void resetPoolExpiry(float lodCoverage, texture_usage_t textureUsage); virtual long getReferencedMemSize(); diff --git a/KREngine/kraken/KRTextureCube.cpp b/KREngine/kraken/KRTextureCube.cpp index 982e6bb..3021265 100644 --- a/KREngine/kraken/KRTextureCube.cpp +++ b/KREngine/kraken/KRTextureCube.cpp @@ -115,14 +115,14 @@ long KRTextureCube::getMemRequiredForSize(int max_dim) } -void KRTextureCube::resetPoolExpiry() +void KRTextureCube::resetPoolExpiry(float lodCoverage, texture_usage_t textureUsage) { - KRTexture::resetPoolExpiry(); + KRTexture::resetPoolExpiry(lodCoverage, textureUsage); for(int i=0; i<6; i++) { std::string faceName = getName() + SUFFIXES[i]; KRTexture2D *faceTexture = (KRTexture2D *)getContext().getTextureManager()->getTexture(faceName); if(faceTexture) { - faceTexture->resetPoolExpiry(); // Ensure that side of cube maps do not expire from the texture pool prematurely, as they are referenced indirectly + faceTexture->resetPoolExpiry(lodCoverage, textureUsage); // Ensure that side of cube maps do not expire from the texture pool prematurely, as they are referenced indirectly } } } diff --git a/KREngine/kraken/KRTextureCube.h b/KREngine/kraken/KRTextureCube.h index e242147..8a0edc1 100644 --- a/KREngine/kraken/KRTextureCube.h +++ b/KREngine/kraken/KRTextureCube.h @@ -44,7 +44,7 @@ public: virtual void bind(GLuint texture_unit); virtual long getMemRequiredForSize(int max_dim); - virtual void resetPoolExpiry(); + virtual void resetPoolExpiry(float lodCoverage, texture_usage_t textureUsage); private: virtual bool createGLTexture(int lod_max_dim); diff --git a/KREngine/kraken/KRTextureManager.cpp b/KREngine/kraken/KRTextureManager.cpp index 878d254..0b1df0f 100644 --- a/KREngine/kraken/KRTextureManager.cpp +++ b/KREngine/kraken/KRTextureManager.cpp @@ -64,7 +64,7 @@ void KRTextureManager::_clearGLState() m_wrapModeS[i] = 0; m_wrapModeT[i] = 0; m_maxAnisotropy[i] = -1.0f; - selectTexture(i, NULL); + selectTexture(i, NULL, 0.0f, KRTexture::TEXTURE_USAGE_NONE); } m_iActiveTexture = -1; @@ -179,19 +179,17 @@ KRTexture *KRTextureManager::getTexture(const std::string &name) { } } -void KRTextureManager::selectTexture(int iTextureUnit, KRTexture *pTexture) { +void KRTextureManager::selectTexture(int iTextureUnit, KRTexture *pTexture, float lod_coverage, KRTexture::texture_usage_t textureUsage) { bool is_animated = false; if(pTexture) { + pTexture->resetPoolExpiry(lod_coverage, textureUsage); if(pTexture->isAnimated()) is_animated = true; } if(m_boundTextures[iTextureUnit] != pTexture || is_animated) { _setActiveTexture(iTextureUnit); if(pTexture != NULL) { - primeTexture(pTexture); - pTexture->bind(iTextureUnit); - } else { GLDEBUG(glBindTexture(GL_TEXTURE_2D, 0)); } @@ -200,14 +198,6 @@ void KRTextureManager::selectTexture(int iTextureUnit, KRTexture *pTexture) { } -void KRTextureManager::primeTexture(KRTexture *pTexture) -{ - m_poolTextures.erase(pTexture); - if(m_activeTextures.find(pTexture) == m_activeTextures.end()) { - m_activeTextures.insert(pTexture); - } -} - long KRTextureManager::getMemUsed() { return m_textureMemUsed; } @@ -234,7 +224,6 @@ void KRTextureManager::startFrame(float deltaTime) // TODO - Implement proper double-buffering to reduce copy operations m_streamerFenceMutex.lock(); m_activeTextures_streamer_copy = m_activeTextures; - m_poolTextures_streamer_copy = m_poolTextures; m_streamerFenceMutex.unlock(); m_memoryTransferredThisFrame = 0; @@ -245,7 +234,7 @@ void KRTextureManager::endFrame(float deltaTime) { for(int iTexture=0; iTexture < KRENGINE_MAX_TEXTURE_UNITS; iTexture++) { if(m_boundTextures[iTexture]) { - m_boundTextures[iTexture]->resetPoolExpiry(); // Even if the same texture is bound, ensure that they don't expire from the texture pool while in use + m_boundTextures[iTexture]->resetPoolExpiry(0.0f, KRTexture::TEXTURE_USAGE_NONE); // Even if the same texture is bound, ensure that they don't expire from the texture pool while in use } } } @@ -255,7 +244,6 @@ void KRTextureManager::doStreaming() // TODO - Implement proper double-buffering to reduce copy operations m_streamerFenceMutex.lock(); m_activeTextures_streamer = m_activeTextures_streamer_copy; - m_poolTextures_streamer = m_poolTextures_streamer_copy; m_streamerFenceMutex.unlock(); balanceTextureMemory(); @@ -269,8 +257,7 @@ void KRTextureManager::balanceTextureMemory() /* NEW ALGORITHM: - The “fixed” textures will be assigned to the skybox and the animated character flares - The rest of the textures are assigned a “weight” by tuneable criteria: + Textures are assigned a “weight” by tuneable criteria: - Area of screen coverage taken by objects containing material (more accurate and generic than distance) - Type of texture (separate weight for normal, diffuse, spec maps) - Last used time (to keep textures loaded for recently seen objects that are outside of the view frustum) @@ -280,6 +267,69 @@ void KRTextureManager::balanceTextureMemory() */ + // --------------- + + // TODO - Would this be faster with int's for weights? + std::vector> sortedTextures; + for(auto itr=m_activeTextures_streamer.begin(); itr != m_activeTextures_streamer.end(); itr++) { + KRTexture *texture = *itr; + float priority = texture->getStreamPriority(); + sortedTextures.push_back(std::pair(priority, texture)); + } + + std::sort(sortedTextures.begin(), sortedTextures.end(), std::greater>()); + + long memoryRemaining = getContext().KRENGINE_TARGET_TEXTURE_MEM_MAX; + long memoryRemainingThisFrame = KRMIN(getContext().KRENGINE_TARGET_TEXTURE_MEM_MAX - getMemUsed(), getContext().KRENGINE_TARGET_TEXTURE_MEM_MAX); + + for(auto itr=sortedTextures.begin(); itr != sortedTextures.end(); itr++) { + KRTexture *texture = (*itr).second; + int min_mip_level = KRMAX(getContext().KRENGINE_MIN_TEXTURE_DIM, texture->getMinMipMap()); + long minLodMem = texture->getMemRequiredForSize(min_mip_level); + memoryRemaining -= minLodMem; + + if(memoryRemainingThisFrame > minLodMem && texture->getCurrentLodMaxDim() < min_mip_level) { + memoryRemainingThisFrame -= minLodMem; + texture->resize(min_mip_level); + } + } + + std::vector mipPercents = {75, 75, 50, 50, 50}; + int mip_drop = -1; + auto mip_itr = mipPercents.begin(); + long memoryRemainingThisMip = 0; + + for(auto itr=sortedTextures.begin(); itr != sortedTextures.end(); itr++) { + if(memoryRemainingThisMip <= 0) { + if(mip_itr == mipPercents.end()) { + break; + } else { + memoryRemainingThisMip = memoryRemaining * (*mip_itr) / 100; + mip_drop++; + mip_itr++; + } + } + + KRTexture *texture = (*itr).second; + int min_mip_level = KRMAX(getContext().KRENGINE_MIN_TEXTURE_DIM, texture->getMinMipMap()); + int max_mip_level = KRMIN(getContext().KRENGINE_MAX_TEXTURE_DIM, texture->getMaxMipMap()); + int target_mip_level = (max_mip_level >> mip_drop); + long targetMem = texture->getMemRequiredForSize(target_mip_level); + long additionalMemRequired = targetMem - texture->getMemRequiredForSize(min_mip_level); + memoryRemainingThisMip -= additionalMemRequired; + memoryRemaining -= additionalMemRequired; + if(memoryRemainingThisMip > 0 && memoryRemainingThisFrame > targetMem) { + if(texture->getCurrentLodMaxDim() != target_mip_level) { + memoryRemainingThisFrame -= targetMem; + texture->resize(target_mip_level); + } + } + } + + + + // --------------- + /* // Determine the additional amount of memory required in order to resize all active textures to the maximum size long wantedTextureMem = 0; @@ -356,8 +406,7 @@ void KRTextureManager::balanceTextureMemory() } } } - - //fprintf(stderr, "Active mipmap size: %i Inactive mapmap size: %i\n", (int)maxDimActive, (int)maxDimInactive); + */ } void KRTextureManager::rotateBuffers() @@ -366,21 +415,16 @@ void KRTextureManager::rotateBuffers() // ----====---- Expire textures that haven't been used in a long time ----====---- std::set expiredTextures; - for(std::set::iterator itr=m_poolTextures.begin(); itr != m_poolTextures.end(); itr++) { - KRTexture *poolTexture = *itr; - if(poolTexture->getLastFrameUsed() + KRENGINE_TEXTURE_EXPIRY_FRAMES < getContext().getCurrentFrame()) { - expiredTextures.insert(poolTexture); - poolTexture->releaseHandles(); + for(std::set::iterator itr=m_activeTextures.begin(); itr != m_activeTextures.end(); itr++) { + KRTexture *activeTexture = *itr; + if(activeTexture->getLastFrameUsed() + KRENGINE_TEXTURE_EXPIRY_FRAMES < getContext().getCurrentFrame()) { + expiredTextures.insert(activeTexture); + activeTexture->releaseHandles(); } } for(std::set::iterator itr=expiredTextures.begin(); itr != expiredTextures.end(); itr++) { - m_poolTextures.erase(*itr); + m_activeTextures.erase(*itr); } - - // ----====---- Swap the buffers ----====---- - - m_poolTextures.insert(m_activeTextures.begin(), m_activeTextures.end()); - m_activeTextures.clear(); } long KRTextureManager::getMemoryTransferedThisFrame() @@ -444,8 +488,10 @@ std::set &KRTextureManager::getActiveTextures() return m_activeTextures; } -std::set &KRTextureManager::getPoolTextures() +void KRTextureManager::primeTexture(KRTexture *texture) { - return m_poolTextures; + if(m_activeTextures.find(texture) == m_activeTextures.end()) { + m_activeTextures.insert(texture); + } } diff --git a/KREngine/kraken/KRTextureManager.h b/KREngine/kraken/KRTextureManager.h index 41b4b21..46ab085 100644 --- a/KREngine/kraken/KRTextureManager.h +++ b/KREngine/kraken/KRTextureManager.h @@ -46,8 +46,7 @@ public: KRTextureManager(KRContext &context); virtual ~KRTextureManager(); - void primeTexture(KRTexture *pTexture); - void selectTexture(int iTextureUnit, KRTexture *pTexture); + void selectTexture(int iTextureUnit, KRTexture *pTexture, float lod_coverage, KRTexture::texture_usage_t textureUsage); KRTexture *loadTexture(const char *szName, const char *szExtension, KRDataBlock *data); KRTexture *getTextureCube(const char *szName); @@ -69,7 +68,6 @@ public: void compress(bool premultiply_alpha = false); std::set &getActiveTextures(); - std::set &getPoolTextures(); void _setActiveTexture(int i); void _setWrapModeS(GLuint i, GLuint wrap_mode); @@ -80,6 +78,7 @@ public: void setMaxAnisotropy(float max_anisotropy); void doStreaming(); + void primeTexture(KRTexture *texture); private: int m_iActiveTexture; @@ -95,12 +94,9 @@ private: std::set m_activeTextures; - std::set m_poolTextures; std::set m_activeTextures_streamer; - std::set m_poolTextures_streamer; std::set m_activeTextures_streamer_copy; - std::set m_poolTextures_streamer_copy; std::atomic m_textureMemUsed; diff --git a/KREngine/kraken/KRViewport.cpp b/KREngine/kraken/KRViewport.cpp index 6dbd0e9..9feab68 100644 --- a/KREngine/kraken/KRViewport.cpp +++ b/KREngine/kraken/KRViewport.cpp @@ -182,10 +182,8 @@ float KRViewport::coverage(const KRAABB &b) const for(int i=0; i<8; i++) { KRVector3 screen_pos = KRMat4::DotWDiv(m_matViewProjection, KRVector3(i & 1 ? b.min.x : b.max.x, i & 2 ? b.min.y : b.max.y, i & 4 ? b.min.z : b.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; + screen_min = screen_pos.xy(); + screen_max = screen_pos.xy(); } 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; @@ -194,7 +192,13 @@ float KRViewport::coverage(const KRAABB &b) const } } - return (screen_max.x - screen_min.x) * (screen_max.y - screen_min.y); + screen_min.x = KRCLAMP(screen_min.x, 0.0f, 1.0f); + screen_min.y = KRCLAMP(screen_min.y, 0.0f, 1.0f); + screen_max.x = KRCLAMP(screen_max.x, 0.0f, 1.0f); + screen_max.y = KRCLAMP(screen_max.y, 0.0f, 1.0f); + + float c = (screen_max.x - screen_min.x) * (screen_max.y - screen_min.y); + return KRCLAMP(c, 0.01f, 1.0f); } } From 0405eb681b1371a71435fe6fda02c0ac323f7acd Mon Sep 17 00:00:00 2001 From: Kearwood Gilbert Date: Sat, 12 Apr 2014 23:42:26 -0700 Subject: [PATCH 68/84] New streaming algorithm in progress Corrected reflections Corrected KRMATERIAL_ALPHA_MODE_BLENDTWOSIDE alpha mode Corrected alpha transparent back face culling --HG-- branch : nfb --- KREngine/kraken/KRCamera.cpp | 6 +- KREngine/kraken/KRLODSet.cpp | 2 +- KREngine/kraken/KRMaterial.cpp | 4 +- KREngine/kraken/KRMaterialManager.cpp | 2 +- KREngine/kraken/KRResource+fbx.cpp | 58 +++++----- KREngine/kraken/KRTexture.cpp | 2 +- KREngine/kraken/KRTextureManager.cpp | 104 +++--------------- KREngine/kraken/KRTextureManager.h | 4 +- KREngine/kraken/KRTextureTGA.cpp | 2 +- KREngine/kraken/KRViewport.cpp | 12 ++ .../Shaders/ObjectShader.fsh | 4 +- .../Shaders/ObjectShader_osx.fsh | 4 +- 12 files changed, 77 insertions(+), 127 deletions(-) diff --git a/KREngine/kraken/KRCamera.cpp b/KREngine/kraken/KRCamera.cpp index 2ee541a..b111908 100644 --- a/KREngine/kraken/KRCamera.cpp +++ b/KREngine/kraken/KRCamera.cpp @@ -335,7 +335,11 @@ void KRCamera::renderFrame(float deltaTime, GLint renderBufferWidth, GLint rende // // // Disable backface culling // GLDEBUG(glDisable(GL_CULL_FACE)); -// +// + // Enable backface culling + GLDEBUG(glCullFace(GL_BACK)); + GLDEBUG(glEnable(GL_CULL_FACE)); + // Disable z-buffer write GLDEBUG(glDepthMask(GL_FALSE)); // diff --git a/KREngine/kraken/KRLODSet.cpp b/KREngine/kraken/KRLODSet.cpp index 80e288f..d056e30 100644 --- a/KREngine/kraken/KRLODSet.cpp +++ b/KREngine/kraken/KRLODSet.cpp @@ -55,7 +55,7 @@ void KRLODSet::updateLODVisibility(const KRViewport &viewport) } else if(m_activeLODGroup == NULL) { m_activeLODGroup = new_active_lod_group; } else if(new_active_lod_group != m_activeLODGroup) { - if(new_active_lod_group->getStreamLevel(true, viewport) >= kraken_stream_level::STREAM_LEVEL_IN_LQ) { + if(true || new_active_lod_group->getStreamLevel(true, viewport) >= kraken_stream_level::STREAM_LEVEL_IN_LQ) { // FINDME, HACK! Disabled due to performance issues. // fprintf(stderr, "LOD %s -> %s\n", m_activeLODGroup->getName().c_str(), new_active_lod_group->getName().c_str()); m_activeLODGroup = new_active_lod_group; } else { diff --git a/KREngine/kraken/KRMaterial.cpp b/KREngine/kraken/KRMaterial.cpp index f938e02..9a33fd5 100644 --- a/KREngine/kraken/KRMaterial.cpp +++ b/KREngine/kraken/KRMaterial.cpp @@ -370,11 +370,11 @@ bool KRMaterial::bind(KRCamera *pCamera, std::vector &point_ligh m_pContext->getTextureManager()->selectTexture(2, m_pNormalMap, lod_coverage, KRTexture::TEXTURE_USAGE_NORMAL_MAP); } - if(bReflectionCubeMap && (renderPass == KRNode::RENDER_PASS_FORWARD_OPAQUE || renderPass == KRNode::RENDER_PASS_DEFERRED_OPAQUE)) { + if(bReflectionCubeMap && (renderPass == KRNode::RENDER_PASS_FORWARD_OPAQUE || renderPass == KRNode::RENDER_PASS_FORWARD_TRANSPARENT || renderPass == KRNode::RENDER_PASS_DEFERRED_OPAQUE)) { m_pContext->getTextureManager()->selectTexture(4, m_pReflectionCube, lod_coverage, KRTexture::TEXTURE_USAGE_REFECTION_CUBE); } - if(bReflectionMap && (renderPass == KRNode::RENDER_PASS_FORWARD_OPAQUE || renderPass == KRNode::RENDER_PASS_DEFERRED_OPAQUE)) { + if(bReflectionMap && (renderPass == KRNode::RENDER_PASS_FORWARD_OPAQUE || renderPass == KRNode::RENDER_PASS_FORWARD_TRANSPARENT || renderPass == KRNode::RENDER_PASS_DEFERRED_OPAQUE)) { // GL_TEXTURE7 is used for reading the depth buffer in gBuffer pass 2 and re-used for the reflection map in gBuffer Pass 3 and in forward rendering m_pContext->getTextureManager()->selectTexture(7, m_pReflectionMap, lod_coverage, KRTexture::TEXTURE_USAGE_REFLECTION_MAP); } diff --git a/KREngine/kraken/KRMaterialManager.cpp b/KREngine/kraken/KRMaterialManager.cpp index cab3a97..f311873 100644 --- a/KREngine/kraken/KRMaterialManager.cpp +++ b/KREngine/kraken/KRMaterialManager.cpp @@ -153,7 +153,7 @@ bool KRMaterialManager::load(const char *szName, KRDataBlock *data) { } else if(strcmp(szSymbol[1], "blendoneside") == 0) { pMaterial->setAlphaMode(KRMaterial::KRMATERIAL_ALPHA_MODE_BLENDONESIDE); } else if(strcmp(szSymbol[1], "blendtwoside") == 0) { - pMaterial->setAlphaMode(KRMaterial::KRMATERIAL_ALPHA_MODE_BLENDONESIDE); + pMaterial->setAlphaMode(KRMaterial::KRMATERIAL_ALPHA_MODE_BLENDTWOSIDE); } else { pMaterial->setAlphaMode(KRMaterial::KRMATERIAL_ALPHA_MODE_OPAQUE); } diff --git a/KREngine/kraken/KRResource+fbx.cpp b/KREngine/kraken/KRResource+fbx.cpp index 09e3ab1..823bdec 100644 --- a/KREngine/kraken/KRResource+fbx.cpp +++ b/KREngine/kraken/KRResource+fbx.cpp @@ -1150,10 +1150,10 @@ void LoadMaterial(KRContext &context, FbxSurfaceMaterial *pMaterial) { lKFbxDouble1 =((FbxSurfacePhong *) pMaterial)->ReflectionFactor; // Reflection color - lKFbxDouble3 =((FbxSurfacePhong *) pMaterial)->Reflection; + //lKFbxDouble3 =((FbxSurfacePhong *) pMaterial)->Reflection; // We modulate Relection color by reflection factor, as we only have one "reflection color" variable in Kraken - new_material->setReflection(KRVector3(lKFbxDouble3.Get()[0] * lKFbxDouble1.Get(), lKFbxDouble3.Get()[1] * lKFbxDouble1.Get(), lKFbxDouble3.Get()[2] * lKFbxDouble1.Get())); + new_material->setReflection(KRVector3(/*lKFbxDouble3.Get()[0] * */lKFbxDouble1.Get(), /*lKFbxDouble3.Get()[1] * */lKFbxDouble1.Get(), /*lKFbxDouble3.Get()[2] * */lKFbxDouble1.Get())); } else if(pMaterial->GetClassId().Is(FbxSurfaceLambert::ClassId) ) { // We found a Lambert material. @@ -1371,6 +1371,8 @@ void LoadMesh(KRContext &context, FbxScene* pFbxScene, FbxGeometryConverter *pGe } } + pMesh->GenerateTangentsDataForAllUVSets(true); + int polygon_count = pMesh->GetPolygonCount(); int uv_count = pMesh->GetElementUVCount(); int normal_count = pMesh->GetElementNormalCount(); @@ -1487,36 +1489,38 @@ void LoadMesh(KRContext &context, FbxScene* pFbxScene, FbxGeometryConverter *pGe mi.normals.push_back(KRVector3(new_normal[0], new_normal[1], new_normal[2])); } - + /* + TODO - Tangent vectors imported from maya appear incorrectly... Only calculating them in Kraken for now + // ----====---- Read Tangents ----====---- - for(int l = 0; l < tangent_count; ++l) - { - FbxVector4 new_tangent; - FbxGeometryElementTangent* leTangent = pMesh->GetElementTangent(l); - - if(leTangent->GetMappingMode() == FbxGeometryElement::eByPolygonVertex) { - switch (leTangent->GetReferenceMode()) { - case FbxGeometryElement::eDirect: - new_tangent = leTangent->GetDirectArray().GetAt(lControlPointIndex); - break; - case FbxGeometryElement::eIndexToDirect: - { - int id = leTangent->GetIndexArray().GetAt(lControlPointIndex); - new_tangent = leTangent->GetDirectArray().GetAt(id); + if(need_tangents) { + for(int l = 0; l < tangent_count; ++l) + { + FbxVector4 new_tangent; + FbxGeometryElementTangent* leTangent = pMesh->GetElementTangent(l); + + if(leTangent->GetMappingMode() == FbxGeometryElement::eByPolygonVertex) { + switch (leTangent->GetReferenceMode()) { + case FbxGeometryElement::eDirect: + new_tangent = leTangent->GetDirectArray().GetAt(lControlPointIndex); + break; + case FbxGeometryElement::eIndexToDirect: + { + int id = leTangent->GetIndexArray().GetAt(lControlPointIndex); + new_tangent = leTangent->GetDirectArray().GetAt(id); + } + break; + default: + break; // other reference modes not shown here! } - break; - default: - break; // other reference modes not shown here! } + if(l == 0) { + mi.tangents.push_back(KRVector3(new_tangent[0], new_tangent[1], new_tangent[2])); + } + } - if(l == 0) { - mi.tangents.push_back(KRVector3(new_tangent[0], new_tangent[1], new_tangent[2])); - } - } - - - + */ source_vertex_id++; dest_vertex_id++; diff --git a/KREngine/kraken/KRTexture.cpp b/KREngine/kraken/KRTexture.cpp index 0789815..cd57fd9 100644 --- a/KREngine/kraken/KRTexture.cpp +++ b/KREngine/kraken/KRTexture.cpp @@ -145,7 +145,7 @@ float KRTexture::getStreamPriority() priority += 100000.0f; } if(m_last_frame_usage & (TEXTURE_USAGE_REFECTION_CUBE)) { - priority += 1000.0f; + priority += 100000.0f; } priority += m_last_frame_max_lod_coverage * 10.0f; return priority; diff --git a/KREngine/kraken/KRTextureManager.cpp b/KREngine/kraken/KRTextureManager.cpp index 0b1df0f..3d4f489 100644 --- a/KREngine/kraken/KRTextureManager.cpp +++ b/KREngine/kraken/KRTextureManager.cpp @@ -223,7 +223,15 @@ void KRTextureManager::startFrame(float deltaTime) // TODO - Implement proper double-buffering to reduce copy operations m_streamerFenceMutex.lock(); - m_activeTextures_streamer_copy = m_activeTextures; + + + m_activeTextures_streamer_copy.clear(); + for(auto itr=m_activeTextures.begin(); itr != m_activeTextures.end(); itr++) { + KRTexture *texture = *itr; + float priority = texture->getStreamPriority(); + m_activeTextures_streamer_copy.push_back(std::pair(priority, texture)); + } + m_streamerFenceMutex.unlock(); m_memoryTransferredThisFrame = 0; @@ -269,20 +277,22 @@ void KRTextureManager::balanceTextureMemory() // --------------- + /* // TODO - Would this be faster with int's for weights? - std::vector> sortedTextures; + std::vector > sortedTextures; for(auto itr=m_activeTextures_streamer.begin(); itr != m_activeTextures_streamer.end(); itr++) { KRTexture *texture = *itr; float priority = texture->getStreamPriority(); sortedTextures.push_back(std::pair(priority, texture)); } + */ - std::sort(sortedTextures.begin(), sortedTextures.end(), std::greater>()); + std::sort(m_activeTextures_streamer.begin(), m_activeTextures_streamer.end(), std::greater>()); long memoryRemaining = getContext().KRENGINE_TARGET_TEXTURE_MEM_MAX; - long memoryRemainingThisFrame = KRMIN(getContext().KRENGINE_TARGET_TEXTURE_MEM_MAX - getMemUsed(), getContext().KRENGINE_TARGET_TEXTURE_MEM_MAX); + long memoryRemainingThisFrame = getContext().KRENGINE_MAX_TEXTURE_MEM - getMemUsed(); - for(auto itr=sortedTextures.begin(); itr != sortedTextures.end(); itr++) { + for(auto itr=m_activeTextures_streamer.begin(); itr != m_activeTextures_streamer.end(); itr++) { KRTexture *texture = (*itr).second; int min_mip_level = KRMAX(getContext().KRENGINE_MIN_TEXTURE_DIM, texture->getMinMipMap()); long minLodMem = texture->getMemRequiredForSize(min_mip_level); @@ -299,7 +309,7 @@ void KRTextureManager::balanceTextureMemory() auto mip_itr = mipPercents.begin(); long memoryRemainingThisMip = 0; - for(auto itr=sortedTextures.begin(); itr != sortedTextures.end(); itr++) { + for(auto itr=m_activeTextures_streamer.begin(); itr != m_activeTextures_streamer.end(); itr++) { if(memoryRemainingThisMip <= 0) { if(mip_itr == mipPercents.end()) { break; @@ -327,91 +337,11 @@ void KRTextureManager::balanceTextureMemory() } - - // --------------- - /* - - // Determine the additional amount of memory required in order to resize all active textures to the maximum size - long wantedTextureMem = 0; - for(std::set::iterator itr=m_activeTextures_streamer.begin(); itr != m_activeTextures_streamer.end(); itr++) { - KRTexture *activeTexture = *itr; - - wantedTextureMem = activeTexture->getMemRequiredForSize(getContext().KRENGINE_MAX_TEXTURE_DIM) - activeTexture->getMemSize(); - } - - // Determine how much memory we need to free up - long memoryDeficit = wantedTextureMem - (getContext().KRENGINE_TARGET_TEXTURE_MEM_MAX - getMemUsed()); - - - // Determine how many mip map levels we need to strip off of inactive textures to free the memory we need - long maxDimInactive = getContext().KRENGINE_MAX_TEXTURE_DIM; - long potentialMemorySaving = 0; - while(potentialMemorySaving < memoryDeficit && maxDimInactive > getContext().KRENGINE_MIN_TEXTURE_DIM) { - maxDimInactive = maxDimInactive >> 1; - potentialMemorySaving = 0; - - for(std::set::iterator itr=m_poolTextures_streamer.begin(); itr != m_poolTextures_streamer.end(); itr++) { - KRTexture *poolTexture = *itr; - long potentialMemoryDelta = poolTexture->getMemRequiredForSize(maxDimInactive) - poolTexture->getMemSize(); - if(potentialMemoryDelta < 0) { - potentialMemorySaving += -potentialMemoryDelta; - } - } - } - - // Strip off mipmap levels of inactive textures to free up memory - long inactive_texture_mem_used_target = 0; - for(std::set::iterator itr=m_poolTextures_streamer.begin(); itr != m_poolTextures_streamer.end(); itr++) { - KRTexture *poolTexture = *itr; - long mem_required = poolTexture->getMemRequiredForSize(maxDimInactive); - long potentialMemoryDelta = mem_required - poolTexture->getMemSize(); - if(potentialMemoryDelta < 0) { - if(mem_required * 2 + getMemUsed() < KRContext::KRENGINE_MAX_TEXTURE_MEM) { - long mem_free; - m_pContext->getMemoryStats(mem_free); - if(mem_required * 2 < mem_free - 10000000) { - poolTexture->resize(maxDimInactive); - } - } - inactive_texture_mem_used_target += mem_required; - } else { - inactive_texture_mem_used_target += poolTexture->getMemSize(); - } - } - - // Determine the maximum mipmap level for the active textures we can achieve with the memory that is available - long memory_available = 0; - long maxDimActive = getContext().KRENGINE_MAX_TEXTURE_DIM; - while(memory_available <= 0 && maxDimActive >= getContext().KRENGINE_MIN_TEXTURE_DIM) { - memory_available = getContext().KRENGINE_TARGET_TEXTURE_MEM_MAX - inactive_texture_mem_used_target; - for(std::set::iterator itr=m_activeTextures_streamer.begin(); itr != m_activeTextures_streamer.end() && memory_available > 0; itr++) { - KRTexture *activeTexture = *itr; - memory_available -= activeTexture->getMemRequiredForSize(maxDimActive); - } - - if(memory_available <= 0) { - maxDimActive = maxDimActive >> 1; // Try the next smaller mipmap size - } - } - - // Resize active textures to balance the memory usage and mipmap levels - for(std::set::iterator itr=m_activeTextures_streamer.begin(); itr != m_activeTextures_streamer.end() && memory_available > 0; itr++) { - KRTexture *activeTexture = *itr; - long mem_required = activeTexture->getMemRequiredForSize(maxDimActive); - if(mem_required * 2 + getMemUsed() < KRContext::KRENGINE_MAX_TEXTURE_MEM) { - long mem_free; - m_pContext->getMemoryStats(mem_free); - if(mem_required * 2 < mem_free - 10000000) { - activeTexture->resize(maxDimActive); - } - } - } - */ } void KRTextureManager::rotateBuffers() { - const long KRENGINE_TEXTURE_EXPIRY_FRAMES = 120; + const long KRENGINE_TEXTURE_EXPIRY_FRAMES = 10; // ----====---- Expire textures that haven't been used in a long time ----====---- std::set expiredTextures; diff --git a/KREngine/kraken/KRTextureManager.h b/KREngine/kraken/KRTextureManager.h index 46ab085..d158ed4 100644 --- a/KREngine/kraken/KRTextureManager.h +++ b/KREngine/kraken/KRTextureManager.h @@ -95,8 +95,8 @@ private: std::set m_activeTextures; - std::set m_activeTextures_streamer; - std::set m_activeTextures_streamer_copy; + std::vector > m_activeTextures_streamer; + std::vector > m_activeTextures_streamer_copy; std::atomic m_textureMemUsed; diff --git a/KREngine/kraken/KRTextureTGA.cpp b/KREngine/kraken/KRTextureTGA.cpp index ca75b8e..b8c97fb 100644 --- a/KREngine/kraken/KRTextureTGA.cpp +++ b/KREngine/kraken/KRTextureTGA.cpp @@ -347,7 +347,7 @@ KRTexture *KRTextureTGA::compress(bool premultiply_alpha) assert(lod_width == 1); GLDEBUG(glBindTexture(GL_TEXTURE_2D, 0)); - getContext().getTextureManager()->selectTexture(0, NULL); + getContext().getTextureManager()->selectTexture(0, NULL, 0.0f, KRTexture::TEXTURE_USAGE_NONE); GLDEBUG(glDeleteTextures(1, &compressed_handle)); KRTextureKTX *new_texture = new KRTextureKTX(getContext(), getName(), internal_format, base_internal_format, width, height, blocks); diff --git a/KREngine/kraken/KRViewport.cpp b/KREngine/kraken/KRViewport.cpp index 9feab68..4a98772 100644 --- a/KREngine/kraken/KRViewport.cpp +++ b/KREngine/kraken/KRViewport.cpp @@ -176,6 +176,17 @@ float KRViewport::coverage(const KRAABB &b) const if(!visible(b)) { return 0.0f; // Culled out by view frustrum } else { + KRVector3 nearest_point = b.nearestPoint(getCameraPosition()); + float distance = (nearest_point - getCameraPosition()).magnitude(); + + KRVector3 v = KRMat4::DotWDiv(m_matProjection, getCameraPosition() + getCameraDirection() * distance); + + float screen_depth = distance / 1000.0f; + + return KRCLAMP(1.0f - screen_depth, 0.01f, 1.0f); + + /* + KRVector2 screen_min; KRVector2 screen_max; // Loop through all corners and transform them to screen space @@ -199,6 +210,7 @@ float KRViewport::coverage(const KRAABB &b) const float c = (screen_max.x - screen_min.x) * (screen_max.y - screen_min.y); return KRCLAMP(c, 0.01f, 1.0f); + */ } } diff --git a/KREngine/kraken_standard_assets_ios/Shaders/ObjectShader.fsh b/KREngine/kraken_standard_assets_ios/Shaders/ObjectShader.fsh index 625f602..62edf5a 100644 --- a/KREngine/kraken_standard_assets_ios/Shaders/ObjectShader.fsh +++ b/KREngine/kraken_standard_assets_ios/Shaders/ObjectShader.fsh @@ -372,9 +372,9 @@ void main() mediump vec3 reflectionVec = mat3(model_matrix) * (incidenceVec - 2.0 * dot(world_space_normal, incidenceVec) * world_space_normal); #endif #if HAS_REFLECTION_MAP == 1 - gl_FragColor += vec4(material_reflection, 0.0) * texture2D(reflectionTexture, reflection_uv) * textureCube(reflectionCubeTexture, reflectionVec); + gl_FragColor += vec4(material_reflection, 0.0) * texture2D(reflectionTexture, reflection_uv) * vec4(textureCube(reflectionCubeTexture, reflectionVec).rgb, 1.0); #else - gl_FragColor += vec4(material_reflection, 0.0) * textureCube(reflectionCubeTexture, reflectionVec); + gl_FragColor += vec4(material_reflection, 0.0) * vec4(textureCube(reflectionCubeTexture, reflectionVec).rgb, 1.0); #endif #endif diff --git a/KREngine/kraken_standard_assets_osx/Shaders/ObjectShader_osx.fsh b/KREngine/kraken_standard_assets_osx/Shaders/ObjectShader_osx.fsh index 7160dbb..6c5fd23 100644 --- a/KREngine/kraken_standard_assets_osx/Shaders/ObjectShader_osx.fsh +++ b/KREngine/kraken_standard_assets_osx/Shaders/ObjectShader_osx.fsh @@ -372,9 +372,9 @@ void main() mediump vec3 reflectionVec = mat3(model_matrix) * (incidenceVec - 2.0 * dot(world_space_normal, incidenceVec) * world_space_normal); #endif #if HAS_REFLECTION_MAP == 1 - gl_FragColor += vec4(material_reflection, 0.0) * texture2D(reflectionTexture, reflection_uv) * textureCube(reflectionCubeTexture, reflectionVec); + gl_FragColor += vec4(material_reflection, 0.0) * texture2D(reflectionTexture, reflection_uv) * vec4(textureCube(reflectionCubeTexture, reflectionVec).rgb, 1.0); #else - gl_FragColor += vec4(material_reflection, 0.0) * textureCube(reflectionCubeTexture, reflectionVec); + gl_FragColor += vec4(material_reflection, 0.0) * vec4(textureCube(reflectionCubeTexture, reflectionVec).rgb, 1.0); #endif #endif From a54ac1e87089ba021689df5baf20202f139abe86 Mon Sep 17 00:00:00 2001 From: Kearwood Gilbert Date: Sun, 13 Apr 2014 01:02:31 -0700 Subject: [PATCH 69/84] New streaming algorithm in progress Corrected reflections --HG-- branch : nfb --- KREngine/kraken/KRTextureManager.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/KREngine/kraken/KRTextureManager.cpp b/KREngine/kraken/KRTextureManager.cpp index 3d4f489..c6458e9 100644 --- a/KREngine/kraken/KRTextureManager.cpp +++ b/KREngine/kraken/KRTextureManager.cpp @@ -314,7 +314,7 @@ void KRTextureManager::balanceTextureMemory() if(mip_itr == mipPercents.end()) { break; } else { - memoryRemainingThisMip = memoryRemaining * (*mip_itr) / 100; + memoryRemainingThisMip = memoryRemaining / 100L * (long)(*mip_itr); mip_drop++; mip_itr++; } From c51552838bf97d1eac04f3267733f7774611e932 Mon Sep 17 00:00:00 2001 From: Kearwood Gilbert Date: Sun, 13 Apr 2014 01:53:16 -0700 Subject: [PATCH 70/84] New streaming algorithm in progress --HG-- branch : nfb --- KREngine/kraken/KRLODSet.cpp | 2 +- KREngine/kraken/KRTexture.cpp | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/KREngine/kraken/KRLODSet.cpp b/KREngine/kraken/KRLODSet.cpp index d056e30..b855815 100644 --- a/KREngine/kraken/KRLODSet.cpp +++ b/KREngine/kraken/KRLODSet.cpp @@ -55,7 +55,7 @@ void KRLODSet::updateLODVisibility(const KRViewport &viewport) } else if(m_activeLODGroup == NULL) { m_activeLODGroup = new_active_lod_group; } else if(new_active_lod_group != m_activeLODGroup) { - if(true || new_active_lod_group->getStreamLevel(true, viewport) >= kraken_stream_level::STREAM_LEVEL_IN_LQ) { // FINDME, HACK! Disabled due to performance issues. + if(/*true || */new_active_lod_group->getStreamLevel(true, viewport) >= kraken_stream_level::STREAM_LEVEL_IN_LQ) { // FINDME, HACK! Disabled due to performance issues. // fprintf(stderr, "LOD %s -> %s\n", m_activeLODGroup->getName().c_str(), new_active_lod_group->getName().c_str()); m_activeLODGroup = new_active_lod_group; } else { diff --git a/KREngine/kraken/KRTexture.cpp b/KREngine/kraken/KRTexture.cpp index cd57fd9..0d9fa1d 100644 --- a/KREngine/kraken/KRTexture.cpp +++ b/KREngine/kraken/KRTexture.cpp @@ -62,7 +62,7 @@ long KRTexture::getReferencedMemSize() { void KRTexture::resize(int max_dim) { - if(!m_handle_lock.test_and_set()) + while(m_handle_lock.test_and_set()); // Spin lock { if(m_iHandle == m_iNewHandle) { if(max_dim == 0) { @@ -197,6 +197,7 @@ bool KRTexture::canStreamOut() const { void KRTexture::_swapHandles() { + //while(m_handle_lock.test_and_set()); // Spin lock if(!m_handle_lock.test_and_set()) { if(m_iHandle != m_iNewHandle) { if(m_iHandle != 0) { From 462d0ec6e76f28b7180ba44f99f21624976ec515 Mon Sep 17 00:00:00 2001 From: Kearwood Gilbert Date: Sun, 13 Apr 2014 19:35:23 -0700 Subject: [PATCH 71/84] CPU Performance Optimization when texture cubes are in use --HG-- branch : nfb --- KREngine/kraken/KRTextureCube.cpp | 28 ++++++++++++---------------- KREngine/kraken/KRTextureCube.h | 4 ++++ 2 files changed, 16 insertions(+), 16 deletions(-) diff --git a/KREngine/kraken/KRTextureCube.cpp b/KREngine/kraken/KRTextureCube.cpp index 3021265..992fe79 100644 --- a/KREngine/kraken/KRTextureCube.cpp +++ b/KREngine/kraken/KRTextureCube.cpp @@ -40,11 +40,12 @@ KRTextureCube::KRTextureCube(KRContext &context, std::string name) : KRTexture(c m_min_lod_max_dim = 64; for(int i=0; i<6; i++) { + m_textures[i] = NULL; std::string faceName = getName() + SUFFIXES[i]; - KRTexture2D *faceTexture = (KRTexture2D *)getContext().getTextureManager()->getTexture(faceName); - if(faceTexture) { - if(faceTexture->getMaxMipMap() < m_max_lod_max_dim) m_max_lod_max_dim = faceTexture->getMaxMipMap(); - if(faceTexture->getMinMipMap() > m_min_lod_max_dim) m_min_lod_max_dim = faceTexture->getMinMipMap(); + m_textures[i] = (KRTexture2D *)getContext().getTextureManager()->getTexture(faceName); + if(m_textures[i]) { + if(m_textures[i]->getMaxMipMap() < m_max_lod_max_dim) m_max_lod_max_dim = m_textures[i]->getMaxMipMap(); + if(m_textures[i]->getMinMipMap() > m_min_lod_max_dim) m_min_lod_max_dim = m_textures[i]->getMinMipMap(); } } } @@ -79,10 +80,9 @@ bool KRTextureCube::createGLTexture(int lod_max_dim) for(int i=0; i<6; i++) { std::string faceName = getName() + SUFFIXES[i]; - KRTexture2D *faceTexture = (KRTexture2D *)getContext().getTextureManager()->getTexture(faceName); - if(faceTexture) { - if(faceTexture->hasMipmaps()) bMipMaps = true; - faceTexture->uploadTexture(TARGETS[i], lod_max_dim, m_current_lod_max_dim, prev_lod_max_dim); + if(m_textures[i]) { + if(m_textures[i]->hasMipmaps()) bMipMaps = true; + m_textures[i]->uploadTexture(TARGETS[i], lod_max_dim, m_current_lod_max_dim, prev_lod_max_dim); } } @@ -105,10 +105,8 @@ long KRTextureCube::getMemRequiredForSize(int max_dim) long memoryRequired = 0; for(int i=0; i<6; i++) { - std::string faceName = getName() + SUFFIXES[i]; - KRTexture2D *faceTexture = (KRTexture2D *)getContext().getTextureManager()->getTexture(faceName); - if(faceTexture) { - memoryRequired += faceTexture->getMemRequiredForSize(target_dim); + if(m_textures[i]) { + memoryRequired += m_textures[i]->getMemRequiredForSize(target_dim); } } return memoryRequired; @@ -119,10 +117,8 @@ void KRTextureCube::resetPoolExpiry(float lodCoverage, texture_usage_t textureUs { KRTexture::resetPoolExpiry(lodCoverage, textureUsage); for(int i=0; i<6; i++) { - std::string faceName = getName() + SUFFIXES[i]; - KRTexture2D *faceTexture = (KRTexture2D *)getContext().getTextureManager()->getTexture(faceName); - if(faceTexture) { - faceTexture->resetPoolExpiry(lodCoverage, textureUsage); // Ensure that side of cube maps do not expire from the texture pool prematurely, as they are referenced indirectly + if(m_textures[i]) { + m_textures[i]->resetPoolExpiry(lodCoverage, textureUsage); // Ensure that side of cube maps do not expire from the texture pool prematurely, as they are referenced indirectly } } } diff --git a/KREngine/kraken/KRTextureCube.h b/KREngine/kraken/KRTextureCube.h index 8a0edc1..95c29fd 100644 --- a/KREngine/kraken/KRTextureCube.h +++ b/KREngine/kraken/KRTextureCube.h @@ -34,6 +34,8 @@ #include "KRTexture.h" +class KRTexture2D; + class KRTextureCube : public KRTexture { public: KRTextureCube(KRContext &context, std::string name); @@ -66,6 +68,8 @@ private: "_positive_z", "_negative_z" }; + + KRTexture2D *m_textures[6]; }; From ee35c5540e1602f38bf3ae57027fbe3ec8c2702c Mon Sep 17 00:00:00 2001 From: Kearwood Gilbert Date: Mon, 14 Apr 2014 00:47:29 -0700 Subject: [PATCH 72/84] Corrected bug that allowed incomplete cube map textures to attempt to load, resulting in gl errors Implemented procedural loading of textures with new streamer algorithm Fixed thread safety issues in streamer (double-buffered old level tracking variables) Texture streamer now only processes once per frame --HG-- branch : nfb --- KREngine/kraken/KRLODSet.cpp | 2 +- KREngine/kraken/KRTexture.cpp | 11 ++++- KREngine/kraken/KRTexture.h | 2 + KREngine/kraken/KRTexture2D.cpp | 16 ++----- KREngine/kraken/KRTexture2D.h | 2 +- KREngine/kraken/KRTextureCube.cpp | 18 +++---- KREngine/kraken/KRTextureCube.h | 2 +- KREngine/kraken/KRTextureKTX.cpp | 6 +-- KREngine/kraken/KRTextureKTX.h | 2 +- KREngine/kraken/KRTextureManager.cpp | 70 ++++++++++++++++++++++------ KREngine/kraken/KRTexturePVR.cpp | 6 +-- KREngine/kraken/KRTexturePVR.h | 2 +- KREngine/kraken/KRTextureTGA.cpp | 4 +- KREngine/kraken/KRTextureTGA.h | 2 +- 14 files changed, 92 insertions(+), 53 deletions(-) diff --git a/KREngine/kraken/KRLODSet.cpp b/KREngine/kraken/KRLODSet.cpp index b855815..d056e30 100644 --- a/KREngine/kraken/KRLODSet.cpp +++ b/KREngine/kraken/KRLODSet.cpp @@ -55,7 +55,7 @@ void KRLODSet::updateLODVisibility(const KRViewport &viewport) } else if(m_activeLODGroup == NULL) { m_activeLODGroup = new_active_lod_group; } else if(new_active_lod_group != m_activeLODGroup) { - if(/*true || */new_active_lod_group->getStreamLevel(true, viewport) >= kraken_stream_level::STREAM_LEVEL_IN_LQ) { // FINDME, HACK! Disabled due to performance issues. + if(true || new_active_lod_group->getStreamLevel(true, viewport) >= kraken_stream_level::STREAM_LEVEL_IN_LQ) { // FINDME, HACK! Disabled due to performance issues. // fprintf(stderr, "LOD %s -> %s\n", m_activeLODGroup->getName().c_str(), new_active_lod_group->getName().c_str()); m_activeLODGroup = new_active_lod_group; } else { diff --git a/KREngine/kraken/KRTexture.cpp b/KREngine/kraken/KRTexture.cpp index 0d9fa1d..5ce80d6 100644 --- a/KREngine/kraken/KRTexture.cpp +++ b/KREngine/kraken/KRTexture.cpp @@ -14,6 +14,8 @@ KRTexture::KRTexture(KRContext &context, std::string name) : KRResource(context, name) { + m_current_lod_max_dim = 0; + m_new_lod_max_dim = 0; m_iHandle = 0; m_iNewHandle = 0; m_textureMemUsed = 0; @@ -45,6 +47,8 @@ void KRTexture::releaseHandles() { m_iHandle = 0; m_textureMemUsed = 0; } + m_current_lod_max_dim = 0; + m_new_lod_max_dim = 0; m_handle_lock.clear(); @@ -71,7 +75,7 @@ void KRTexture::resize(int max_dim) int target_dim = max_dim; if(target_dim < m_min_lod_max_dim) target_dim = m_min_lod_max_dim; - if(m_current_lod_max_dim != target_dim || (m_iHandle == 0 && m_iNewHandle == 0)) { + if(m_new_lod_max_dim != target_dim || (m_iHandle == 0 && m_iNewHandle == 0)) { assert(m_newTextureMemUsed == 0); m_newTextureMemUsed = getMemRequiredForSize(target_dim); @@ -175,6 +179,10 @@ int KRTexture::getCurrentLodMaxDim() { return m_current_lod_max_dim; } +int KRTexture::getNewLodMaxDim() { + return m_new_lod_max_dim; +} + int KRTexture::getMaxMipMap() { return m_max_lod_max_dim; } @@ -207,6 +215,7 @@ void KRTexture::_swapHandles() m_textureMemUsed = (long)m_newTextureMemUsed; m_newTextureMemUsed = 0; m_iHandle = m_iNewHandle; + m_current_lod_max_dim = m_new_lod_max_dim; } m_handle_lock.clear(); } diff --git a/KREngine/kraken/KRTexture.h b/KREngine/kraken/KRTexture.h index cc741fb..fa338b5 100644 --- a/KREngine/kraken/KRTexture.h +++ b/KREngine/kraken/KRTexture.h @@ -80,6 +80,7 @@ public: virtual KRTexture *compress(bool premultiply_alpha = false); int getCurrentLodMaxDim(); + int getNewLodMaxDim(); // For use by streamer only int getMaxMipMap(); int getMinMipMap(); bool hasMipmaps(); @@ -100,6 +101,7 @@ protected: std::atomic_flag m_handle_lock; int m_current_lod_max_dim; + int m_new_lod_max_dim; uint32_t m_max_lod_max_dim; uint32_t m_min_lod_max_dim; diff --git a/KREngine/kraken/KRTexture2D.cpp b/KREngine/kraken/KRTexture2D.cpp index 11918d9..25a7a7e 100644 --- a/KREngine/kraken/KRTexture2D.cpp +++ b/KREngine/kraken/KRTexture2D.cpp @@ -34,7 +34,6 @@ #include "KRTextureManager.h" KRTexture2D::KRTexture2D(KRContext &context, KRDataBlock *data, std::string name) : KRTexture(context, name) { - m_current_lod_max_dim = 0; m_pData = data; } @@ -48,16 +47,11 @@ bool KRTexture2D::createGLTexture(int lod_max_dim) { } bool success = true; - int prev_lod_max_dim = 0; -#if GL_APPLE_copy_texture_levels && GL_EXT_texture_storage - - if(m_iHandle != 0) { - prev_lod_max_dim = m_current_lod_max_dim; - } -#endif + int prev_lod_max_dim = m_new_lod_max_dim; + m_iNewHandle = 0; - m_current_lod_max_dim = 0; + m_new_lod_max_dim = 0; GLDEBUG(glGenTextures(1, &m_iNewHandle)); if(m_iNewHandle == 0) { @@ -71,10 +65,10 @@ bool KRTexture2D::createGLTexture(int lod_max_dim) { GLDEBUG(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR)); } - if(!uploadTexture(GL_TEXTURE_2D, lod_max_dim, m_current_lod_max_dim, prev_lod_max_dim)) { + if(!uploadTexture(GL_TEXTURE_2D, lod_max_dim, m_new_lod_max_dim)) { GLDEBUG(glDeleteTextures(1, &m_iNewHandle)); m_iNewHandle = m_iHandle; - m_current_lod_max_dim = prev_lod_max_dim; + m_new_lod_max_dim = prev_lod_max_dim; success = false; } } diff --git a/KREngine/kraken/KRTexture2D.h b/KREngine/kraken/KRTexture2D.h index 921370b..8b32bdd 100644 --- a/KREngine/kraken/KRTexture2D.h +++ b/KREngine/kraken/KRTexture2D.h @@ -46,7 +46,7 @@ public: virtual bool save(const std::string& path); virtual bool save(KRDataBlock &data); - virtual bool uploadTexture(GLenum target, int lod_max_dim, int ¤t_lod_max_dim, int prev_lod_max_dim, bool compress = false, bool premultiply_alpha = false) = 0; + virtual bool uploadTexture(GLenum target, int lod_max_dim, int ¤t_lod_max_dim, bool compress = false, bool premultiply_alpha = false) = 0; virtual void bind(GLuint texture_unit); protected: diff --git a/KREngine/kraken/KRTextureCube.cpp b/KREngine/kraken/KRTextureCube.cpp index 992fe79..57a7bef 100644 --- a/KREngine/kraken/KRTextureCube.cpp +++ b/KREngine/kraken/KRTextureCube.cpp @@ -46,6 +46,8 @@ KRTextureCube::KRTextureCube(KRContext &context, std::string name) : KRTexture(c if(m_textures[i]) { if(m_textures[i]->getMaxMipMap() < m_max_lod_max_dim) m_max_lod_max_dim = m_textures[i]->getMaxMipMap(); if(m_textures[i]->getMinMipMap() > m_min_lod_max_dim) m_min_lod_max_dim = m_textures[i]->getMinMipMap(); + } else { + assert(false); } } } @@ -59,21 +61,12 @@ bool KRTextureCube::createGLTexture(int lod_max_dim) assert(m_iNewHandle == m_iHandle); // Only allow one resize per frame bool success = true; - int prev_lod_max_dim = 0; -#if GL_APPLE_copy_texture_levels && GL_EXT_texture_storage - - if(m_iHandle != 0) { - prev_lod_max_dim = m_current_lod_max_dim; - } - - -#endif m_iNewHandle = 0; GLDEBUG(glGenTextures(1, &m_iNewHandle)); assert(m_iNewHandle != 0); - m_current_lod_max_dim = 0; + m_new_lod_max_dim = 0; GLDEBUG(glBindTexture(GL_TEXTURE_CUBE_MAP, m_iNewHandle)); bool bMipMaps = false; @@ -82,7 +75,7 @@ bool KRTextureCube::createGLTexture(int lod_max_dim) std::string faceName = getName() + SUFFIXES[i]; if(m_textures[i]) { if(m_textures[i]->hasMipmaps()) bMipMaps = true; - m_textures[i]->uploadTexture(TARGETS[i], lod_max_dim, m_current_lod_max_dim, prev_lod_max_dim); + m_textures[i]->uploadTexture(TARGETS[i], lod_max_dim, m_new_lod_max_dim); } } @@ -112,7 +105,7 @@ long KRTextureCube::getMemRequiredForSize(int max_dim) return memoryRequired; } - +/* void KRTextureCube::resetPoolExpiry(float lodCoverage, texture_usage_t textureUsage) { KRTexture::resetPoolExpiry(lodCoverage, textureUsage); @@ -122,6 +115,7 @@ void KRTextureCube::resetPoolExpiry(float lodCoverage, texture_usage_t textureUs } } } +*/ void KRTextureCube::bind(GLuint texture_unit) { diff --git a/KREngine/kraken/KRTextureCube.h b/KREngine/kraken/KRTextureCube.h index 95c29fd..c643e2b 100644 --- a/KREngine/kraken/KRTextureCube.h +++ b/KREngine/kraken/KRTextureCube.h @@ -46,7 +46,7 @@ public: virtual void bind(GLuint texture_unit); virtual long getMemRequiredForSize(int max_dim); - virtual void resetPoolExpiry(float lodCoverage, texture_usage_t textureUsage); +// virtual void resetPoolExpiry(float lodCoverage, texture_usage_t textureUsage); private: virtual bool createGLTexture(int lod_max_dim); diff --git a/KREngine/kraken/KRTextureKTX.cpp b/KREngine/kraken/KRTextureKTX.cpp index 863c624..a1bb5bd 100644 --- a/KREngine/kraken/KRTextureKTX.cpp +++ b/KREngine/kraken/KRTextureKTX.cpp @@ -153,7 +153,7 @@ long KRTextureKTX::getMemRequiredForSize(int max_dim) return memoryRequired; } -bool KRTextureKTX::uploadTexture(GLenum target, int lod_max_dim, int ¤t_lod_max_dim, int prev_lod_max_dim, bool compress, bool premultiply_alpha) +bool KRTextureKTX::uploadTexture(GLenum target, int lod_max_dim, int ¤t_lod_max_dim, bool compress, bool premultiply_alpha) { int target_dim = lod_max_dim; if(target_dim < m_min_lod_max_dim) target_dim = m_min_lod_max_dim; @@ -220,7 +220,7 @@ bool KRTextureKTX::uploadTexture(GLenum target, int lod_max_dim, int ¤t_lo current_lod_max_dim = height; } #if GL_APPLE_copy_texture_levels && GL_EXT_texture_storage - if(target == GL_TEXTURE_2D && width <= prev_lod_max_dim && height <= prev_lod_max_dim) { + if(target == GL_TEXTURE_2D && width <= m_current_lod_max_dim && height <= m_current_lod_max_dim) { //GLDEBUG(glCompressedTexImage2D(target, i, (GLenum)m_header.glInternalFormat, width, height, 0, block.length, NULL)); // Allocate, but don't copy // GLDEBUG(glTexImage2D(target, 0, GL_RGBA, width, height, 0, GL_BGRA, GL_UNSIGNED_BYTE, NULL)); GLDEBUG(glCopyTextureLevelsAPPLE(m_iNewHandle, m_iHandle, source_level, 1)); @@ -253,7 +253,7 @@ bool KRTextureKTX::uploadTexture(GLenum target, int lod_max_dim, int ¤t_lo destination_level++; } - if(width <= prev_lod_max_dim && height <= prev_lod_max_dim) { + if(width <= m_current_lod_max_dim && height <= m_current_lod_max_dim) { source_level++; } diff --git a/KREngine/kraken/KRTextureKTX.h b/KREngine/kraken/KRTextureKTX.h index f9f5d30..4f1279e 100644 --- a/KREngine/kraken/KRTextureKTX.h +++ b/KREngine/kraken/KRTextureKTX.h @@ -19,7 +19,7 @@ public: virtual ~KRTextureKTX(); virtual std::string getExtension(); - bool uploadTexture(GLenum target, int lod_max_dim, int ¤t_lod_max_dim, int prev_lod_max_dim, bool compress = false, bool premultiply_alpha = false); + bool uploadTexture(GLenum target, int lod_max_dim, int ¤t_lod_max_dim, bool compress = false, bool premultiply_alpha = false); virtual long getMemRequiredForSize(int max_dim); diff --git a/KREngine/kraken/KRTextureManager.cpp b/KREngine/kraken/KRTextureManager.cpp index c6458e9..6222772 100644 --- a/KREngine/kraken/KRTextureManager.cpp +++ b/KREngine/kraken/KRTextureManager.cpp @@ -145,10 +145,42 @@ KRTexture *KRTextureManager::getTextureCube(const char *szName) { unordered_map::iterator itr = m_textures.find(lowerName); if(itr == m_textures.end()) { - KRTextureCube *pTexture = new KRTextureCube(getContext(), lowerName); - m_textures[lowerName] = pTexture; - return pTexture; + // Defer resolving the texture cube until its referenced textures are ready + const GLenum TARGETS[6] = { + GL_TEXTURE_CUBE_MAP_POSITIVE_X, + GL_TEXTURE_CUBE_MAP_NEGATIVE_X, + GL_TEXTURE_CUBE_MAP_POSITIVE_Y, + GL_TEXTURE_CUBE_MAP_NEGATIVE_Y, + GL_TEXTURE_CUBE_MAP_POSITIVE_Z, + GL_TEXTURE_CUBE_MAP_NEGATIVE_Z + }; + + const char *SUFFIXES[6] = { + "_positive_x", + "_negative_x", + "_positive_y", + "_negative_y", + "_positive_z", + "_negative_z" + }; + bool found_all = true; + for(int i=0; i<6; i++) { + std::string faceName = lowerName + SUFFIXES[i]; + KRTexture *faceTexture = dynamic_cast(getContext().getTextureManager()->getTexture(faceName)); + if(faceTexture == NULL) { + found_all = false; + } + } + + if(found_all) { + KRTextureCube *pTexture = new KRTextureCube(getContext(), lowerName); + + m_textures[lowerName] = pTexture; + return pTexture; + } else { + return NULL; + } } else { return (*itr).second; } @@ -251,7 +283,7 @@ void KRTextureManager::doStreaming() { // TODO - Implement proper double-buffering to reduce copy operations m_streamerFenceMutex.lock(); - m_activeTextures_streamer = m_activeTextures_streamer_copy; + m_activeTextures_streamer = std::move(m_activeTextures_streamer_copy); m_streamerFenceMutex.unlock(); balanceTextureMemory(); @@ -277,15 +309,8 @@ void KRTextureManager::balanceTextureMemory() // --------------- - /* - // TODO - Would this be faster with int's for weights? - std::vector > sortedTextures; - for(auto itr=m_activeTextures_streamer.begin(); itr != m_activeTextures_streamer.end(); itr++) { - KRTexture *texture = *itr; - float priority = texture->getStreamPriority(); - sortedTextures.push_back(std::pair(priority, texture)); - } - */ + //long MAX_STREAM_TIME = 66; + //long startTime = getContext().getAbsoluteTimeMilliseconds(); std::sort(m_activeTextures_streamer.begin(), m_activeTextures_streamer.end(), std::greater>()); @@ -298,12 +323,14 @@ void KRTextureManager::balanceTextureMemory() long minLodMem = texture->getMemRequiredForSize(min_mip_level); memoryRemaining -= minLodMem; - if(memoryRemainingThisFrame > minLodMem && texture->getCurrentLodMaxDim() < min_mip_level) { + if(memoryRemainingThisFrame > minLodMem && texture->getNewLodMaxDim() < min_mip_level) { memoryRemainingThisFrame -= minLodMem; texture->resize(min_mip_level); } } + //long minMipTime = getContext().getAbsoluteTimeMilliseconds() - startTime; + std::vector mipPercents = {75, 75, 50, 50, 50}; int mip_drop = -1; auto mip_itr = mipPercents.begin(); @@ -329,13 +356,26 @@ void KRTextureManager::balanceTextureMemory() memoryRemainingThisMip -= additionalMemRequired; memoryRemaining -= additionalMemRequired; if(memoryRemainingThisMip > 0 && memoryRemainingThisFrame > targetMem) { - if(texture->getCurrentLodMaxDim() != target_mip_level) { + int current_mip_level = texture->getNewLodMaxDim(); + if(current_mip_level == (target_mip_level >> 1) || target_mip_level < current_mip_level) { memoryRemainingThisFrame -= targetMem; texture->resize(target_mip_level); + } else if(current_mip_level == (target_mip_level >> 2)) { + memoryRemainingThisFrame -= texture->getMemRequiredForSize(target_mip_level >> 1); + texture->resize(target_mip_level >> 1); + } else if(current_mip_level < (target_mip_level >> 2)) { + memoryRemainingThisFrame -= texture->getMemRequiredForSize(target_mip_level >> 2); + texture->resize(target_mip_level >> 2); } } + + //if(getContext().getAbsoluteTimeMilliseconds() - startTime > MAX_STREAM_TIME) { + // return; // Bail out early if we spend too long + //} } + //long streamerTime = getContext().getAbsoluteTimeMilliseconds() - startTime; + //fprintf(stderr, "%i / %i\n", (int)minMipTime, (int)streamerTime); } diff --git a/KREngine/kraken/KRTexturePVR.cpp b/KREngine/kraken/KRTexturePVR.cpp index 46f3ef4..24de4b4 100644 --- a/KREngine/kraken/KRTexturePVR.cpp +++ b/KREngine/kraken/KRTexturePVR.cpp @@ -173,7 +173,7 @@ long KRTexturePVR::getMemRequiredForSize(int max_dim) return memoryRequired; } -bool KRTexturePVR::uploadTexture(GLenum target, int lod_max_dim, int ¤t_lod_max_dim, int prev_lod_max_dim, bool compress, bool premultiply_alpha) +bool KRTexturePVR::uploadTexture(GLenum target, int lod_max_dim, int ¤t_lod_max_dim, bool compress, bool premultiply_alpha) { int target_dim = lod_max_dim; if(target_dim < m_min_lod_max_dim) target_dim = m_min_lod_max_dim; @@ -240,7 +240,7 @@ bool KRTexturePVR::uploadTexture(GLenum target, int lod_max_dim, int ¤t_lo current_lod_max_dim = height; } #if GL_APPLE_copy_texture_levels && GL_EXT_texture_storage - if(target == GL_TEXTURE_2D && width <= prev_lod_max_dim && height <= prev_lod_max_dim) { + if(target == GL_TEXTURE_2D && width <= m_current_lod_max_dim && height <= m_current_lod_max_dim) { //GLDEBUG(glCompressedTexImage2D(target, i, m_internalFormat, width, height, 0, block.length, NULL)); // Allocate, but don't copy // GLDEBUG(glTexImage2D(target, 0, GL_RGBA, width, height, 0, GL_BGRA, GL_UNSIGNED_BYTE, NULL)); GLDEBUG(glCopyTextureLevelsAPPLE(m_iNewHandle, m_iHandle, source_level, 1)); @@ -273,7 +273,7 @@ bool KRTexturePVR::uploadTexture(GLenum target, int lod_max_dim, int ¤t_lo destination_level++; } - if(width <= prev_lod_max_dim && height <= prev_lod_max_dim) { + if(width <= m_current_lod_max_dim && height <= m_current_lod_max_dim) { source_level++; } diff --git a/KREngine/kraken/KRTexturePVR.h b/KREngine/kraken/KRTexturePVR.h index 3d973d4..0914960 100644 --- a/KREngine/kraken/KRTexturePVR.h +++ b/KREngine/kraken/KRTexturePVR.h @@ -18,7 +18,7 @@ public: virtual ~KRTexturePVR(); virtual std::string getExtension(); - bool uploadTexture(GLenum target, int lod_max_dim, int ¤t_lod_max_dim, int prev_lod_max_dim, bool compress = false, bool premultiply_alpha = false); + bool uploadTexture(GLenum target, int lod_max_dim, int ¤t_lod_max_dim, bool compress = false, bool premultiply_alpha = false); virtual long getMemRequiredForSize(int max_dim); diff --git a/KREngine/kraken/KRTextureTGA.cpp b/KREngine/kraken/KRTextureTGA.cpp index b8c97fb..e7f1d3c 100644 --- a/KREngine/kraken/KRTextureTGA.cpp +++ b/KREngine/kraken/KRTextureTGA.cpp @@ -71,7 +71,7 @@ KRTextureTGA::~KRTextureTGA() } -bool KRTextureTGA::uploadTexture(GLenum target, int lod_max_dim, int ¤t_lod_max_dim, int prev_lod_max_dim, bool compress, bool premultiply_alpha) +bool KRTextureTGA::uploadTexture(GLenum target, int lod_max_dim, int ¤t_lod_max_dim, bool compress, bool premultiply_alpha) { m_pData->lock(); TGA_HEADER *pHeader = (TGA_HEADER *)m_pData->getStart(); @@ -298,7 +298,7 @@ KRTexture *KRTextureTGA::compress(bool premultiply_alpha) GLDEBUG(glBindTexture(GL_TEXTURE_2D, compressed_handle)); int current_max_dim = 0; - if(!uploadTexture(GL_TEXTURE_2D, m_max_lod_max_dim, current_max_dim, 0, true, premultiply_alpha)) { + if(!uploadTexture(GL_TEXTURE_2D, m_max_lod_max_dim, current_max_dim, true, premultiply_alpha)) { assert(false); // Failed to upload the texture } GLDEBUG(glGenerateMipmap(GL_TEXTURE_2D)); diff --git a/KREngine/kraken/KRTextureTGA.h b/KREngine/kraken/KRTextureTGA.h index abca2a8..18e992a 100644 --- a/KREngine/kraken/KRTextureTGA.h +++ b/KREngine/kraken/KRTextureTGA.h @@ -18,7 +18,7 @@ public: virtual ~KRTextureTGA(); virtual std::string getExtension(); - bool uploadTexture(GLenum target, int lod_max_dim, int ¤t_lod_max_dim, int prev_lod_max_dim, bool compress = false, bool premultiply_alpha = false); + bool uploadTexture(GLenum target, int lod_max_dim, int ¤t_lod_max_dim, bool compress = false, bool premultiply_alpha = false); #if !TARGET_OS_IPHONE virtual KRTexture *compress(bool premultiply_alpha = false); From 8112a0362f924c5ea0f22ff7dd3225babd018dc8 Mon Sep 17 00:00:00 2001 From: Kearwood Gilbert Date: Mon, 14 Apr 2014 02:27:16 -0700 Subject: [PATCH 73/84] Streaming optimizations and black-pop elimination in progress --HG-- branch : nfb --- KREngine/kraken/KRLODSet.cpp | 15 +++++++++++++-- KREngine/kraken/KRTexture.cpp | 4 +++- 2 files changed, 16 insertions(+), 3 deletions(-) diff --git a/KREngine/kraken/KRLODSet.cpp b/KREngine/kraken/KRLODSet.cpp index d056e30..cc37fe0 100644 --- a/KREngine/kraken/KRLODSet.cpp +++ b/KREngine/kraken/KRLODSet.cpp @@ -106,8 +106,19 @@ void KRLODSet::showLOD() kraken_stream_level KRLODSet::getStreamLevel(bool prime, const KRViewport &viewport) { - if(m_activeLODGroup) { - return m_activeLODGroup->getStreamLevel(prime, viewport); + KRLODGroup *new_active_lod_group = NULL; + + // Upgrade and downgrade LOD groups as needed + for(std::set::iterator itr=m_childNodes.begin(); itr != m_childNodes.end(); ++itr) { + KRLODGroup *lod_group = dynamic_cast(*itr); + assert(lod_group != NULL); + if(lod_group->getLODVisibility(viewport)) { + new_active_lod_group = lod_group; + } + } + + if(new_active_lod_group) { + return new_active_lod_group->getStreamLevel(prime, viewport); } else { return kraken_stream_level::STREAM_LEVEL_IN_HQ; } diff --git a/KREngine/kraken/KRTexture.cpp b/KREngine/kraken/KRTexture.cpp index 5ce80d6..062d52f 100644 --- a/KREngine/kraken/KRTexture.cpp +++ b/KREngine/kraken/KRTexture.cpp @@ -124,8 +124,10 @@ kraken_stream_level KRTexture::getStreamLevel(bool prime, float lodCoverage, KRT return kraken_stream_level::STREAM_LEVEL_OUT; } else if(m_current_lod_max_dim == KRMIN(getContext().KRENGINE_MAX_TEXTURE_DIM, m_max_lod_max_dim)) { return kraken_stream_level::STREAM_LEVEL_IN_HQ; - } else { + } else if(m_current_lod_max_dim >= KRMAX(getContext().KRENGINE_MIN_TEXTURE_DIM, m_min_lod_max_dim)) { return kraken_stream_level::STREAM_LEVEL_IN_LQ; + } else { + return kraken_stream_level::STREAM_LEVEL_OUT; } } From 3a3d5bd64ca05809cb06bd84539b6452887727c6 Mon Sep 17 00:00:00 2001 From: Kearwood Gilbert Date: Mon, 14 Apr 2014 21:48:09 -0700 Subject: [PATCH 74/84] Fixed "multi-context texture usage without flush" errors, and now restricting the texture handle swapping to the streamer fence period. --HG-- branch : nfb --- KREngine/kraken/KRTextureManager.cpp | 63 ++++++++++++++++------------ KREngine/kraken/KRTextureManager.h | 1 + 2 files changed, 38 insertions(+), 26 deletions(-) diff --git a/KREngine/kraken/KRTextureManager.cpp b/KREngine/kraken/KRTextureManager.cpp index 6222772..ab131be 100644 --- a/KREngine/kraken/KRTextureManager.cpp +++ b/KREngine/kraken/KRTextureManager.cpp @@ -47,7 +47,7 @@ KRTextureManager::KRTextureManager(KRContext &context) : KRContextObject(context m_boundTextures[iTexture] = NULL; } m_memoryTransferredThisFrame = 0; - + m_streamerComplete = true; _clearGLState(); } @@ -248,20 +248,36 @@ void KRTextureManager::startFrame(float deltaTime) { m_streamer.startStreamer(); _clearGLState(); - for(std::set::iterator itr=m_activeTextures.begin(); itr != m_activeTextures.end(); itr++) { - KRTexture *activeTexture = *itr; - activeTexture->_swapHandles(); - } // TODO - Implement proper double-buffering to reduce copy operations m_streamerFenceMutex.lock(); - - m_activeTextures_streamer_copy.clear(); - for(auto itr=m_activeTextures.begin(); itr != m_activeTextures.end(); itr++) { - KRTexture *texture = *itr; - float priority = texture->getStreamPriority(); - m_activeTextures_streamer_copy.push_back(std::pair(priority, texture)); + if(m_streamerComplete) { + assert(m_activeTextures_streamer_copy.size() == 0); // The streamer should have emptied this if it really did complete + + const long KRENGINE_TEXTURE_EXPIRY_FRAMES = 10; + + + std::set expiredTextures; + for(std::set::iterator itr=m_activeTextures.begin(); itr != m_activeTextures.end(); itr++) { + KRTexture *activeTexture = *itr; + activeTexture->_swapHandles(); + if(activeTexture->getLastFrameUsed() + KRENGINE_TEXTURE_EXPIRY_FRAMES < getContext().getCurrentFrame()) { + // Expire textures that haven't been used in a long time + expiredTextures.insert(activeTexture); + activeTexture->releaseHandles(); + } else { + float priority = activeTexture->getStreamPriority(); + m_activeTextures_streamer_copy.push_back(std::pair(priority, activeTexture)); + } + } + for(std::set::iterator itr=expiredTextures.begin(); itr != expiredTextures.end(); itr++) { + m_activeTextures.erase(*itr); + } + + if(m_activeTextures_streamer_copy.size() > 0) { + m_streamerComplete = false; + } } m_streamerFenceMutex.unlock(); @@ -286,7 +302,13 @@ void KRTextureManager::doStreaming() m_activeTextures_streamer = std::move(m_activeTextures_streamer_copy); m_streamerFenceMutex.unlock(); - balanceTextureMemory(); + if(m_activeTextures_streamer.size() > 0) { + balanceTextureMemory(); + + m_streamerFenceMutex.lock(); + m_streamerComplete = true; + m_streamerFenceMutex.unlock(); + } } void KRTextureManager::balanceTextureMemory() @@ -374,6 +396,8 @@ void KRTextureManager::balanceTextureMemory() //} } + glFlush(); + //long streamerTime = getContext().getAbsoluteTimeMilliseconds() - startTime; //fprintf(stderr, "%i / %i\n", (int)minMipTime, (int)streamerTime); @@ -381,20 +405,7 @@ void KRTextureManager::balanceTextureMemory() void KRTextureManager::rotateBuffers() { - const long KRENGINE_TEXTURE_EXPIRY_FRAMES = 10; - - // ----====---- Expire textures that haven't been used in a long time ----====---- - std::set expiredTextures; - for(std::set::iterator itr=m_activeTextures.begin(); itr != m_activeTextures.end(); itr++) { - KRTexture *activeTexture = *itr; - if(activeTexture->getLastFrameUsed() + KRENGINE_TEXTURE_EXPIRY_FRAMES < getContext().getCurrentFrame()) { - expiredTextures.insert(activeTexture); - activeTexture->releaseHandles(); - } - } - for(std::set::iterator itr=expiredTextures.begin(); itr != expiredTextures.end(); itr++) { - m_activeTextures.erase(*itr); - } + } long KRTextureManager::getMemoryTransferedThisFrame() diff --git a/KREngine/kraken/KRTextureManager.h b/KREngine/kraken/KRTextureManager.h index d158ed4..02360d0 100644 --- a/KREngine/kraken/KRTextureManager.h +++ b/KREngine/kraken/KRTextureManager.h @@ -97,6 +97,7 @@ private: std::vector > m_activeTextures_streamer; std::vector > m_activeTextures_streamer_copy; + bool m_streamerComplete; std::atomic m_textureMemUsed; From 7194d39f7ddf29c1889ca9907db0d2c25865cd65 Mon Sep 17 00:00:00 2001 From: "admin8onf@admin8onfs-pro.nfbonf.nfb.ca" Date: Thu, 17 Apr 2014 13:22:04 -0700 Subject: [PATCH 75/84] Added tag Release 1.0.2 for changeset be35d6215978 --HG-- branch : nfb --- .hgtags | 1 + 1 file changed, 1 insertion(+) create mode 100644 .hgtags diff --git a/.hgtags b/.hgtags new file mode 100644 index 0000000..07cc9f0 --- /dev/null +++ b/.hgtags @@ -0,0 +1 @@ +be35d62159788e03c335f8b7f3ddbde8030a3ccf Release 1.0.2 From e9c17df90095c7061975b668ff675e377675bf1e Mon Sep 17 00:00:00 2001 From: Kearwood Gilbert Date: Wed, 23 Apr 2014 01:43:00 -0700 Subject: [PATCH 76/84] Implemented texture pre-streaming for lod swaps --HG-- branch : nfb --- KREngine/kraken/KRAmbientZone.cpp | 1 + KREngine/kraken/KRAudioSource.cpp | 2 + KREngine/kraken/KRBone.cpp | 1 + KREngine/kraken/KRCamera.cpp | 9 ++- KREngine/kraken/KRCollider.cpp | 1 + KREngine/kraken/KRContext.cpp | 1 + KREngine/kraken/KRContext.h | 1 + KREngine/kraken/KRDirectionalLight.cpp | 2 + KREngine/kraken/KREngine.mm | 2 + KREngine/kraken/KRLODGroup.cpp | 25 ++++-- KREngine/kraken/KRLODGroup.h | 2 +- KREngine/kraken/KRLODSet.cpp | 80 ++++++++----------- KREngine/kraken/KRLODSet.h | 11 +-- KREngine/kraken/KRLight.cpp | 2 + KREngine/kraken/KRMaterial.cpp | 44 ++++++++-- KREngine/kraken/KRMaterial.h | 3 +- KREngine/kraken/KRMesh.cpp | 15 +++- KREngine/kraken/KRMesh.h | 3 +- KREngine/kraken/KRModel.cpp | 39 ++++++--- KREngine/kraken/KRModel.h | 4 +- KREngine/kraken/KRNode.cpp | 45 +++++------ KREngine/kraken/KRNode.h | 20 +++-- KREngine/kraken/KRParticleSystemNewtonian.cpp | 1 + KREngine/kraken/KRPointLight.cpp | 1 + KREngine/kraken/KRReverbZone.cpp | 1 + KREngine/kraken/KRScene.cpp | 6 +- KREngine/kraken/KRSprite.cpp | 2 + KREngine/kraken/KRTexture.cpp | 13 +-- KREngine/kraken/KRTexture.h | 4 +- 29 files changed, 202 insertions(+), 139 deletions(-) diff --git a/KREngine/kraken/KRAmbientZone.cpp b/KREngine/kraken/KRAmbientZone.cpp index 927caa5..a76142f 100644 --- a/KREngine/kraken/KRAmbientZone.cpp +++ b/KREngine/kraken/KRAmbientZone.cpp @@ -87,6 +87,7 @@ void KRAmbientZone::setZone(const std::string &zone) void KRAmbientZone::render(KRCamera *pCamera, std::vector &point_lights, std::vector &directional_lights, std::vector&spot_lights, const KRViewport &viewport, KRNode::RenderPass renderPass) { + if(m_lod_visible <= LOD_VISIBILITY_PRESTREAM) return; KRNode::render(pCamera, point_lights, directional_lights, spot_lights, viewport, renderPass); diff --git a/KREngine/kraken/KRAudioSource.cpp b/KREngine/kraken/KRAudioSource.cpp index dd1668a..bce86a7 100644 --- a/KREngine/kraken/KRAudioSource.cpp +++ b/KREngine/kraken/KRAudioSource.cpp @@ -200,6 +200,8 @@ void KRAudioSource::queueBuffer() void KRAudioSource::render(KRCamera *pCamera, std::vector &point_lights, std::vector &directional_lights, std::vector&spot_lights, const KRViewport &viewport, KRNode::RenderPass renderPass) { + if(m_lod_visible <= LOD_VISIBILITY_PRESTREAM) return; + KRNode::render(pCamera, point_lights, directional_lights, spot_lights, viewport, renderPass); bool bVisualize = false; diff --git a/KREngine/kraken/KRBone.cpp b/KREngine/kraken/KRBone.cpp index 18b4a87..974985b 100644 --- a/KREngine/kraken/KRBone.cpp +++ b/KREngine/kraken/KRBone.cpp @@ -41,6 +41,7 @@ KRAABB KRBone::getBounds() { void KRBone::render(KRCamera *pCamera, std::vector &point_lights, std::vector &directional_lights, std::vector&spot_lights, const KRViewport &viewport, KRNode::RenderPass renderPass) { + if(m_lod_visible <= LOD_VISIBILITY_PRESTREAM) return; KRNode::render(pCamera, point_lights, directional_lights, spot_lights, viewport, renderPass); diff --git a/KREngine/kraken/KRCamera.cpp b/KREngine/kraken/KRCamera.cpp index b111908..915a873 100644 --- a/KREngine/kraken/KRCamera.cpp +++ b/KREngine/kraken/KRCamera.cpp @@ -115,11 +115,14 @@ void KRCamera::renderFrame(float deltaTime, GLint renderBufferWidth, GLint rende scene.updateOctree(m_viewport); + // ----====---- Pre-stream resources ----====---- + scene.render(this, m_viewport.getVisibleBounds(), m_viewport, KRNode::RENDER_PASS_PRESTREAM, true); + // ----====---- Generate Shadowmaps for Lights ----====---- if(settings.m_cShadowBuffers > 0) { GL_PUSH_GROUP_MARKER("Generate Shadowmaps"); - scene.render(this, m_viewport.getVisibleBounds(), m_viewport, KRNode::RENDER_PASS_GENERATE_SHADOWMAPS, settings.bEnableDeferredLighting); + scene.render(this, m_viewport.getVisibleBounds(), m_viewport, KRNode::RENDER_PASS_GENERATE_SHADOWMAPS, false /*settings.bEnableDeferredLighting*/); GLDEBUG(glViewport(0, 0, m_viewport.getSize().x, m_viewport.getSize().y)); GL_POP_GROUP_MARKER; } @@ -158,7 +161,7 @@ void KRCamera::renderFrame(float deltaTime, GLint renderBufferWidth, GLint rende GLDEBUG(glDisable(GL_BLEND)); // Render the geometry - scene.render(this, m_viewport.getVisibleBounds(), m_viewport, KRNode::RENDER_PASS_DEFERRED_GBUFFER, true); + scene.render(this, m_viewport.getVisibleBounds(), m_viewport, KRNode::RENDER_PASS_DEFERRED_GBUFFER, false); GL_POP_GROUP_MARKER; @@ -280,7 +283,7 @@ void KRCamera::renderFrame(float deltaTime, GLint renderBufferWidth, GLint rende // Render the geometry - scene.render(this, m_viewport.getVisibleBounds(), m_viewport, KRNode::RENDER_PASS_FORWARD_OPAQUE, true); + scene.render(this, m_viewport.getVisibleBounds(), m_viewport, KRNode::RENDER_PASS_FORWARD_OPAQUE, false); GL_POP_GROUP_MARKER; } diff --git a/KREngine/kraken/KRCollider.cpp b/KREngine/kraken/KRCollider.cpp index df0f9a8..5b98e43 100644 --- a/KREngine/kraken/KRCollider.cpp +++ b/KREngine/kraken/KRCollider.cpp @@ -187,6 +187,7 @@ void KRCollider::setAudioOcclusion(float audio_occlusion) void KRCollider::render(KRCamera *pCamera, std::vector &point_lights, std::vector &directional_lights, std::vector&spot_lights, const KRViewport &viewport, KRNode::RenderPass renderPass) { + if(m_lod_visible <= LOD_VISIBILITY_PRESTREAM) return; KRNode::render(pCamera, point_lights, directional_lights, spot_lights, viewport, renderPass); diff --git a/KREngine/kraken/KRContext.cpp b/KREngine/kraken/KRContext.cpp index 60a4dff..ca54490 100644 --- a/KREngine/kraken/KRContext.cpp +++ b/KREngine/kraken/KRContext.cpp @@ -20,6 +20,7 @@ int KRContext::KRENGINE_TARGET_TEXTURE_MEM_MAX; int KRContext::KRENGINE_MAX_TEXTURE_DIM; int KRContext::KRENGINE_MIN_TEXTURE_DIM; int KRContext::KRENGINE_MAX_TEXTURE_THROUGHPUT; +int KRContext::KRENGINE_PRESTREAM_DISTANCE; const char *KRContext::extension_names[KRENGINE_NUM_EXTENSIONS] = { "GL_EXT_texture_storage" diff --git a/KREngine/kraken/KRContext.h b/KREngine/kraken/KRContext.h index 06402e2..d311214 100644 --- a/KREngine/kraken/KRContext.h +++ b/KREngine/kraken/KRContext.h @@ -32,6 +32,7 @@ public: static int KRENGINE_MAX_TEXTURE_DIM; static int KRENGINE_MIN_TEXTURE_DIM; static int KRENGINE_MAX_TEXTURE_THROUGHPUT; + static int KRENGINE_PRESTREAM_DISTANCE; KRContext(); diff --git a/KREngine/kraken/KRDirectionalLight.cpp b/KREngine/kraken/KRDirectionalLight.cpp index 532e6d0..31245b8 100644 --- a/KREngine/kraken/KRDirectionalLight.cpp +++ b/KREngine/kraken/KRDirectionalLight.cpp @@ -89,6 +89,8 @@ int KRDirectionalLight::configureShadowBufferViewports(const KRViewport &viewpor void KRDirectionalLight::render(KRCamera *pCamera, std::vector &point_lights, std::vector &directional_lights, std::vector&spot_lights, const KRViewport &viewport, KRNode::RenderPass renderPass) { + if(m_lod_visible <= LOD_VISIBILITY_PRESTREAM) return; + KRLight::render(pCamera, point_lights, directional_lights, spot_lights, viewport, renderPass); if(renderPass == KRNode::RENDER_PASS_DEFERRED_LIGHTS) { diff --git a/KREngine/kraken/KREngine.mm b/KREngine/kraken/KREngine.mm index a45570c..d5949cc 100644 --- a/KREngine/kraken/KREngine.mm +++ b/KREngine/kraken/KREngine.mm @@ -94,6 +94,7 @@ void kraken::set_debug_text(const std::string &print_text) KRContext::KRENGINE_MAX_TEXTURE_HANDLES = 10000; KRContext::KRENGINE_MAX_TEXTURE_DIM = 2048; KRContext::KRENGINE_MIN_TEXTURE_DIM = 64; + KRContext::KRENGINE_PRESTREAM_DISTANCE = 1000.0f; KRContext::KRENGINE_MAX_TEXTURE_THROUGHPUT = 4000000; @@ -139,6 +140,7 @@ void kraken::set_debug_text(const std::string &print_text) KRContext::KRENGINE_MAX_TEXTURE_DIM = 8192; KRContext::KRENGINE_MIN_TEXTURE_DIM = 64; KRContext::KRENGINE_MAX_TEXTURE_THROUGHPUT = 128000000; + KRContext::KRENGINE_PRESTREAM_DISTANCE = 1000.0f; #endif _context = NULL; diff --git a/KREngine/kraken/KRLODGroup.cpp b/KREngine/kraken/KRLODGroup.cpp index 907427a..e3cbe52 100644 --- a/KREngine/kraken/KRLODGroup.cpp +++ b/KREngine/kraken/KRLODGroup.cpp @@ -102,15 +102,17 @@ void KRLODGroup::setReference(const KRAABB &reference) m_reference = reference; } -bool KRLODGroup::getLODVisibility(const KRViewport &viewport) +KRNode::LodVisibility KRLODGroup::calcLODVisibility(const KRViewport &viewport) { if(m_min_distance == 0 && m_max_distance == 0) { - return true; + return LOD_VISIBILITY_VISIBLE; } else { float lod_bias = viewport.getLODBias(); lod_bias = pow(2.0f, -lod_bias); - float sqr_distance; // Compare using squared distances as sqrt is expensive + // Compare using squared distances as sqrt is expensive + float sqr_distance; + float sqr_prestream_distance; KRVector3 world_camera_position = viewport.getCameraPosition(); KRVector3 local_camera_position = worldToLocal(world_camera_position); @@ -119,13 +121,24 @@ bool KRLODGroup::getLODVisibility(const KRViewport &viewport) if(m_use_world_units) { KRVector3 world_reference_point = localToWorld(local_reference_point); sqr_distance = (world_camera_position - world_reference_point).sqrMagnitude() * (lod_bias * lod_bias); + sqr_prestream_distance = getContext().KRENGINE_PRESTREAM_DISTANCE * getContext().KRENGINE_PRESTREAM_DISTANCE; } else { sqr_distance = (local_camera_position - local_reference_point).sqrMagnitude() * (lod_bias * lod_bias); + + KRVector3 world_reference_point = localToWorld(local_reference_point); + sqr_prestream_distance = worldToLocal(KRVector3::Normalize(world_reference_point - world_camera_position) * getContext().KRENGINE_PRESTREAM_DISTANCE).sqrMagnitude(); // TODO, FINDME - Optimize with precalc? + } - float sqr_min_distance = m_min_distance * m_min_distance; - float sqr_max_distance = m_max_distance * m_max_distance; - return ((sqr_distance >= sqr_min_distance || m_min_distance == 0) && (sqr_distance < sqr_max_distance || m_max_distance == 0)); + float sqr_min_visible_distance = m_min_distance * m_min_distance; + float sqr_max_visible_distance = m_max_distance * m_max_distance; + if((sqr_distance >= sqr_min_visible_distance || m_min_distance == 0) && (sqr_distance < sqr_max_visible_distance || m_max_distance == 0)) { + return LOD_VISIBILITY_VISIBLE; + } else if((sqr_distance >= sqr_min_visible_distance - sqr_prestream_distance || m_min_distance == 0) && (sqr_distance < sqr_max_visible_distance + sqr_prestream_distance || m_max_distance == 0)) { + return LOD_VISIBILITY_PRESTREAM; + } else { + return LOD_VISIBILITY_HIDDEN; + } } } diff --git a/KREngine/kraken/KRLODGroup.h b/KREngine/kraken/KRLODGroup.h index e86a9c8..f16929a 100644 --- a/KREngine/kraken/KRLODGroup.h +++ b/KREngine/kraken/KRLODGroup.h @@ -30,7 +30,7 @@ public: void setUseWorldUnits(bool use_world_units); bool getUseWorldUnits() const; - bool getLODVisibility(const KRViewport &viewport); + LodVisibility calcLODVisibility(const KRViewport &viewport); private: float m_min_distance; diff --git a/KREngine/kraken/KRLODSet.cpp b/KREngine/kraken/KRLODSet.cpp index cc37fe0..bb1f144 100644 --- a/KREngine/kraken/KRLODSet.cpp +++ b/KREngine/kraken/KRLODSet.cpp @@ -12,7 +12,7 @@ KRLODSet::KRLODSet(KRScene &scene, std::string name) : KRNode(scene, name) { - m_activeLODGroup = NULL; + } KRLODSet::~KRLODSet() @@ -38,37 +38,38 @@ void KRLODSet::loadXML(tinyxml2::XMLElement *e) void KRLODSet::updateLODVisibility(const KRViewport &viewport) { - if(m_lod_visible) { + if(m_lod_visible >= LOD_VISIBILITY_PRESTREAM) { KRLODGroup *new_active_lod_group = NULL; // Upgrade and downgrade LOD groups as needed for(std::set::iterator itr=m_childNodes.begin(); itr != m_childNodes.end(); ++itr) { KRLODGroup *lod_group = dynamic_cast(*itr); assert(lod_group != NULL); - if(lod_group->getLODVisibility(viewport)) { + LodVisibility group_lod_visibility = KRMIN(lod_group->calcLODVisibility(viewport), m_lod_visible); + if(group_lod_visibility == LOD_VISIBILITY_VISIBLE) { new_active_lod_group = lod_group; } + lod_group->setLODVisibility(group_lod_visibility); } + /* + // FINDME, TODO, HACK - Disabled streamer delayed LOD load due to performance issues: + bool streamer_ready = false; if(new_active_lod_group == NULL) { - m_activeLODGroup = NULL; - } else if(m_activeLODGroup == NULL) { - m_activeLODGroup = new_active_lod_group; - } else if(new_active_lod_group != m_activeLODGroup) { - if(true || new_active_lod_group->getStreamLevel(true, viewport) >= kraken_stream_level::STREAM_LEVEL_IN_LQ) { // FINDME, HACK! Disabled due to performance issues. - // fprintf(stderr, "LOD %s -> %s\n", m_activeLODGroup->getName().c_str(), new_active_lod_group->getName().c_str()); - m_activeLODGroup = new_active_lod_group; - } else { - // fprintf(stderr, "LOD %s -> %s - waiting for streaming...\n", m_activeLODGroup->getName().c_str(), new_active_lod_group->getName().c_str()); - } + streamer_ready = true; + } else if(new_active_lod_group->getStreamLevel(viewport) >= kraken_stream_level::STREAM_LEVEL_IN_LQ) { + streamer_ready = true; } - - for(std::set::iterator itr=m_childNodes.begin(); itr != m_childNodes.end(); ++itr) { - KRNode *child = *itr; - if(child == m_activeLODGroup) { - child->showLOD(); - } else { - child->hideLOD(); + */ + bool streamer_ready = true; + + if(streamer_ready) { + // Upgrade and downgrade LOD groups as needed + for(std::set::iterator itr=m_childNodes.begin(); itr != m_childNodes.end(); ++itr) { + KRLODGroup *lod_group = dynamic_cast(*itr); + assert(lod_group != NULL); + LodVisibility group_lod_visibility = KRMIN(lod_group->calcLODVisibility(viewport), m_lod_visible); + lod_group->setLODVisibility(group_lod_visibility); } } @@ -76,35 +77,20 @@ void KRLODSet::updateLODVisibility(const KRViewport &viewport) } } -KRLODGroup *KRLODSet::getActiveLODGroup() const +void KRLODSet::setLODVisibility(KRNode::LodVisibility lod_visibility) { - return m_activeLODGroup; -} - -void KRLODSet::childDeleted(KRNode *child_node) -{ - KRNode::childDeleted(child_node); - if(m_activeLODGroup == child_node) { - m_activeLODGroup = NULL; + if(lod_visibility == LOD_VISIBILITY_HIDDEN) { + KRNode::setLODVisibility(lod_visibility); + } else if(m_lod_visible != lod_visibility) { + // Don't automatically recurse into our children, as only one of those will be activated, by updateLODVisibility + if(m_lod_visible == LOD_VISIBILITY_HIDDEN && lod_visibility >= LOD_VISIBILITY_PRESTREAM) { + getScene().notify_sceneGraphCreate(this); + } + m_lod_visible = lod_visibility; } } -void KRLODSet::hideLOD() -{ - KRNode::hideLOD(); - m_activeLODGroup = NULL; // Ensure that the streamer will wait for the group to load in next time -} - -void KRLODSet::showLOD() -{ - // Don't automatically recurse into our children, as only one of those will be activated, by updateLODVisibility - if(!m_lod_visible) { - getScene().notify_sceneGraphCreate(this); - m_lod_visible = true; - } -} - -kraken_stream_level KRLODSet::getStreamLevel(bool prime, const KRViewport &viewport) +kraken_stream_level KRLODSet::getStreamLevel(const KRViewport &viewport) { KRLODGroup *new_active_lod_group = NULL; @@ -112,13 +98,13 @@ kraken_stream_level KRLODSet::getStreamLevel(bool prime, const KRViewport &viewp for(std::set::iterator itr=m_childNodes.begin(); itr != m_childNodes.end(); ++itr) { KRLODGroup *lod_group = dynamic_cast(*itr); assert(lod_group != NULL); - if(lod_group->getLODVisibility(viewport)) { + if(lod_group->calcLODVisibility(viewport) == LOD_VISIBILITY_VISIBLE) { new_active_lod_group = lod_group; } } if(new_active_lod_group) { - return new_active_lod_group->getStreamLevel(prime, viewport); + return new_active_lod_group->getStreamLevel(viewport); } else { return kraken_stream_level::STREAM_LEVEL_IN_HQ; } diff --git a/KREngine/kraken/KRLODSet.h b/KREngine/kraken/KRLODSet.h index 3e3c433..2e8034b 100644 --- a/KREngine/kraken/KRLODSet.h +++ b/KREngine/kraken/KRLODSet.h @@ -24,16 +24,9 @@ public: virtual void updateLODVisibility(const KRViewport &viewport); - KRLODGroup *getActiveLODGroup() const; + virtual void setLODVisibility(LodVisibility lod_visibility); - virtual void showLOD(); - virtual void hideLOD(); - virtual void childDeleted(KRNode *child_node); - - virtual kraken_stream_level getStreamLevel(bool prime, const KRViewport &viewport); - -private: - KRLODGroup *m_activeLODGroup; + virtual kraken_stream_level getStreamLevel(const KRViewport &viewport); }; diff --git a/KREngine/kraken/KRLight.cpp b/KREngine/kraken/KRLight.cpp index 6f44976..4d531ca 100644 --- a/KREngine/kraken/KRLight.cpp +++ b/KREngine/kraken/KRLight.cpp @@ -176,6 +176,8 @@ float KRLight::getDecayStart() { void KRLight::render(KRCamera *pCamera, std::vector &point_lights, std::vector &directional_lights, std::vector&spot_lights, const KRViewport &viewport, KRNode::RenderPass renderPass) { + if(m_lod_visible <= LOD_VISIBILITY_PRESTREAM) return; + KRNode::render(pCamera, point_lights, directional_lights, spot_lights, viewport, renderPass); if(renderPass == KRNode::RENDER_PASS_GENERATE_SHADOWMAPS && (pCamera->settings.volumetric_environment_enable || pCamera->settings.dust_particle_enable || (pCamera->settings.m_cShadowBuffers > 0 && m_casts_shadow))) { diff --git a/KREngine/kraken/KRMaterial.cpp b/KREngine/kraken/KRMaterial.cpp index 9a33fd5..fa91451 100644 --- a/KREngine/kraken/KRMaterial.cpp +++ b/KREngine/kraken/KRMaterial.cpp @@ -217,34 +217,64 @@ bool KRMaterial::isTransparent() { return m_tr < 1.0 || m_alpha_mode == KRMATERIAL_ALPHA_MODE_BLENDONESIDE || m_alpha_mode == KRMATERIAL_ALPHA_MODE_BLENDTWOSIDE; } -kraken_stream_level KRMaterial::getStreamLevel(bool prime, float lodCoverage) +void KRMaterial::preStream(float lodCoverage) +{ + getTextures(); + + if(m_pAmbientMap) { + m_pAmbientMap->resetPoolExpiry(lodCoverage, KRTexture::TEXTURE_USAGE_AMBIENT_MAP); + } + + if(m_pDiffuseMap) { + m_pDiffuseMap->resetPoolExpiry(lodCoverage, KRTexture::TEXTURE_USAGE_DIFFUSE_MAP); + } + + if(m_pNormalMap) { + m_pNormalMap->resetPoolExpiry(lodCoverage, KRTexture::TEXTURE_USAGE_NORMAL_MAP); + } + + if(m_pSpecularMap) { + m_pSpecularMap->resetPoolExpiry(lodCoverage, KRTexture::TEXTURE_USAGE_SPECULAR_MAP); + } + + if(m_pReflectionMap) { + m_pReflectionMap->resetPoolExpiry(lodCoverage, KRTexture::TEXTURE_USAGE_REFLECTION_MAP); + } + + if(m_pReflectionCube) { + m_pReflectionCube->resetPoolExpiry(lodCoverage, KRTexture::TEXTURE_USAGE_REFECTION_CUBE); + } +} + + +kraken_stream_level KRMaterial::getStreamLevel() { kraken_stream_level stream_level = kraken_stream_level::STREAM_LEVEL_IN_HQ; getTextures(); if(m_pAmbientMap) { - stream_level = KRMIN(stream_level, m_pNormalMap->getStreamLevel(prime, lodCoverage, KRTexture::TEXTURE_USAGE_AMBIENT_MAP)); + stream_level = KRMIN(stream_level, m_pAmbientMap->getStreamLevel(KRTexture::TEXTURE_USAGE_AMBIENT_MAP)); } if(m_pDiffuseMap) { - stream_level = KRMIN(stream_level, m_pDiffuseMap->getStreamLevel(prime, lodCoverage, KRTexture::TEXTURE_USAGE_DIFFUSE_MAP)); + stream_level = KRMIN(stream_level, m_pDiffuseMap->getStreamLevel(KRTexture::TEXTURE_USAGE_DIFFUSE_MAP)); } if(m_pNormalMap) { - stream_level = KRMIN(stream_level, m_pNormalMap->getStreamLevel(prime, lodCoverage, KRTexture::TEXTURE_USAGE_NORMAL_MAP)); + stream_level = KRMIN(stream_level, m_pNormalMap->getStreamLevel(KRTexture::TEXTURE_USAGE_NORMAL_MAP)); } if(m_pSpecularMap) { - stream_level = KRMIN(stream_level, m_pSpecularMap->getStreamLevel(prime, lodCoverage, KRTexture::TEXTURE_USAGE_SPECULAR_MAP)); + stream_level = KRMIN(stream_level, m_pSpecularMap->getStreamLevel(KRTexture::TEXTURE_USAGE_SPECULAR_MAP)); } if(m_pReflectionMap) { - stream_level = KRMIN(stream_level, m_pReflectionMap->getStreamLevel(prime, lodCoverage, KRTexture::TEXTURE_USAGE_REFLECTION_MAP)); + stream_level = KRMIN(stream_level, m_pReflectionMap->getStreamLevel(KRTexture::TEXTURE_USAGE_REFLECTION_MAP)); } if(m_pReflectionCube) { - stream_level = KRMIN(stream_level, m_pReflectionCube->getStreamLevel(prime, lodCoverage, KRTexture::TEXTURE_USAGE_REFECTION_CUBE)); + stream_level = KRMIN(stream_level, m_pReflectionCube->getStreamLevel(KRTexture::TEXTURE_USAGE_REFECTION_CUBE)); } return stream_level; diff --git a/KREngine/kraken/KRMaterial.h b/KREngine/kraken/KRMaterial.h index 31a09a5..d15a2a6 100644 --- a/KREngine/kraken/KRMaterial.h +++ b/KREngine/kraken/KRMaterial.h @@ -88,7 +88,8 @@ public: bool needsVertexTangents(); - kraken_stream_level getStreamLevel(bool prime, float lodCoverage); + kraken_stream_level getStreamLevel(); + void preStream(float lodCoverage); private: std::string m_name; diff --git a/KREngine/kraken/KRMesh.cpp b/KREngine/kraken/KRMesh.cpp index c180b3c..8358bde 100644 --- a/KREngine/kraken/KRMesh.cpp +++ b/KREngine/kraken/KRMesh.cpp @@ -186,21 +186,30 @@ void KRMesh::getMaterials() } } -kraken_stream_level KRMesh::getStreamLevel(bool prime, float lodCoverage) +void KRMesh::preStream(float lodCoverage) +{ + getSubmeshes(); + getMaterials(); + + for(std::set::iterator mat_itr = m_uniqueMaterials.begin(); mat_itr != m_uniqueMaterials.end(); mat_itr++) { + (*mat_itr)->preStream(lodCoverage); + } +} + +kraken_stream_level KRMesh::getStreamLevel() { kraken_stream_level stream_level = kraken_stream_level::STREAM_LEVEL_IN_HQ; getSubmeshes(); getMaterials(); for(std::set::iterator mat_itr = m_uniqueMaterials.begin(); mat_itr != m_uniqueMaterials.end(); mat_itr++) { - stream_level = KRMIN(stream_level, (*mat_itr)->getStreamLevel(prime, lodCoverage)); + stream_level = KRMIN(stream_level, (*mat_itr)->getStreamLevel()); } return stream_level; } void KRMesh::render(const std::string &object_name, KRCamera *pCamera, std::vector &point_lights, std::vector &directional_lights, std::vector&spot_lights, const KRViewport &viewport, const KRMat4 &matModel, KRTexture *pLightMap, KRNode::RenderPass renderPass, const std::vector &bones, const KRVector3 &rim_color, float rim_power, float lod_coverage) { - //fprintf(stderr, "Rendering model: %s\n", m_name.c_str()); if(renderPass != KRNode::RENDER_PASS_ADDITIVE_PARTICLES && renderPass != KRNode::RENDER_PASS_PARTICLE_OCCLUSION && renderPass != KRNode::RENDER_PASS_VOLUMETRIC_EFFECTS_ADDITIVE) { diff --git a/KREngine/kraken/KRMesh.h b/KREngine/kraken/KRMesh.h index c2ab5ea..4e426d7 100644 --- a/KREngine/kraken/KRMesh.h +++ b/KREngine/kraken/KRMesh.h @@ -67,7 +67,8 @@ public: KRMesh(KRContext &context, std::string name); virtual ~KRMesh(); - kraken_stream_level getStreamLevel(bool prime, float lodCoverage); + kraken_stream_level getStreamLevel(); + void preStream(float lodCoverage); bool hasTransparency(); diff --git a/KREngine/kraken/KRModel.cpp b/KREngine/kraken/KRModel.cpp index 731baea..58b1cdb 100644 --- a/KREngine/kraken/KRModel.cpp +++ b/KREngine/kraken/KRModel.cpp @@ -149,10 +149,15 @@ void KRModel::loadModel() { void KRModel::render(KRCamera *pCamera, std::vector &point_lights, std::vector &directional_lights, std::vector&spot_lights, const KRViewport &viewport, KRNode::RenderPass renderPass) { + if(m_lod_visible == LOD_VISIBILITY_PRESTREAM && renderPass == KRNode::RENDER_PASS_PRESTREAM) { + preStream(viewport); + } + + if(m_lod_visible <= LOD_VISIBILITY_PRESTREAM) return; KRNode::render(pCamera, point_lights, directional_lights, spot_lights, viewport, renderPass); - if(renderPass != KRNode::RENDER_PASS_DEFERRED_LIGHTS && renderPass != KRNode::RENDER_PASS_ADDITIVE_PARTICLES && renderPass != KRNode::RENDER_PASS_PARTICLE_OCCLUSION && renderPass != KRNode::RENDER_PASS_VOLUMETRIC_EFFECTS_ADDITIVE && renderPass != KRNode::RENDER_PASS_GENERATE_SHADOWMAPS) { + if(renderPass != KRNode::RENDER_PASS_DEFERRED_LIGHTS && renderPass != KRNode::RENDER_PASS_ADDITIVE_PARTICLES && renderPass != KRNode::RENDER_PASS_PARTICLE_OCCLUSION && renderPass != KRNode::RENDER_PASS_VOLUMETRIC_EFFECTS_ADDITIVE && renderPass != KRNode::RENDER_PASS_GENERATE_SHADOWMAPS && renderPass != KRNode::RENDER_PASS_PRESTREAM) { loadModel(); if(m_models.size() > 0) { @@ -205,19 +210,33 @@ void KRModel::render(KRCamera *pCamera, std::vector &point_light } } - -kraken_stream_level KRModel::getStreamLevel(bool prime, const KRViewport &viewport) +void KRModel::preStream(const KRViewport &viewport) { - kraken_stream_level stream_level = KRNode::getStreamLevel(prime, viewport); - loadModel(); - float lod_coverage = 0.0f; - if(prime) { - lod_coverage = viewport.coverage(getBounds()); // This is only used when prime is true - } + float lod_coverage = viewport.coverage(getBounds()); for(auto itr = m_models.begin(); itr != m_models.end(); itr++) { - stream_level = KRMIN(stream_level, (*itr)->getStreamLevel(prime, lod_coverage)); + (*itr)->preStream(lod_coverage); + } + + if(m_pLightMap == NULL && m_lightMap.size()) { + m_pLightMap = getContext().getTextureManager()->getTexture(m_lightMap); + } + + if(m_pLightMap) { + m_pLightMap->resetPoolExpiry(lod_coverage, KRTexture::TEXTURE_USAGE_LIGHT_MAP); + } +} + + +kraken_stream_level KRModel::getStreamLevel(const KRViewport &viewport) +{ + kraken_stream_level stream_level = KRNode::getStreamLevel(viewport); + + loadModel(); + + for(auto itr = m_models.begin(); itr != m_models.end(); itr++) { + stream_level = KRMIN(stream_level, (*itr)->getStreamLevel()); } return stream_level; diff --git a/KREngine/kraken/KRModel.h b/KREngine/kraken/KRModel.h index f771a4b..95e1ee3 100644 --- a/KREngine/kraken/KRModel.h +++ b/KREngine/kraken/KRModel.h @@ -69,9 +69,11 @@ public: void setLightMap(const std::string &name); std::string getLightMap(); - virtual kraken_stream_level getStreamLevel(bool prime, const KRViewport &viewport); + virtual kraken_stream_level getStreamLevel(const KRViewport &viewport); private: + void preStream(const KRViewport &viewport); + std::vector m_models; unordered_map > m_bones; // Outer std::map connects model to set of bones KRTexture *m_pLightMap; diff --git a/KREngine/kraken/KRNode.cpp b/KREngine/kraken/KRNode.cpp index 4f5b07f..90b35de 100644 --- a/KREngine/kraken/KRNode.cpp +++ b/KREngine/kraken/KRNode.cpp @@ -63,7 +63,7 @@ KRNode::KRNode(KRScene &scene, std::string name) : KRContextObject(scene.getCont m_modelMatrix = KRMat4(); m_bindPoseMatrix = KRMat4(); m_activePoseMatrix = KRMat4(); - m_lod_visible = false; + m_lod_visible = LOD_VISIBILITY_HIDDEN; m_scale_compensation = false; m_boundsValid = false; @@ -117,10 +117,7 @@ void KRNode::addChild(KRNode *child) { assert(child->m_parentNode == NULL); child->m_parentNode = this; m_childNodes.insert(child); - if(m_lod_visible) { - // Child node inherits LOD visibility status from parent - child->showLOD(); - } + child->setLODVisibility(m_lod_visible); // Child node inherits LOD visibility status from parent } tinyxml2::XMLElement *KRNode::saveXML(tinyxml2::XMLNode *parent) { @@ -456,6 +453,8 @@ KRNode *KRNode::LoadXML(KRScene &scene, tinyxml2::XMLElement *e) { void KRNode::render(KRCamera *pCamera, std::vector &point_lights, std::vector &directional_lights, std::vector&spot_lights, const KRViewport &viewport, RenderPass renderPass) { + if(m_lod_visible <= LOD_VISIBILITY_PRESTREAM) return; + m_lastRenderFrame = getContext().getCurrentFrame(); } @@ -887,37 +886,31 @@ void KRNode::addToOctreeNode(KROctreeNode *octree_node) void KRNode::updateLODVisibility(const KRViewport &viewport) { - if(m_lod_visible) { + if(m_lod_visible >= LOD_VISIBILITY_PRESTREAM) { for(std::set::iterator itr=m_childNodes.begin(); itr != m_childNodes.end(); ++itr) { (*itr)->updateLODVisibility(viewport); } } } -void KRNode::hideLOD() +void KRNode::setLODVisibility(KRNode::LodVisibility lod_visibility) { - if(m_lod_visible) { - m_lod_visible = false; - getScene().notify_sceneGraphDelete(this); + if(m_lod_visible != lod_visibility) { + if(m_lod_visible == LOD_VISIBILITY_HIDDEN && lod_visibility >= LOD_VISIBILITY_PRESTREAM) { + getScene().notify_sceneGraphCreate(this); + } else if(m_lod_visible >= LOD_VISIBILITY_PRESTREAM && lod_visibility == LOD_VISIBILITY_HIDDEN) { + getScene().notify_sceneGraphDelete(this); + } + + m_lod_visible = lod_visibility; + for(std::set::iterator itr=m_childNodes.begin(); itr != m_childNodes.end(); ++itr) { - (*itr)->hideLOD(); + (*itr)->setLODVisibility(lod_visibility); } } } -void KRNode::showLOD() -{ - if(!m_lod_visible) { - getScene().notify_sceneGraphCreate(this); - m_lod_visible = true; - for(std::set::iterator itr=m_childNodes.begin(); itr != m_childNodes.end(); ++itr) { - (*itr)->showLOD(); - } - } -} - - -bool KRNode::lodIsVisible() +KRNode::LodVisibility KRNode::getLODVisibility() { return m_lod_visible; } @@ -944,12 +937,12 @@ std::set &KRNode::getBehaviors() return m_behaviors; } -kraken_stream_level KRNode::getStreamLevel(bool prime, const KRViewport &viewport) +kraken_stream_level KRNode::getStreamLevel(const KRViewport &viewport) { kraken_stream_level stream_level = kraken_stream_level::STREAM_LEVEL_IN_HQ; for(std::set::iterator itr=m_childNodes.begin(); itr != m_childNodes.end(); ++itr) { - stream_level = KRMIN(stream_level, (*itr)->getStreamLevel(prime, viewport)); + stream_level = KRMIN(stream_level, (*itr)->getStreamLevel(viewport)); } return stream_level; diff --git a/KREngine/kraken/KRNode.h b/KREngine/kraken/KRNode.h index f542d1a..937f6bf 100644 --- a/KREngine/kraken/KRNode.h +++ b/KREngine/kraken/KRNode.h @@ -43,7 +43,14 @@ public: RENDER_PASS_ADDITIVE_PARTICLES, RENDER_PASS_VOLUMETRIC_EFFECTS_ADDITIVE, RENDER_PASS_GENERATE_SHADOWMAPS, - RENDER_PASS_SHADOWMAP + RENDER_PASS_SHADOWMAP, + RENDER_PASS_PRESTREAM + }; + + enum LodVisibility { + LOD_VISIBILITY_HIDDEN, + LOD_VISIBILITY_PRESTREAM, + LOD_VISIBILITY_VISIBLE }; KRNode(KRScene &scene, std::string name); @@ -159,7 +166,7 @@ public: virtual bool hasPhysics(); virtual void updateLODVisibility(const KRViewport &viewport); - bool lodIsVisible(); + LodVisibility getLODVisibility(); void setScaleCompensation(bool scale_compensation); bool getScaleCompensation(); @@ -167,10 +174,9 @@ public: bool getAnimationEnabled(node_attribute_type attrib) const; - virtual kraken_stream_level getStreamLevel(bool prime, const KRViewport &viewport); + virtual kraken_stream_level getStreamLevel(const KRViewport &viewport); - virtual void hideLOD(); - virtual void showLOD(); + virtual void setLODVisibility(LodVisibility lod_visibility); protected: KRVector3 m_localTranslation; @@ -195,7 +201,7 @@ protected: KRVector3 m_initialPreRotation; KRVector3 m_initialPostRotation; - bool m_lod_visible; + LodVisibility m_lod_visible; KRNode *m_parentNode; std::set m_childNodes; @@ -246,7 +252,7 @@ public: } void removeFromOctreeNodes(); void addToOctreeNode(KROctreeNode *octree_node); - virtual void childDeleted(KRNode *child_node); + void childDeleted(KRNode *child_node); template T *find() { diff --git a/KREngine/kraken/KRParticleSystemNewtonian.cpp b/KREngine/kraken/KRParticleSystemNewtonian.cpp index 7431e75..8426600 100644 --- a/KREngine/kraken/KRParticleSystemNewtonian.cpp +++ b/KREngine/kraken/KRParticleSystemNewtonian.cpp @@ -56,6 +56,7 @@ bool KRParticleSystemNewtonian::hasPhysics() void KRParticleSystemNewtonian::render(KRCamera *pCamera, std::vector &point_lights, std::vector &directional_lights, std::vector&spot_lights, const KRViewport &viewport, KRNode::RenderPass renderPass) { + if(m_lod_visible <= LOD_VISIBILITY_PRESTREAM) return; KRNode::render(pCamera, point_lights, directional_lights, spot_lights, viewport, renderPass); diff --git a/KREngine/kraken/KRPointLight.cpp b/KREngine/kraken/KRPointLight.cpp index 8ce2f07..bd9355a 100644 --- a/KREngine/kraken/KRPointLight.cpp +++ b/KREngine/kraken/KRPointLight.cpp @@ -43,6 +43,7 @@ KRAABB KRPointLight::getBounds() { void KRPointLight::render(KRCamera *pCamera, std::vector &point_lights, std::vector &directional_lights, std::vector&spot_lights, const KRViewport &viewport, KRNode::RenderPass renderPass) { + if(m_lod_visible <= LOD_VISIBILITY_PRESTREAM) return; KRLight::render(pCamera, point_lights, directional_lights, spot_lights, viewport, renderPass); diff --git a/KREngine/kraken/KRReverbZone.cpp b/KREngine/kraken/KRReverbZone.cpp index 322dfea..577316e 100644 --- a/KREngine/kraken/KRReverbZone.cpp +++ b/KREngine/kraken/KRReverbZone.cpp @@ -86,6 +86,7 @@ void KRReverbZone::setZone(const std::string &zone) void KRReverbZone::render(KRCamera *pCamera, std::vector &point_lights, std::vector &directional_lights, std::vector&spot_lights, const KRViewport &viewport, KRNode::RenderPass renderPass) { + if(m_lod_visible <= LOD_VISIBILITY_PRESTREAM) return; KRNode::render(pCamera, point_lights, directional_lights, spot_lights, viewport, renderPass); diff --git a/KREngine/kraken/KRScene.cpp b/KREngine/kraken/KRScene.cpp index 576d8a8..df92172 100644 --- a/KREngine/kraken/KRScene.cpp +++ b/KREngine/kraken/KRScene.cpp @@ -485,7 +485,7 @@ void KRScene::notify_sceneGraphDelete(KRNode *pNode) void KRScene::updateOctree(const KRViewport &viewport) { - m_pRootNode->showLOD(); + m_pRootNode->setLODVisibility(KRNode::LOD_VISIBILITY_VISIBLE); m_pRootNode->updateLODVisibility(viewport); std::set newNodes = std::move(m_newNodes); @@ -518,7 +518,7 @@ void KRScene::updateOctree(const KRViewport &viewport) } for(std::set::iterator itr=modifiedNodes.begin(); itr != modifiedNodes.end(); itr++) { KRNode *node = *itr; - if(node->lodIsVisible()) { + if(node->getLODVisibility() >= KRNode::LOD_VISIBILITY_PRESTREAM) { m_nodeTree.update(node); } if(node->hasPhysics()) { @@ -605,7 +605,7 @@ kraken_stream_level KRScene::getStreamLevel() if(m_pRootNode) { KRViewport viewport; // This isn't used when prime is false - stream_level = KRMIN(stream_level, m_pRootNode->getStreamLevel(false, viewport)); + stream_level = KRMIN(stream_level, m_pRootNode->getStreamLevel(viewport)); } return stream_level; diff --git a/KREngine/kraken/KRSprite.cpp b/KREngine/kraken/KRSprite.cpp index 62fbe5f..eebabcc 100644 --- a/KREngine/kraken/KRSprite.cpp +++ b/KREngine/kraken/KRSprite.cpp @@ -85,6 +85,8 @@ KRAABB KRSprite::getBounds() { void KRSprite::render(KRCamera *pCamera, std::vector &point_lights, std::vector &directional_lights, std::vector&spot_lights, const KRViewport &viewport, KRNode::RenderPass renderPass) { + if(m_lod_visible <= LOD_VISIBILITY_PRESTREAM) return; + KRNode::render(pCamera, point_lights, directional_lights, spot_lights, viewport, renderPass); diff --git a/KREngine/kraken/KRTexture.cpp b/KREngine/kraken/KRTexture.cpp index 062d52f..c6e1e6f 100644 --- a/KREngine/kraken/KRTexture.cpp +++ b/KREngine/kraken/KRTexture.cpp @@ -21,7 +21,6 @@ KRTexture::KRTexture(KRContext &context, std::string name) : KRResource(context, m_textureMemUsed = 0; m_newTextureMemUsed = 0; m_last_frame_used = 0; - m_last_frame_bound = 0; m_last_frame_max_lod_coverage = 0.0f; m_last_frame_usage = TEXTURE_USAGE_NONE; m_handle_lock.clear(); @@ -114,12 +113,8 @@ void KRTexture::resetPoolExpiry(float lodCoverage, KRTexture::texture_usage_t te m_last_frame_usage = static_cast(static_cast(m_last_frame_usage) | static_cast(textureUsage)); } -kraken_stream_level KRTexture::getStreamLevel(bool prime, float lodCoverage, KRTexture::texture_usage_t textureUsage) +kraken_stream_level KRTexture::getStreamLevel(KRTexture::texture_usage_t textureUsage) { - if(prime) { - resetPoolExpiry(lodCoverage, textureUsage); - } - if(m_current_lod_max_dim == 0) { return kraken_stream_level::STREAM_LEVEL_OUT; } else if(m_current_lod_max_dim == KRMIN(getContext().KRENGINE_MAX_TEXTURE_DIM, m_max_lod_max_dim)) { @@ -198,11 +193,7 @@ bool KRTexture::hasMipmaps() { } void KRTexture::bind(GLuint texture_unit) { - m_last_frame_bound = getContext().getCurrentFrame(); -} - -bool KRTexture::canStreamOut() const { - return (m_last_frame_bound + 2 > getContext().getCurrentFrame()); + } void KRTexture::_swapHandles() diff --git a/KREngine/kraken/KRTexture.h b/KREngine/kraken/KRTexture.h index fa338b5..75284f6 100644 --- a/KREngine/kraken/KRTexture.h +++ b/KREngine/kraken/KRTexture.h @@ -85,8 +85,7 @@ public: int getMinMipMap(); bool hasMipmaps(); - bool canStreamOut() const; - kraken_stream_level getStreamLevel(bool prime, float lodCoverage, KRTexture::texture_usage_t textureUsage); + kraken_stream_level getStreamLevel(KRTexture::texture_usage_t textureUsage); float getLastFrameLodCoverage() const; void _swapHandles(); @@ -107,7 +106,6 @@ protected: uint32_t m_min_lod_max_dim; long m_last_frame_used; - long m_last_frame_bound; float m_last_frame_max_lod_coverage; texture_usage_t m_last_frame_usage; From 16953ba9326cbb208c7bb26deb3f322824c92074 Mon Sep 17 00:00:00 2001 From: Kearwood Gilbert Date: Tue, 29 Apr 2014 00:30:14 -0700 Subject: [PATCH 77/84] Implemented camera downsampling (without reducing the render target resolution, for temporary drops of resolution without any skipped frames) --HG-- branch : nfb --- KREngine/kraken/KRCamera.cpp | 41 ++++++++++++------- KREngine/kraken/KRCamera.h | 6 ++- KREngine/kraken/KRShader.cpp | 13 ++++-- KREngine/kraken/KRShader.h | 1 + .../Shaders/PostShader.vsh | 3 +- .../Shaders/PostShader_osx.vsh | 5 ++- 6 files changed, 46 insertions(+), 23 deletions(-) diff --git a/KREngine/kraken/KRCamera.cpp b/KREngine/kraken/KRCamera.cpp index 915a873..54638a6 100644 --- a/KREngine/kraken/KRCamera.cpp +++ b/KREngine/kraken/KRCamera.cpp @@ -39,8 +39,8 @@ KRCamera::KRCamera(KRScene &scene, std::string name) : KRNode(scene, name) { m_last_frame_start = 0; m_particlesAbsoluteTime = 0.0f; - backingWidth = 0; - backingHeight = 0; + m_backingWidth = 0; + m_backingHeight = 0; volumetricBufferWidth = 0; volumetricBufferHeight = 0; m_pSkyBoxTexture = NULL; @@ -54,6 +54,7 @@ KRCamera::KRCamera(KRScene &scene, std::string name) : KRNode(scene, name) { volumetricLightAccumulationBuffer = 0; volumetricLightAccumulationTexture = 0; m_frame_times_filled = 0; + m_downsample = KRVector2::One(); } KRCamera::~KRCamera() { @@ -83,7 +84,7 @@ void KRCamera::flushSkybox() } void KRCamera::renderFrame(float deltaTime, GLint renderBufferWidth, GLint renderBufferHeight) -{ +{ // ----====---- Record timing information for measuring FPS ----====---- uint64_t current_time = m_pContext->getAbsoluteTimeMilliseconds(); if(m_last_frame_start != 0) { @@ -104,7 +105,7 @@ void KRCamera::renderFrame(float deltaTime, GLint renderBufferWidth, GLint rende //KRMat4 viewMatrix = KRMat4::Invert(getModelMatrix()); - settings.setViewportSize(KRVector2(backingWidth, backingHeight)); + settings.setViewportSize(KRVector2(m_backingWidth, m_backingHeight)); KRMat4 projectionMatrix; projectionMatrix.perspective(settings.perspective_fov, settings.m_viewportSize.x / settings.m_viewportSize.y, settings.perspective_nearz, settings.perspective_farz); m_viewport = KRViewport(settings.getViewportSize(), viewMatrix, projectionMatrix); @@ -175,6 +176,7 @@ void KRCamera::renderFrame(float deltaTime, GLint renderBufferWidth, GLint rende // Set render target GLDEBUG(glBindFramebuffer(GL_FRAMEBUFFER, lightAccumulationBuffer)); GLDEBUG(glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D, compositeDepthTexture, 0)); + GLDEBUG(glViewport(0, 0, m_viewport.getSize().x * m_downsample.x, m_viewport.getSize().y * m_downsample.y)); GLDEBUG(glClearColor(0.0f, 0.0f, 0.0f, 0.0f)); GLDEBUG(glClear(GL_COLOR_BUFFER_BIT)); @@ -251,7 +253,7 @@ void KRCamera::renderFrame(float deltaTime, GLint renderBufferWidth, GLint rende // Set render target GLDEBUG(glBindFramebuffer(GL_FRAMEBUFFER, compositeFramebuffer)); GLDEBUG(glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D, compositeDepthTexture, 0)); - GLDEBUG(glViewport(0, 0, m_viewport.getSize().x, m_viewport.getSize().y)); + GLDEBUG(glViewport(0, 0, m_viewport.getSize().x * m_downsample.x, m_viewport.getSize().y * m_downsample.y)); // Disable alpha blending GLDEBUG(glDisable(GL_BLEND)); @@ -517,9 +519,9 @@ void KRCamera::renderFrame(float deltaTime, GLint renderBufferWidth, GLint rende void KRCamera::createBuffers(GLint renderBufferWidth, GLint renderBufferHeight) { - if(renderBufferWidth != backingWidth || renderBufferHeight != backingHeight) { - backingWidth = renderBufferWidth; - backingHeight = renderBufferHeight; + if(renderBufferWidth != m_backingWidth || renderBufferHeight != m_backingHeight) { + m_backingWidth = renderBufferWidth; + m_backingHeight = renderBufferHeight; if (compositeDepthTexture) { GLDEBUG(glDeleteTextures(1, &compositeDepthTexture)); @@ -558,7 +560,7 @@ void KRCamera::createBuffers(GLint renderBufferWidth, GLint renderBufferHeight) GLDEBUG(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR)); GLDEBUG(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE)); // This is necessary for non-power-of-two textures GLDEBUG(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE)); // This is necessary for non-power-of-two textures - GLDEBUG(glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, backingWidth, backingHeight, 0, GL_BGRA, GL_UNSIGNED_BYTE, NULL)); + GLDEBUG(glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, m_backingWidth, m_backingHeight, 0, GL_BGRA, GL_UNSIGNED_BYTE, NULL)); GLDEBUG(glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, compositeColorTexture, 0)); // ----- Create Depth Texture for compositeFramebuffer ----- @@ -568,9 +570,9 @@ void KRCamera::createBuffers(GLint renderBufferWidth, GLint renderBufferHeight) GLDEBUG(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST)); GLDEBUG(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE)); // This is necessary for non-power-of-two textures GLDEBUG(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE)); // This is necessary for non-power-of-two textures - GLDEBUG(glTexImage2D(GL_TEXTURE_2D, 0, GL_DEPTH_COMPONENT, backingWidth, backingHeight, 0, GL_DEPTH_COMPONENT, GL_UNSIGNED_INT, NULL)); - //GLDEBUG(glTexImage2D(GL_TEXTURE_2D, 0, GL_DEPTH_COMPONENT16, backingWidth, backingHeight, 0, GL_DEPTH_COMPONENT, GL_UNSIGNED_INT, NULL)); - //GLDEBUG(glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT24_OES, backingWidth, backingHeight)); + GLDEBUG(glTexImage2D(GL_TEXTURE_2D, 0, GL_DEPTH_COMPONENT, m_backingWidth, m_backingHeight, 0, GL_DEPTH_COMPONENT, GL_UNSIGNED_INT, NULL)); + //GLDEBUG(glTexImage2D(GL_TEXTURE_2D, 0, GL_DEPTH_COMPONENT16, m_backingWidth, m_backingHeight, 0, GL_DEPTH_COMPONENT, GL_UNSIGNED_INT, NULL)); + //GLDEBUG(glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT24_OES, m_backingWidth, m_backingHeight)); GLDEBUG(glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D, compositeDepthTexture, 0)); // ===== Create offscreen compositing framebuffer object ===== @@ -584,7 +586,7 @@ void KRCamera::createBuffers(GLint renderBufferWidth, GLint renderBufferHeight) GLDEBUG(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR)); GLDEBUG(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE)); // This is necessary for non-power-of-two textures GLDEBUG(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE)); // This is necessary for non-power-of-two textures - GLDEBUG(glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, backingWidth, backingHeight, 0, GL_BGRA, GL_UNSIGNED_BYTE, NULL)); + GLDEBUG(glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, m_backingWidth, m_backingHeight, 0, GL_BGRA, GL_UNSIGNED_BYTE, NULL)); GLDEBUG(glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, lightAccumulationTexture, 0)); } @@ -692,7 +694,7 @@ void KRCamera::renderPost() }}; - + GLDEBUG(glViewport(0, 0, m_viewport.getSize().x, m_viewport.getSize().y)); GLDEBUG(glDisable(GL_DEPTH_TEST)); KRShader *postShader = m_pContext->getShaderManager()->getShader("PostShader", this, std::vector(), std::vector(), std::vector(), 0, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, KRNode::RENDER_PASS_FORWARD_TRANSPARENT); @@ -1117,3 +1119,14 @@ const KRViewport &KRCamera::getViewport() const { return m_viewport; } + + +KRVector2 KRCamera::getDownsample() +{ + return m_downsample; +} + +void KRCamera::setDownsample(float v) +{ + m_downsample = v; +} diff --git a/KREngine/kraken/KRCamera.h b/KREngine/kraken/KRCamera.h index d84b589..b1d2070 100644 --- a/KREngine/kraken/KRCamera.h +++ b/KREngine/kraken/KRCamera.h @@ -69,11 +69,13 @@ public: std::string getDebugText(); void flushSkybox(); // this will delete the skybox and cause the camera to reload a new skybox based on the settings + KRVector2 getDownsample(); + void setDownsample(float v); private: void createBuffers(GLint renderBufferWidth, GLint renderBufferHeight); - GLint backingWidth, backingHeight; + GLint m_backingWidth, m_backingHeight; GLint volumetricBufferWidth, volumetricBufferHeight; GLuint compositeFramebuffer, compositeDepthTexture, compositeColorTexture; @@ -91,7 +93,7 @@ private: float m_particlesAbsoluteTime; - + KRVector2 m_downsample; typedef struct { GLfloat x; diff --git a/KREngine/kraken/KRShader.cpp b/KREngine/kraken/KRShader.cpp index a2fd09a..ff74cf5 100644 --- a/KREngine/kraken/KRShader.cpp +++ b/KREngine/kraken/KRShader.cpp @@ -64,6 +64,7 @@ const char *KRShader::KRENGINE_UNIFORM_NAMES[] = { "projection_matrix", // KRENGINE_UNIFORM_PROJECTION_MATRIX "camera_position_model_space", // KRENGINE_UNIFORM_CAMERAPOS_MODEL_SPACE "viewport", // KRENGINE_UNIFORM_VIEWPORT + "viewport_downsample", // KRENGINE_UNIFORM_VIEWPORT_DOWNSAMPLE "diffuseTexture", // KRENGINE_UNIFORM_DIFFUSETEXTURE "specularTexture", // KRENGINE_UNIFORM_SPECULARTEXTURE "reflectionCubeTexture", // KRENGINE_UNIFORM_REFLECTIONCUBETEXTURE @@ -502,14 +503,18 @@ bool KRShader::bind(KRCamera &camera, const KRViewport &viewport, const KRMat4 & if(m_uniforms[KRENGINE_UNIFORM_VIEWPORT] != -1) { setUniform(KRENGINE_UNIFORM_VIEWPORT, KRVector4( - (GLfloat)0.0, - (GLfloat)0.0, - (GLfloat)viewport.getSize().x, - (GLfloat)viewport.getSize().y + (GLfloat)0.0, + (GLfloat)0.0, + (GLfloat)viewport.getSize().x, + (GLfloat)viewport.getSize().y ) ); } + if(m_uniforms[KRENGINE_UNIFORM_VIEWPORT_DOWNSAMPLE] != -1) { + setUniform(KRENGINE_UNIFORM_VIEWPORT_DOWNSAMPLE, camera.getDownsample()); + } + // Rim highlighting parameters setUniform(KRENGINE_UNIFORM_RIM_COLOR, rim_color); setUniform(KRENGINE_UNIFORM_RIM_POWER, rim_power); diff --git a/KREngine/kraken/KRShader.h b/KREngine/kraken/KRShader.h index f0cdccc..c198ce6 100644 --- a/KREngine/kraken/KRShader.h +++ b/KREngine/kraken/KRShader.h @@ -76,6 +76,7 @@ public: KRENGINE_UNIFORM_PROJECTION_MATRIX, KRENGINE_UNIFORM_CAMERAPOS_MODEL_SPACE, KRENGINE_UNIFORM_VIEWPORT, + KRENGINE_UNIFORM_VIEWPORT_DOWNSAMPLE, KRENGINE_UNIFORM_DIFFUSETEXTURE, KRENGINE_UNIFORM_SPECULARTEXTURE, KRENGINE_UNIFORM_REFLECTIONCUBETEXTURE, diff --git a/KREngine/kraken_standard_assets_ios/Shaders/PostShader.vsh b/KREngine/kraken_standard_assets_ios/Shaders/PostShader.vsh index 58b3827..c147629 100644 --- a/KREngine/kraken_standard_assets_ios/Shaders/PostShader.vsh +++ b/KREngine/kraken_standard_assets_ios/Shaders/PostShader.vsh @@ -25,6 +25,7 @@ // or implied, of Kearwood Gilbert. // +uniform mediump vec2 viewport_downsample; attribute vec4 vertex_position; attribute lowp vec4 vertex_uv; @@ -33,5 +34,5 @@ varying mediump vec2 textureCoordinate; void main() { gl_Position = vertex_position; - textureCoordinate = vertex_uv.xy; + textureCoordinate = vertex_uv.xy * viewport_downsample.xy; } \ No newline at end of file diff --git a/KREngine/kraken_standard_assets_osx/Shaders/PostShader_osx.vsh b/KREngine/kraken_standard_assets_osx/Shaders/PostShader_osx.vsh index 7a4719f..c147629 100644 --- a/KREngine/kraken_standard_assets_osx/Shaders/PostShader_osx.vsh +++ b/KREngine/kraken_standard_assets_osx/Shaders/PostShader_osx.vsh @@ -25,6 +25,7 @@ // or implied, of Kearwood Gilbert. // +uniform mediump vec2 viewport_downsample; attribute vec4 vertex_position; attribute lowp vec4 vertex_uv; @@ -33,5 +34,5 @@ varying mediump vec2 textureCoordinate; void main() { gl_Position = vertex_position; - textureCoordinate = vertex_uv.xy; -} + textureCoordinate = vertex_uv.xy * viewport_downsample.xy; +} \ No newline at end of file From 8d7ac095c9c8f60edbf8cf678f1fe3cb6815eb6b Mon Sep 17 00:00:00 2001 From: Kearwood Gilbert Date: Mon, 5 May 2014 23:02:13 -0700 Subject: [PATCH 78/84] Reduced memory utilization of Siren audio engine by dyanmically opening and closing the CoreAudio virtual file. --HG-- branch : nfb --- KREngine/kraken/KRAudioManager.cpp | 17 +++++++++++++ KREngine/kraken/KRAudioManager.h | 5 ++++ KREngine/kraken/KRAudioSample.cpp | 38 +++++++++++++++++++++++++----- KREngine/kraken/KRAudioSample.h | 4 ++++ 4 files changed, 58 insertions(+), 6 deletions(-) diff --git a/KREngine/kraken/KRAudioManager.cpp b/KREngine/kraken/KRAudioManager.cpp index eecaec2..5295c4a 100644 --- a/KREngine/kraken/KRAudioManager.cpp +++ b/KREngine/kraken/KRAudioManager.cpp @@ -414,6 +414,13 @@ void KRAudioManager::renderBlock() // ----====---- Advance audio sources ----====---- m_audio_frame += KRENGINE_AUDIO_BLOCK_LENGTH; + std::set open_samples = m_openAudioSamples; + + for(auto itr=open_samples.begin(); itr != open_samples.end(); itr++) { + KRAudioSample *sample = *itr; + sample->_endFrame(); + } + m_anticlick_block = false; m_mutex.unlock(); } @@ -1429,6 +1436,16 @@ void KRAudioManager::deactivateAudioSource(KRAudioSource *audioSource) m_activeAudioSources.erase(audioSource); } +void KRAudioManager::_registerOpenAudioSample(KRAudioSample *audioSample) +{ + m_openAudioSamples.insert(audioSample); +} + +void KRAudioManager::_registerCloseAudioSample(KRAudioSample *audioSample) +{ + m_openAudioSamples.erase(audioSample); +} + __int64_t KRAudioManager::getAudioFrame() { return m_audio_frame; diff --git a/KREngine/kraken/KRAudioManager.h b/KREngine/kraken/KRAudioManager.h index 33266f8..7b67b97 100644 --- a/KREngine/kraken/KRAudioManager.h +++ b/KREngine/kraken/KRAudioManager.h @@ -160,6 +160,9 @@ public: float getReverbMaxLength(); void setReverbMaxLength(float max_length); + void _registerOpenAudioSample(KRAudioSample *audioSample); + void _registerCloseAudioSample(KRAudioSample *audioSample); + private: bool m_enable_audio; bool m_enable_hrtf; @@ -184,6 +187,8 @@ private: std::set m_activeAudioSources; + std::set m_openAudioSamples; + void initAudio(); void initOpenAL(); void initSiren(); diff --git a/KREngine/kraken/KRAudioSample.cpp b/KREngine/kraken/KRAudioSample.cpp index f1f7cc4..a45fa6f 100644 --- a/KREngine/kraken/KRAudioSample.cpp +++ b/KREngine/kraken/KRAudioSample.cpp @@ -49,7 +49,7 @@ KRAudioSample::KRAudioSample(KRContext &context, std::string name, std::string e m_frameRate = 0; m_bufferCount = 0; - openFile(); + m_last_frame_used = 0; } KRAudioSample::KRAudioSample(KRContext &context, std::string name, std::string extension, KRDataBlock *data) : KRResource(context, name) @@ -64,8 +64,7 @@ KRAudioSample::KRAudioSample(KRContext &context, std::string name, std::string e m_frameRate = 0; m_bufferCount = 0; - openFile(); - + m_last_frame_used = 0; } KRAudioSample::~KRAudioSample() @@ -76,18 +75,21 @@ KRAudioSample::~KRAudioSample() int KRAudioSample::getChannelCount() { - openFile(); + loadInfo(); return m_channelsPerFrame; } int KRAudioSample::getFrameCount() { + loadInfo(); //return (int)((__int64_t)m_totalFrames * (__int64_t)frame_rate / (__int64_t)m_frameRate); return m_totalFrames; } float KRAudioSample::sample(int frame_offset, int frame_rate, int channel) { + loadInfo(); + int c = KRMIN(channel, m_channelsPerFrame - 1); if(frame_offset < 0) { @@ -123,6 +125,10 @@ float KRAudioSample::sample(int frame_offset, int frame_rate, int channel) void KRAudioSample::sample(__int64_t frame_offset, int frame_count, int channel, float *buffer, float amplitude, bool loop) { + loadInfo(); + + m_last_frame_used = getContext().getAudioManager()->getAudioFrame(); + if(loop) { int buffer_offset = 0; int frames_left = frame_count; @@ -275,6 +281,8 @@ void KRAudioSample::openFile() m_dataFormat = (outputFormat.mChannelsPerFrame > 1) ? AL_FORMAT_STEREO16 : AL_FORMAT_MONO16; m_channelsPerFrame = outputFormat.mChannelsPerFrame; + + getContext().getAudioManager()->_registerOpenAudioSample(this); } } @@ -289,6 +297,16 @@ void KRAudioSample::closeFile() AudioFileClose(m_audio_file_id); m_audio_file_id = 0; } + + getContext().getAudioManager()->_registerCloseAudioSample(this); +} + +void KRAudioSample::loadInfo() +{ + if(m_frameRate == 0) { + openFile(); + closeFile(); + } } std::string KRAudioSample::getExtension() @@ -304,13 +322,13 @@ bool KRAudioSample::save(KRDataBlock &data) float KRAudioSample::getDuration() { - openFile(); + loadInfo(); return (float)m_totalFrames / (float)m_frameRate; } int KRAudioSample::getBufferCount() { - openFile(); + loadInfo(); return m_bufferCount; } @@ -348,3 +366,11 @@ KRAudioBuffer *KRAudioSample::getBuffer(int index) return buffer; } +void KRAudioSample::_endFrame() +{ + const __int64_t AUDIO_SAMPLE_EXPIRY_FRAMES = 500; + long current_frame = getContext().getAudioManager()->getAudioFrame(); + if(current_frame > m_last_frame_used + AUDIO_SAMPLE_EXPIRY_FRAMES) { + closeFile(); + } +} diff --git a/KREngine/kraken/KRAudioSample.h b/KREngine/kraken/KRAudioSample.h index ab7093b..e348401 100644 --- a/KREngine/kraken/KRAudioSample.h +++ b/KREngine/kraken/KRAudioSample.h @@ -60,8 +60,11 @@ public: float sample(int frame_offset, int frame_rate, int channel); void sample(__int64_t frame_offset, int frame_count, int channel, float *buffer, float amplitude, bool loop); + void _endFrame(); private: + long m_last_frame_used; + std::string m_extension; KRDataBlock *m_pData; @@ -78,6 +81,7 @@ private: void openFile(); void closeFile(); + void loadInfo(); static OSStatus ReadProc( // AudioFile_ReadProc void * inClientData, From 1560c8f19fc3d3d26baf954a7290b83cf889893d Mon Sep 17 00:00:00 2001 From: Kearwood Gilbert Date: Tue, 13 May 2014 21:56:06 -0700 Subject: [PATCH 79/84] Refactoring of streamer code to integrate texture and vbo memory management in progress. --HG-- branch : nfb --- KREngine/Kraken.xcodeproj/project.pbxproj | 38 +++++++------------ KREngine/kraken/KRAmbientZone.cpp | 2 +- KREngine/kraken/KRAudioSource.cpp | 2 +- KREngine/kraken/KRBone.cpp | 2 +- KREngine/kraken/KRCamera.cpp | 30 +++++++-------- KREngine/kraken/KRCollider.cpp | 2 +- KREngine/kraken/KRContext.cpp | 37 +++++++++++------- KREngine/kraken/KRContext.h | 10 +++-- KREngine/kraken/KRDirectionalLight.cpp | 2 +- KREngine/kraken/KREngine.mm | 5 --- KREngine/kraken/KRLight.cpp | 8 ++-- KREngine/kraken/KRMesh.cpp | 14 +++---- KREngine/kraken/KRMeshManager.cpp | 7 +++- KREngine/kraken/KRMeshManager.h | 7 ++-- KREngine/kraken/KRModel.cpp | 2 +- KREngine/kraken/KRParticleSystemNewtonian.cpp | 6 +-- KREngine/kraken/KRPointLight.cpp | 4 +- KREngine/kraken/KRResource+fbx.cpp | 2 +- KREngine/kraken/KRReverbZone.cpp | 2 +- KREngine/kraken/KRScene.cpp | 4 +- KREngine/kraken/KRSprite.cpp | 2 +- KREngine/kraken/KRTextureManager.cpp | 13 +++---- KREngine/kraken/KRTextureManager.h | 8 ++-- 23 files changed, 103 insertions(+), 106 deletions(-) diff --git a/KREngine/Kraken.xcodeproj/project.pbxproj b/KREngine/Kraken.xcodeproj/project.pbxproj index 3c787f3..314545c 100644 --- a/KREngine/Kraken.xcodeproj/project.pbxproj +++ b/KREngine/Kraken.xcodeproj/project.pbxproj @@ -91,14 +91,10 @@ E43F70DD181B20E400136169 /* KRLODSet.cpp in Sources */ = {isa = PBXBuildFile; fileRef = E43F70DA181B20E300136169 /* KRLODSet.cpp */; }; E43F70DE181B20E400136169 /* KRLODSet.h in Headers */ = {isa = PBXBuildFile; fileRef = E43F70DB181B20E400136169 /* KRLODSet.h */; }; E43F70DF181B20E400136169 /* KRLODSet.h in Headers */ = {isa = PBXBuildFile; fileRef = E43F70DB181B20E400136169 /* KRLODSet.h */; settings = {ATTRIBUTES = (Public, ); }; }; - E43F70E51824D9AB00136169 /* KRTextureStreamer.mm in Sources */ = {isa = PBXBuildFile; fileRef = E43F70E31824D9AB00136169 /* KRTextureStreamer.mm */; }; - E43F70E61824D9AB00136169 /* KRTextureStreamer.mm in Sources */ = {isa = PBXBuildFile; fileRef = E43F70E31824D9AB00136169 /* KRTextureStreamer.mm */; }; - E43F70E71824D9AB00136169 /* KRTextureStreamer.h in Headers */ = {isa = PBXBuildFile; fileRef = E43F70E41824D9AB00136169 /* KRTextureStreamer.h */; }; - E43F70E81824D9AB00136169 /* KRTextureStreamer.h in Headers */ = {isa = PBXBuildFile; fileRef = E43F70E41824D9AB00136169 /* KRTextureStreamer.h */; settings = {ATTRIBUTES = (Public, ); }; }; - E43F70FF1824E73100136169 /* KRMeshStreamer.mm in Sources */ = {isa = PBXBuildFile; fileRef = E43F70FD1824E73100136169 /* KRMeshStreamer.mm */; }; - E43F71001824E73100136169 /* KRMeshStreamer.mm in Sources */ = {isa = PBXBuildFile; fileRef = E43F70FD1824E73100136169 /* KRMeshStreamer.mm */; }; - E43F71011824E73100136169 /* KRMeshStreamer.h in Headers */ = {isa = PBXBuildFile; fileRef = E43F70FE1824E73100136169 /* KRMeshStreamer.h */; }; - E43F71021824E73100136169 /* KRMeshStreamer.h in Headers */ = {isa = PBXBuildFile; fileRef = E43F70FE1824E73100136169 /* KRMeshStreamer.h */; settings = {ATTRIBUTES = (Public, ); }; }; + E43F70E51824D9AB00136169 /* KRStreamer.mm in Sources */ = {isa = PBXBuildFile; fileRef = E43F70E31824D9AB00136169 /* KRStreamer.mm */; }; + E43F70E61824D9AB00136169 /* KRStreamer.mm in Sources */ = {isa = PBXBuildFile; fileRef = E43F70E31824D9AB00136169 /* KRStreamer.mm */; }; + E43F70E71824D9AB00136169 /* KRStreamer.h in Headers */ = {isa = PBXBuildFile; fileRef = E43F70E41824D9AB00136169 /* KRStreamer.h */; }; + E43F70E81824D9AB00136169 /* KRStreamer.h in Headers */ = {isa = PBXBuildFile; fileRef = E43F70E41824D9AB00136169 /* KRStreamer.h */; settings = {ATTRIBUTES = (Public, ); }; }; E4409D2916FA748700310F76 /* font.tga in Resources */ = {isa = PBXBuildFile; fileRef = E41AE1DD16B124CA00980428 /* font.tga */; }; E44F38241683B23000399B5D /* KRRenderSettings.h in Headers */ = {isa = PBXBuildFile; fileRef = E44F38231683B22C00399B5D /* KRRenderSettings.h */; settings = {ATTRIBUTES = (); }; }; E44F38251683B23000399B5D /* KRRenderSettings.h in Headers */ = {isa = PBXBuildFile; fileRef = E44F38231683B22C00399B5D /* KRRenderSettings.h */; settings = {ATTRIBUTES = (Public, ); }; }; @@ -438,7 +434,7 @@ E40BA45315EFF79500D7C3DD /* KRAABB.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = KRAABB.h; sourceTree = ""; }; E40F982A184A7A2700CFA4D8 /* KRMeshQuad.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = KRMeshQuad.cpp; sourceTree = ""; }; E40F982B184A7A2700CFA4D8 /* KRMeshQuad.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = KRMeshQuad.h; sourceTree = ""; }; - E40F9830184A7BAC00CFA4D8 /* KRSprite.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = KRSprite.cpp; sourceTree = ""; }; + E40F9830184A7BAC00CFA4D8 /* KRSprite.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; lineEnding = 0; path = KRSprite.cpp; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.cpp; }; E40F9831184A7BAC00CFA4D8 /* KRSprite.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = KRSprite.h; sourceTree = ""; }; E414BAE11435557300A668C4 /* KRModel.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; lineEnding = 0; path = KRModel.h; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.objcpp; }; E414BAE41435558800A668C4 /* KRModel.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; lineEnding = 0; path = KRModel.cpp; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.cpp; }; @@ -478,10 +474,8 @@ E43B0AD515DDCA0D00A5CB9F /* KRContextObject.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = KRContextObject.h; sourceTree = ""; }; E43F70DA181B20E300136169 /* KRLODSet.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = KRLODSet.cpp; sourceTree = ""; }; E43F70DB181B20E400136169 /* KRLODSet.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = KRLODSet.h; sourceTree = ""; }; - E43F70E31824D9AB00136169 /* KRTextureStreamer.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = KRTextureStreamer.mm; sourceTree = ""; }; - E43F70E41824D9AB00136169 /* KRTextureStreamer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = KRTextureStreamer.h; sourceTree = ""; }; - E43F70FD1824E73100136169 /* KRMeshStreamer.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = KRMeshStreamer.mm; sourceTree = ""; }; - E43F70FE1824E73100136169 /* KRMeshStreamer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = KRMeshStreamer.h; sourceTree = ""; }; + E43F70E31824D9AB00136169 /* KRStreamer.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; lineEnding = 0; path = KRStreamer.mm; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.objcpp; }; + E43F70E41824D9AB00136169 /* KRStreamer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; lineEnding = 0; path = KRStreamer.h; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.objcpp; }; E44F38231683B22C00399B5D /* KRRenderSettings.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = KRRenderSettings.h; sourceTree = ""; }; E44F38271683B24400399B5D /* KRRenderSettings.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = KRRenderSettings.cpp; sourceTree = ""; }; E450273716E0491D00FDEC5C /* KRReverbZone.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; lineEnding = 0; path = KRReverbZone.cpp; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.cpp; }; @@ -1020,8 +1014,6 @@ E4CA10EE1637BD58005D9400 /* KRTextureTGA.cpp */, E460292516681CFE00261BB9 /* KRTextureAnimated.h */, E460292716681D1000261BB9 /* KRTextureAnimated.cpp */, - E43F70E31824D9AB00136169 /* KRTextureStreamer.mm */, - E43F70E41824D9AB00136169 /* KRTextureStreamer.h */, E404701E18695DD200F01F42 /* KRTextureKTX.cpp */, E404701F18695DD200F01F42 /* KRTextureKTX.h */, ); @@ -1039,8 +1031,6 @@ E4C454AE167BB8FC003586CD /* KRMeshCube.cpp */, E4C454B1167BC04B003586CD /* KRMeshSphere.h */, E4C454B4167BC05C003586CD /* KRMeshSphere.cpp */, - E43F70FD1824E73100136169 /* KRMeshStreamer.mm */, - E43F70FE1824E73100136169 /* KRMeshStreamer.h */, E40F982A184A7A2700CFA4D8 /* KRMeshQuad.cpp */, E40F982B184A7A2700CFA4D8 /* KRMeshQuad.h */, ); @@ -1159,6 +1149,8 @@ E491016613C99B9E0098455B /* kraken */ = { isa = PBXGroup; children = ( + E43F70E31824D9AB00136169 /* KRStreamer.mm */, + E43F70E41824D9AB00136169 /* KRStreamer.h */, E488399915F92BA300BD66D5 /* Managers */, E461A173152E59DF00F2044A /* Math */, E461A170152E598200F2044A /* Resources */, @@ -1341,7 +1333,7 @@ E461A15C152E563100F2044A /* KRDirectionalLight.h in Headers */, E461A168152E570700F2044A /* KRSpotLight.h in Headers */, E468448117FFDF51001F1FA1 /* KRLocator.h in Headers */, - E43F70E71824D9AB00136169 /* KRTextureStreamer.h in Headers */, + E43F70E71824D9AB00136169 /* KRStreamer.h in Headers */, E4F975321536220900FD60B2 /* KRNode.h in Headers */, E48C696F15374F5B00232E28 /* KRContext.h in Headers */, E46F4A0B155E002100CCF8B8 /* KRDataBlock.h in Headers */, @@ -1392,7 +1384,6 @@ E4AE635F1704FB0A00B460CD /* KRLODGroup.h in Headers */, E4EC73C31720B1FF0065299F /* KRVector4.h in Headers */, E48CF944173453990005EBBB /* KRFloat.h in Headers */, - E43F71011824E73100136169 /* KRMeshStreamer.h in Headers */, E45134B81746A4A300443C21 /* KRBehavior.h in Headers */, ); runOnlyForDeploymentPostprocessing = 0; @@ -1487,8 +1478,7 @@ E43F70DF181B20E400136169 /* KRLODSet.h in Headers */, E4AE63601704FB0A00B460CD /* KRLODGroup.h in Headers */, E45134B91746A4A300443C21 /* KRBehavior.h in Headers */, - E43F71021824E73100136169 /* KRMeshStreamer.h in Headers */, - E43F70E81824D9AB00136169 /* KRTextureStreamer.h in Headers */, + E43F70E81824D9AB00136169 /* KRStreamer.h in Headers */, E40F982F184A7A2700CFA4D8 /* KRMeshQuad.h in Headers */, E40F9835184A7BAC00CFA4D8 /* KRSprite.h in Headers */, E48CF945173453990005EBBB /* KRFloat.h in Headers */, @@ -1715,7 +1705,6 @@ E497B946151BA99500D3DC67 /* KRVector2.cpp in Sources */, E497B94D151BCF2500D3DC67 /* KRResource.cpp in Sources */, E497B950151BD2CE00D3DC67 /* KRResource+obj.cpp in Sources */, - E43F70FF1824E73100136169 /* KRMeshStreamer.mm in Sources */, E461A156152E54F800F2044A /* KRLight.cpp in Sources */, E461A159152E557E00F2044A /* KRPointLight.cpp in Sources */, E468447F17FFDF51001F1FA1 /* KRLocator.cpp in Sources */, @@ -1742,7 +1731,7 @@ E4324BA816444C230043185B /* KRParticleSystem.cpp in Sources */, E4324BAE16444E120043185B /* KRParticleSystemNewtonian.cpp in Sources */, E460292816681D1000261BB9 /* KRTextureAnimated.cpp in Sources */, - E43F70E51824D9AB00136169 /* KRTextureStreamer.mm in Sources */, + E43F70E51824D9AB00136169 /* KRStreamer.mm in Sources */, E428C2F51669611600A16EDF /* KRAnimationManager.cpp in Sources */, E428C2FB1669613200A16EDF /* KRAnimation.cpp in Sources */, E428C3071669628A00A16EDF /* KRAnimationCurve.cpp in Sources */, @@ -1817,7 +1806,6 @@ E488399515F928CA00BD66D5 /* KRBundle.cpp in Sources */, E488399D15F92BE000BD66D5 /* KRBundleManager.cpp in Sources */, E43F70DD181B20E400136169 /* KRLODSet.cpp in Sources */, - E43F71001824E73100136169 /* KRMeshStreamer.mm in Sources */, E4B175AD161F5A1000B8FB80 /* KRTexture.cpp in Sources */, E4B175B3161F5FAF00B8FB80 /* KRTextureCube.cpp in Sources */, E40F9833184A7BAC00CFA4D8 /* KRSprite.cpp in Sources */, @@ -1835,7 +1823,7 @@ E428C31A1669A25D00A16EDF /* KRAnimationAttribute.cpp in Sources */, E416AA9D1671375C000F6786 /* KRAnimationCurveManager.cpp in Sources */, E480BE6D1671C653004EC8AD /* KRBone.cpp in Sources */, - E43F70E61824D9AB00136169 /* KRTextureStreamer.mm in Sources */, + E43F70E61824D9AB00136169 /* KRStreamer.mm in Sources */, E4C454B0167BB8FC003586CD /* KRMeshCube.cpp in Sources */, E4C454B6167BC05C003586CD /* KRMeshSphere.cpp in Sources */, E4C454BC167BD248003586CD /* KRHitInfo.cpp in Sources */, diff --git a/KREngine/kraken/KRAmbientZone.cpp b/KREngine/kraken/KRAmbientZone.cpp index a76142f..cc7675a 100644 --- a/KREngine/kraken/KRAmbientZone.cpp +++ b/KREngine/kraken/KRAmbientZone.cpp @@ -113,7 +113,7 @@ void KRAmbientZone::render(KRCamera *pCamera, std::vector &point GLDEBUG(glEnable(GL_DEPTH_TEST)); GLDEBUG(glDepthFunc(GL_LEQUAL)); GLDEBUG(glDepthRangef(0.0, 1.0)); - std::vector sphereModels = getContext().getModelManager()->getModel("__sphere"); + std::vector sphereModels = getContext().getMeshManager()->getModel("__sphere"); if(sphereModels.size()) { for(int i=0; i < sphereModels[0]->getSubmeshCount(); i++) { sphereModels[0]->renderSubmesh(i, renderPass, getName(), "visualize_overlay"); diff --git a/KREngine/kraken/KRAudioSource.cpp b/KREngine/kraken/KRAudioSource.cpp index bce86a7..874ad22 100644 --- a/KREngine/kraken/KRAudioSource.cpp +++ b/KREngine/kraken/KRAudioSource.cpp @@ -226,7 +226,7 @@ void KRAudioSource::render(KRCamera *pCamera, std::vector &point GLDEBUG(glEnable(GL_DEPTH_TEST)); GLDEBUG(glDepthFunc(GL_LEQUAL)); GLDEBUG(glDepthRangef(0.0, 1.0)); - std::vector sphereModels = getContext().getModelManager()->getModel("__sphere"); + std::vector sphereModels = getContext().getMeshManager()->getModel("__sphere"); if(sphereModels.size()) { for(int i=0; i < sphereModels[0]->getSubmeshCount(); i++) { sphereModels[0]->renderSubmesh(i, renderPass, getName(), "visualize_overlay"); diff --git a/KREngine/kraken/KRBone.cpp b/KREngine/kraken/KRBone.cpp index 974985b..1fdbb62 100644 --- a/KREngine/kraken/KRBone.cpp +++ b/KREngine/kraken/KRBone.cpp @@ -65,7 +65,7 @@ void KRBone::render(KRCamera *pCamera, std::vector &point_lights KRVector3 rim_color; if(getContext().getShaderManager()->selectShader(*pCamera, pShader, viewport, sphereModelMatrix, point_lights, directional_lights, spot_lights, 0, renderPass, rim_color, 0.0f)) { - std::vector sphereModels = getContext().getModelManager()->getModel("__sphere"); + std::vector sphereModels = getContext().getMeshManager()->getModel("__sphere"); if(sphereModels.size()) { for(int i=0; i < sphereModels[0]->getSubmeshCount(); i++) { sphereModels[0]->renderSubmesh(i, renderPass, getName(), "visualize_overlay"); diff --git a/KREngine/kraken/KRCamera.cpp b/KREngine/kraken/KRCamera.cpp index 54638a6..823141b 100644 --- a/KREngine/kraken/KRCamera.cpp +++ b/KREngine/kraken/KRCamera.cpp @@ -321,7 +321,7 @@ void KRCamera::renderFrame(float deltaTime, GLint renderBufferWidth, GLint rende getContext().getTextureManager()->selectTexture(0, m_pSkyBoxTexture, 0.0f, KRTexture::TEXTURE_USAGE_SKY_CUBE); // Render a full screen quad - m_pContext->getModelManager()->bindVBO(getContext().getModelManager()->KRENGINE_VBO_2D_SQUARE_VERTICES, getContext().getModelManager()->KRENGINE_VBO_2D_SQUARE_INDEXES, getContext().getModelManager()->KRENGINE_VBO_2D_SQUARE_ATTRIBS, true); + m_pContext->getMeshManager()->bindVBO(getContext().getMeshManager()->KRENGINE_VBO_2D_SQUARE_VERTICES, getContext().getMeshManager()->KRENGINE_VBO_2D_SQUARE_INDEXES, getContext().getMeshManager()->KRENGINE_VBO_2D_SQUARE_ATTRIBS, true); GLDEBUG(glDrawArrays(GL_TRIANGLE_STRIP, 0, 4)); } @@ -482,7 +482,7 @@ void KRCamera::renderFrame(float deltaTime, GLint renderBufferWidth, GLint rende KRShader *pVisShader = getContext().getShaderManager()->getShader("visualize_overlay", this, std::vector(), std::vector(), std::vector(), 0, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, KRNode::RENDER_PASS_FORWARD_TRANSPARENT); - m_pContext->getModelManager()->bindVBO(getContext().getModelManager()->KRENGINE_VBO_3D_CUBE_VERTICES, getContext().getModelManager()->KRENGINE_VBO_3D_CUBE_INDEXES, getContext().getModelManager()->KRENGINE_VBO_3D_CUBE_ATTRIBS, true); + m_pContext->getMeshManager()->bindVBO(getContext().getMeshManager()->KRENGINE_VBO_3D_CUBE_VERTICES, getContext().getMeshManager()->KRENGINE_VBO_3D_CUBE_INDEXES, getContext().getMeshManager()->KRENGINE_VBO_3D_CUBE_ATTRIBS, true); for(unordered_map::iterator itr=m_viewport.getVisibleBounds().begin(); itr != m_viewport.getVisibleBounds().end(); itr++) { KRMat4 matModel = KRMat4(); matModel.scale((*itr).first.size() * 0.5f); @@ -500,12 +500,12 @@ void KRCamera::renderFrame(float deltaTime, GLint renderBufferWidth, GLint rende GL_POP_GROUP_MARKER; -// fprintf(stderr, "VBO Mem: %i Kbyte Texture Mem: %i/%i Kbyte (active/total) Shader Handles: %i Visible Bounds: %i Max Texture LOD: %i\n", (int)m_pContext->getModelManager()->getMemUsed() / 1024, (int)m_pContext->getTextureManager()->getActiveMemUsed() / 1024, (int)m_pContext->getTextureManager()->getMemUsed() / 1024, (int)m_pContext->getShaderManager()->getShaderHandlesUsed(), (int)m_visibleBounds.size(), m_pContext->getTextureManager()->getLODDimCap()); +// fprintf(stderr, "VBO Mem: %i Kbyte Texture Mem: %i/%i Kbyte (active/total) Shader Handles: %i Visible Bounds: %i Max Texture LOD: %i\n", (int)m_pContext->getMeshManager()->getMemUsed() / 1024, (int)m_pContext->getTextureManager()->getActiveMemUsed() / 1024, (int)m_pContext->getTextureManager()->getMemUsed() / 1024, (int)m_pContext->getShaderManager()->getShaderHandlesUsed(), (int)m_visibleBounds.size(), m_pContext->getTextureManager()->getLODDimCap()); GL_PUSH_GROUP_MARKER("Post Processing"); GLDEBUG(glBindFramebuffer(GL_FRAMEBUFFER, defaultFBO)); renderPost(); - m_pContext->getModelManager()->unbindVBO(); + m_pContext->getMeshManager()->unbindVBO(); GL_POP_GROUP_MARKER; @@ -716,7 +716,7 @@ void KRCamera::renderPost() } // Update attribute values. - m_pContext->getModelManager()->bindVBO(getContext().getModelManager()->KRENGINE_VBO_2D_SQUARE_VERTICES, getContext().getModelManager()->KRENGINE_VBO_2D_SQUARE_INDEXES, getContext().getModelManager()->KRENGINE_VBO_2D_SQUARE_ATTRIBS, true); + m_pContext->getMeshManager()->bindVBO(getContext().getMeshManager()->KRENGINE_VBO_2D_SQUARE_VERTICES, getContext().getMeshManager()->KRENGINE_VBO_2D_SQUARE_INDEXES, getContext().getMeshManager()->KRENGINE_VBO_2D_SQUARE_ATTRIBS, true); GLDEBUG(glDrawArrays(GL_TRIANGLE_STRIP, 0, 4)); @@ -738,7 +738,7 @@ void KRCamera::renderPost() // viewMatrix.translate(-0.70, 0.70 - 0.45 * iShadow, 0.0); // getContext().getShaderManager()->selectShader(blitShader, KRViewport(getViewportSize(), viewMatrix, KRMat4()), shadowViewports, KRMat4(), KRVector3(), NULL, 0, KRNode::RENDER_PASS_FORWARD_TRANSPARENT); // m_pContext->getTextureManager()->selectTexture(1, NULL); -// m_pContext->getModelManager()->bindVBO(KRENGINE_VBO_2D_SQUARE_INDICES, KRENGINE_VBO_2D_SQUARE_VERTEXES, KRENGINE_VBO_2D_SQUARE_ATTRIBS, true); +// m_pContext->getMeshManager()->bindVBO(KRENGINE_VBO_2D_SQUARE_INDICES, KRENGINE_VBO_2D_SQUARE_VERTEXES, KRENGINE_VBO_2D_SQUARE_ATTRIBS, true); // m_pContext->getTextureManager()->_setActiveTexture(0); // GLDEBUG(glBindTexture(GL_TEXTURE_2D, shadowDepthTexture[iShadow])); //#if GL_EXT_shadow_samplers @@ -758,7 +758,7 @@ void KRCamera::renderPost() if(m_debug_text_vertices.getSize()) { - m_pContext->getModelManager()->releaseVBO(m_debug_text_vertices); + m_pContext->getMeshManager()->releaseVBO(m_debug_text_vertices); } const char *szText = settings.m_debug_text.c_str(); @@ -907,8 +907,8 @@ void KRCamera::renderPost() m_pContext->getTextureManager()->selectTexture(0, m_pContext->getTextureManager()->getTexture("font"), 0.0f, KRTexture::TEXTURE_USAGE_UI); KRDataBlock index_data; - //m_pContext->getModelManager()->bindVBO((void *)m_debug_text_vertices, vertex_count * sizeof(DebugTextVertexData), NULL, 0, (1 << KRMesh::KRENGINE_ATTRIB_VERTEX) | (1 << KRMesh::KRENGINE_ATTRIB_TEXUVA), true); - m_pContext->getModelManager()->bindVBO(m_debug_text_vertices, index_data, (1 << KRMesh::KRENGINE_ATTRIB_VERTEX) | (1 << KRMesh::KRENGINE_ATTRIB_TEXUVA), true); + //m_pContext->getMeshManager()->bindVBO((void *)m_debug_text_vertices, vertex_count * sizeof(DebugTextVertexData), NULL, 0, (1 << KRMesh::KRENGINE_ATTRIB_VERTEX) | (1 << KRMesh::KRENGINE_ATTRIB_TEXUVA), true); + m_pContext->getMeshManager()->bindVBO(m_debug_text_vertices, index_data, (1 << KRMesh::KRENGINE_ATTRIB_VERTEX) | (1 << KRMesh::KRENGINE_ATTRIB_TEXUVA), true); GLDEBUG(glDrawArrays(GL_TRIANGLES, 0, vertex_count)); @@ -997,11 +997,11 @@ std::string KRCamera::getDebugText() long texture_mem_used = m_pContext->getTextureManager()->getMemUsed(); long texture_mem_throughput = m_pContext->getTextureManager()->getMemoryTransferedThisFrame(); - int vbo_count_active = m_pContext->getModelManager()->getActiveVBOCount(); - int vbo_count_pooled = m_pContext->getModelManager()->getPoolVBOCount(); - long vbo_mem_active = m_pContext->getModelManager()->getMemActive(); - long vbo_mem_used = m_pContext->getModelManager()->getMemUsed(); - long vbo_mem_throughput = m_pContext->getModelManager()->getMemoryTransferedThisFrame(); + int vbo_count_active = m_pContext->getMeshManager()->getActiveVBOCount(); + int vbo_count_pooled = m_pContext->getMeshManager()->getPoolVBOCount(); + long vbo_mem_active = m_pContext->getMeshManager()->getMemActive(); + long vbo_mem_used = m_pContext->getMeshManager()->getMemUsed(); + long vbo_mem_throughput = m_pContext->getMeshManager()->getMemoryTransferedThisFrame(); long total_mem_active = texture_mem_active + vbo_mem_active; long total_mem_used = texture_mem_used + vbo_mem_used; @@ -1050,7 +1050,7 @@ std::string KRCamera::getDebugText() case KRRenderSettings::KRENGINE_DEBUG_DISPLAY_DRAW_CALLS: // ----====---- List Draw Calls ----====---- { - std::vector draw_calls = m_pContext->getModelManager()->getDrawCalls(); + std::vector draw_calls = m_pContext->getMeshManager()->getDrawCalls(); long draw_call_count = 0; long vertex_count = 0; diff --git a/KREngine/kraken/KRCollider.cpp b/KREngine/kraken/KRCollider.cpp index 5b98e43..3367423 100644 --- a/KREngine/kraken/KRCollider.cpp +++ b/KREngine/kraken/KRCollider.cpp @@ -77,7 +77,7 @@ void KRCollider::loadXML(tinyxml2::XMLElement *e) { void KRCollider::loadModel() { 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 + m_models = m_pContext->getMeshManager()->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); } diff --git a/KREngine/kraken/KRContext.cpp b/KREngine/kraken/KRContext.cpp index ca54490..db735b8 100644 --- a/KREngine/kraken/KRContext.cpp +++ b/KREngine/kraken/KRContext.cpp @@ -19,7 +19,6 @@ int KRContext::KRENGINE_MAX_TEXTURE_MEM; int KRContext::KRENGINE_TARGET_TEXTURE_MEM_MAX; int KRContext::KRENGINE_MAX_TEXTURE_DIM; int KRContext::KRENGINE_MIN_TEXTURE_DIM; -int KRContext::KRENGINE_MAX_TEXTURE_THROUGHPUT; int KRContext::KRENGINE_PRESTREAM_DISTANCE; const char *KRContext::extension_names[KRENGINE_NUM_EXTENSIONS] = { @@ -29,7 +28,8 @@ const char *KRContext::extension_names[KRENGINE_NUM_EXTENSIONS] = { KRContext::log_callback *KRContext::s_log_callback = NULL; void *KRContext::s_log_callback_user_data = NULL; -KRContext::KRContext() { +KRContext::KRContext() : m_streamer(*this) +{ m_streamingEnabled = false; mach_timebase_info(&m_timebase_info); @@ -41,7 +41,7 @@ KRContext::KRContext() { m_pShaderManager = new KRShaderManager(*this); m_pTextureManager = new KRTextureManager(*this); m_pMaterialManager = new KRMaterialManager(*this, m_pTextureManager, m_pShaderManager); - m_pModelManager = new KRMeshManager(*this); + m_pMeshManager = new KRMeshManager(*this); m_pSceneManager = new KRSceneManager(*this); m_pAnimationManager = new KRAnimationManager(*this); m_pAnimationCurveManager = new KRAnimationCurveManager(*this); @@ -59,9 +59,9 @@ KRContext::~KRContext() { m_pSceneManager = NULL; } - if(m_pModelManager) { - delete m_pModelManager; - m_pModelManager = NULL; + if(m_pMeshManager) { + delete m_pMeshManager; + m_pMeshManager = NULL; } if(m_pTextureManager) { @@ -147,8 +147,8 @@ KRMaterialManager *KRContext::getMaterialManager() { KRShaderManager *KRContext::getShaderManager() { return m_pShaderManager; } -KRMeshManager *KRContext::getModelManager() { - return m_pModelManager; +KRMeshManager *KRContext::getMeshManager() { + return m_pMeshManager; } KRAnimationManager *KRContext::getAnimationManager() { return m_pAnimationManager; @@ -177,7 +177,7 @@ std::vector KRContext::getResources() for(unordered_map::iterator itr = m_pMaterialManager->getMaterials().begin(); itr != m_pMaterialManager->getMaterials().end(); itr++) { resources.push_back((*itr).second); } - for(unordered_multimap::iterator itr = m_pModelManager->getModels().begin(); itr != m_pModelManager->getModels().end(); itr++) { + for(unordered_multimap::iterator itr = m_pMeshManager->getModels().begin(); itr != m_pMeshManager->getModels().end(); itr++) { resources.push_back((*itr).second); } for(unordered_map::iterator itr = m_pAnimationManager->getAnimations().begin(); itr != m_pAnimationManager->getAnimations().end(); itr++) { @@ -211,7 +211,7 @@ void KRContext::loadResource(const std::string &file_name, KRDataBlock *data) { if(extension.compare("krbundle") == 0) { m_pBundleManager->loadBundle(name.c_str(), data); } else if(extension.compare("krmesh") == 0) { - m_pModelManager->loadModel(name.c_str(), data); + m_pMeshManager->loadModel(name.c_str(), data); } else if(extension.compare("krscene") == 0) { m_pSceneManager->loadScene(name.c_str(), data); } else if(extension.compare("kranimation") == 0) { @@ -263,7 +263,7 @@ void KRContext::rotateBuffers(bool new_frame) { //fprintf(stderr, "Rotating Buffers...\n"); if(!new_frame) GLDEBUG(glFinish()); - m_pModelManager->rotateBuffers(new_frame); + m_pMeshManager->rotateBuffers(new_frame); } void KRContext::detectExtensions() { @@ -273,17 +273,18 @@ void KRContext::detectExtensions() { void KRContext::startFrame(float deltaTime) { + m_streamer.startStreamer(); m_pTextureManager->startFrame(deltaTime); m_pAnimationManager->startFrame(deltaTime); m_pSoundManager->startFrame(deltaTime); - m_pModelManager->startFrame(deltaTime); + m_pMeshManager->startFrame(deltaTime); } void KRContext::endFrame(float deltaTime) { m_pTextureManager->endFrame(deltaTime); m_pAnimationManager->endFrame(deltaTime); - m_pModelManager->endFrame(deltaTime); + m_pMeshManager->endFrame(deltaTime); rotateBuffers(true); m_current_frame++; m_absolute_time += deltaTime; @@ -337,3 +338,13 @@ void KRContext::getMemoryStats(long &free_memory) #error Unsupported Platform #endif } + +void KRContext::doStreaming() +{ + if(m_streamingEnabled) { + long memoryRemaining = KRENGINE_TARGET_TEXTURE_MEM_MAX; + long memoryRemainingThisFrame = KRENGINE_MAX_TEXTURE_MEM - m_pTextureManager->getMemUsed(); + m_pMeshManager->doStreaming(memoryRemaining, memoryRemainingThisFrame); + m_pTextureManager->doStreaming(memoryRemaining, memoryRemainingThisFrame); + } +} diff --git a/KREngine/kraken/KRContext.h b/KREngine/kraken/KRContext.h index d311214..147df98 100644 --- a/KREngine/kraken/KRContext.h +++ b/KREngine/kraken/KRContext.h @@ -20,6 +20,7 @@ #include "KRAnimationCurveManager.h" #include "KRAudioManager.h" #include "KRUnknownManager.h" +#include "KRStreamer.h" class KRContext { public: @@ -31,7 +32,6 @@ public: static int KRENGINE_TARGET_TEXTURE_MEM_MAX; static int KRENGINE_MAX_TEXTURE_DIM; static int KRENGINE_MIN_TEXTURE_DIM; - static int KRENGINE_MAX_TEXTURE_THROUGHPUT; static int KRENGINE_PRESTREAM_DISTANCE; @@ -46,7 +46,7 @@ public: KRTextureManager *getTextureManager(); KRMaterialManager *getMaterialManager(); KRShaderManager *getShaderManager(); - KRMeshManager *getModelManager(); + KRMeshManager *getMeshManager(); KRAnimationManager *getAnimationManager(); KRAnimationCurveManager *getAnimationCurveManager(); KRAudioManager *getAudioManager(); @@ -89,13 +89,15 @@ public: static void SetLogCallback(log_callback *log_callback, void *user_data); static void Log(log_level level, const std::string &message_format, ...); + void doStreaming(); + private: KRBundleManager *m_pBundleManager; KRSceneManager *m_pSceneManager; KRTextureManager *m_pTextureManager; KRMaterialManager *m_pMaterialManager; KRShaderManager *m_pShaderManager; - KRMeshManager *m_pModelManager; + KRMeshManager *m_pMeshManager; KRAnimationManager *m_pAnimationManager; KRAnimationCurveManager *m_pAnimationCurveManager; KRAudioManager *m_pSoundManager; @@ -114,6 +116,8 @@ private: static log_callback *s_log_callback; static void *s_log_callback_user_data; + + KRStreamer m_streamer; }; #endif diff --git a/KREngine/kraken/KRDirectionalLight.cpp b/KREngine/kraken/KRDirectionalLight.cpp index 31245b8..b3de30d 100644 --- a/KREngine/kraken/KRDirectionalLight.cpp +++ b/KREngine/kraken/KRDirectionalLight.cpp @@ -123,7 +123,7 @@ void KRDirectionalLight::render(KRCamera *pCamera, std::vector & GLDEBUG(glDisable(GL_DEPTH_TEST)); // Render a full screen quad - m_pContext->getModelManager()->bindVBO(getContext().getModelManager()->KRENGINE_VBO_2D_SQUARE_VERTICES, getContext().getModelManager()->KRENGINE_VBO_2D_SQUARE_INDEXES, getContext().getModelManager()->KRENGINE_VBO_2D_SQUARE_ATTRIBS, true); + m_pContext->getMeshManager()->bindVBO(getContext().getMeshManager()->KRENGINE_VBO_2D_SQUARE_VERTICES, getContext().getMeshManager()->KRENGINE_VBO_2D_SQUARE_INDEXES, getContext().getMeshManager()->KRENGINE_VBO_2D_SQUARE_ATTRIBS, true); GLDEBUG(glDrawArrays(GL_TRIANGLE_STRIP, 0, 4)); } } diff --git a/KREngine/kraken/KREngine.mm b/KREngine/kraken/KREngine.mm index d5949cc..f8c2540 100644 --- a/KREngine/kraken/KREngine.mm +++ b/KREngine/kraken/KREngine.mm @@ -96,8 +96,6 @@ void kraken::set_debug_text(const std::string &print_text) KRContext::KRENGINE_MIN_TEXTURE_DIM = 64; KRContext::KRENGINE_PRESTREAM_DISTANCE = 1000.0f; - KRContext::KRENGINE_MAX_TEXTURE_THROUGHPUT = 4000000; - KRContext::KRENGINE_MAX_VBO_MEM = total_ram * 2 / 4; KRContext::KRENGINE_MAX_TEXTURE_MEM = total_ram * 1 / 8; @@ -117,7 +115,6 @@ void kraken::set_debug_text(const std::string &print_text) KRContext::KRENGINE_TARGET_TEXTURE_MEM_MAX = 48000000 * 2; KRContext::KRENGINE_MAX_TEXTURE_DIM = 2048; KRContext::KRENGINE_MIN_TEXTURE_DIM = 64; - KRContext::KRENGINE_MAX_TEXTURE_THROUGHPUT = 32000000; } else { KRContext::KRENGINE_MAX_VBO_HANDLES = 10000; KRContext::KRENGINE_MAX_VBO_MEM = 128000000; @@ -127,7 +124,6 @@ void kraken::set_debug_text(const std::string &print_text) KRContext::KRENGINE_TARGET_TEXTURE_MEM_MAX = 48000000; KRContext::KRENGINE_MAX_TEXTURE_DIM = 2048; KRContext::KRENGINE_MIN_TEXTURE_DIM = 64; - KRContext::KRENGINE_MAX_TEXTURE_THROUGHPUT = 32000000; } */ #else @@ -139,7 +135,6 @@ void kraken::set_debug_text(const std::string &print_text) KRContext::KRENGINE_TARGET_TEXTURE_MEM_MAX = 192000000; KRContext::KRENGINE_MAX_TEXTURE_DIM = 8192; KRContext::KRENGINE_MIN_TEXTURE_DIM = 64; - KRContext::KRENGINE_MAX_TEXTURE_THROUGHPUT = 128000000; KRContext::KRENGINE_PRESTREAM_DISTANCE = 1000.0f; #endif diff --git a/KREngine/kraken/KRLight.cpp b/KREngine/kraken/KRLight.cpp index 4d531ca..226df8e 100644 --- a/KREngine/kraken/KRLight.cpp +++ b/KREngine/kraken/KRLight.cpp @@ -230,7 +230,7 @@ void KRLight::render(KRCamera *pCamera, std::vector &point_light pParticleShader->setUniform(KRShader::KRENGINE_UNIFORM_FLARE_SIZE, m_dust_particle_size); KRDataBlock particle_index_data; - m_pContext->getModelManager()->bindVBO(m_pContext->getModelManager()->getRandomParticles(), particle_index_data, (1 << KRMesh::KRENGINE_ATTRIB_VERTEX) | (1 << KRMesh::KRENGINE_ATTRIB_TEXUVA), true); + m_pContext->getMeshManager()->bindVBO(m_pContext->getMeshManager()->getRandomParticles(), particle_index_data, (1 << KRMesh::KRENGINE_ATTRIB_VERTEX) | (1 << KRMesh::KRENGINE_ATTRIB_TEXUVA), true); GLDEBUG(glDrawArrays(GL_TRIANGLES, 0, particle_count*3)); } } @@ -271,7 +271,7 @@ void KRLight::render(KRCamera *pCamera, std::vector &point_light pFogShader->setUniform(KRShader::KRENGINE_UNIFORM_LIGHT_COLOR, (m_color * pCamera->settings.volumetric_environment_intensity * m_intensity * -slice_spacing / 1000.0f)); KRDataBlock index_data; - m_pContext->getModelManager()->bindVBO(m_pContext->getModelManager()->getVolumetricLightingVertexes(), index_data, (1 << KRMesh::KRENGINE_ATTRIB_VERTEX), true); + m_pContext->getMeshManager()->bindVBO(m_pContext->getMeshManager()->getVolumetricLightingVertexes(), index_data, (1 << KRMesh::KRENGINE_ATTRIB_VERTEX), true); GLDEBUG(glDrawArrays(GL_TRIANGLES, 0, slice_count*6)); } @@ -298,7 +298,7 @@ void KRLight::render(KRCamera *pCamera, std::vector &point_light GLDEBUG(glBeginQuery(GL_SAMPLES_PASSED, m_occlusionQuery)); #endif - std::vector sphereModels = getContext().getModelManager()->getModel("__sphere"); + std::vector sphereModels = getContext().getMeshManager()->getModel("__sphere"); if(sphereModels.size()) { for(int i=0; i < sphereModels[0]->getSubmeshCount(); i++) { sphereModels[0]->renderSubmesh(i, renderPass, getName(), "occlusion_test"); @@ -341,7 +341,7 @@ void KRLight::render(KRCamera *pCamera, std::vector &point_light pShader->setUniform(KRShader::KRENGINE_UNIFORM_MATERIAL_ALPHA, 1.0f); pShader->setUniform(KRShader::KRENGINE_UNIFORM_FLARE_SIZE, m_flareSize); m_pContext->getTextureManager()->selectTexture(0, m_pFlareTexture, 0.0f, KRTexture::TEXTURE_USAGE_LIGHT_FLARE); - m_pContext->getModelManager()->bindVBO(getContext().getModelManager()->KRENGINE_VBO_2D_SQUARE_VERTICES, getContext().getModelManager()->KRENGINE_VBO_2D_SQUARE_INDEXES, getContext().getModelManager()->KRENGINE_VBO_2D_SQUARE_ATTRIBS, true); + m_pContext->getMeshManager()->bindVBO(getContext().getMeshManager()->KRENGINE_VBO_2D_SQUARE_VERTICES, getContext().getMeshManager()->KRENGINE_VBO_2D_SQUARE_INDEXES, getContext().getMeshManager()->KRENGINE_VBO_2D_SQUARE_ATTRIBS, true); GLDEBUG(glDrawArrays(GL_TRIANGLE_STRIP, 0, 4)); } } diff --git a/KREngine/kraken/KRMesh.cpp b/KREngine/kraken/KRMesh.cpp index 8358bde..ab3a99f 100644 --- a/KREngine/kraken/KRMesh.cpp +++ b/KREngine/kraken/KRMesh.cpp @@ -342,15 +342,15 @@ void KRMesh::renderSubmesh(int iSubmesh, KRNode::RenderPass renderPass, const st } vbo_index++; - //m_pContext->getModelManager()->bindVBO((unsigned char *)pVertexData + start_vertex_offset * m_vertex_size, vertex_count * m_vertex_size, index_data + start_index_offset, index_count * 2, vertex_attrib_flags, true); - m_pContext->getModelManager()->bindVBO(*vertex_data_block, *index_data_block, vertex_attrib_flags, true); + //m_pContext->getMeshManager()->bindVBO((unsigned char *)pVertexData + start_vertex_offset * m_vertex_size, vertex_count * m_vertex_size, index_data + start_index_offset, index_count * 2, vertex_attrib_flags, true); + m_pContext->getMeshManager()->bindVBO(*vertex_data_block, *index_data_block, vertex_attrib_flags, true); int vertex_draw_count = cVertexes; if(vertex_draw_count > index_count - index_group_offset) vertex_draw_count = index_count - index_group_offset; glDrawElements(GL_TRIANGLES, vertex_draw_count, GL_UNSIGNED_SHORT, BUFFER_OFFSET(index_group_offset * 2)); - m_pContext->getModelManager()->log_draw_call(renderPass, object_name, material_name, vertex_draw_count); + m_pContext->getMeshManager()->log_draw_call(renderPass, object_name, material_name, vertex_draw_count); cVertexes -= vertex_draw_count; index_group_offset = 0; } @@ -375,8 +375,8 @@ void KRMesh::renderSubmesh(int iSubmesh, KRNode::RenderPass renderPass, const st } vbo_index++; - //m_pContext->getModelManager()->bindVBO((unsigned char *)pVertexData + iBuffer * MAX_VBO_SIZE * vertex_size, vertex_size * cBufferVertexes, NULL, 0, vertex_attrib_flags, true); - m_pContext->getModelManager()->bindVBO(*vertex_data_block, *index_data_block, vertex_attrib_flags, true); + //m_pContext->getMeshManager()->bindVBO((unsigned char *)pVertexData + iBuffer * MAX_VBO_SIZE * vertex_size, vertex_size * cBufferVertexes, NULL, 0, vertex_attrib_flags, true); + m_pContext->getMeshManager()->bindVBO(*vertex_data_block, *index_data_block, vertex_attrib_flags, true); if(iVertex + cVertexes >= MAX_VBO_SIZE) { @@ -397,7 +397,7 @@ void KRMesh::renderSubmesh(int iSubmesh, KRNode::RenderPass renderPass, const st default: break; } - m_pContext->getModelManager()->log_draw_call(renderPass, object_name, material_name, (MAX_VBO_SIZE - iVertex)); + m_pContext->getMeshManager()->log_draw_call(renderPass, object_name, material_name, (MAX_VBO_SIZE - iVertex)); cVertexes -= (MAX_VBO_SIZE - iVertex); iVertex = 0; @@ -415,7 +415,7 @@ void KRMesh::renderSubmesh(int iSubmesh, KRNode::RenderPass renderPass, const st default: break; } - m_pContext->getModelManager()->log_draw_call(renderPass, object_name, material_name, cVertexes); + m_pContext->getMeshManager()->log_draw_call(renderPass, object_name, material_name, cVertexes); cVertexes = 0; } diff --git a/KREngine/kraken/KRMeshManager.cpp b/KREngine/kraken/KRMeshManager.cpp index 68b5c53..a18b01e 100644 --- a/KREngine/kraken/KRMeshManager.cpp +++ b/KREngine/kraken/KRMeshManager.cpp @@ -38,7 +38,7 @@ #include "KRMeshQuad.h" #include "KRMeshSphere.h" -KRMeshManager::KRMeshManager(KRContext &context) : KRContextObject(context), m_streamer(context) { +KRMeshManager::KRMeshManager(KRContext &context) : KRContextObject(context) { m_currentVBO.vbo_handle = -1; m_currentVBO.vbo_handle_indexes = -1; m_currentVBO.vao_handle = -1; @@ -526,3 +526,8 @@ std::vector KRMeshManager::getDrawCalls() m_draw_call_log_used = true; return m_draw_calls; } + +void KRMeshManager::doStreaming(long &memoryRemaining, long &memoryRemainingThisFrame) +{ + +} diff --git a/KREngine/kraken/KRMeshManager.h b/KREngine/kraken/KRMeshManager.h index 2783617..661276f 100644 --- a/KREngine/kraken/KRMeshManager.h +++ b/KREngine/kraken/KRMeshManager.h @@ -37,8 +37,6 @@ #include "KRDataBlock.h" #include "KRNode.h" -#include "KRMeshStreamer.h" - class KRContext; class KRMesh; @@ -116,6 +114,9 @@ public: KRDataBlock KRENGINE_VBO_2D_SQUARE_VERTICES, KRENGINE_VBO_2D_SQUARE_INDEXES; __int32_t KRENGINE_VBO_2D_SQUARE_ATTRIBS; + + void doStreaming(long &memoryRemaining, long &memoryRemainingThisFrame); + private: unordered_multimap m_models; // Multiple models with the same name/key may be inserted, representing multiple LOD levels of the model @@ -141,8 +142,6 @@ private: std::vector m_draw_calls; bool m_draw_call_logging_enabled; bool m_draw_call_log_used; - - KRMeshStreamer m_streamer; }; diff --git a/KREngine/kraken/KRModel.cpp b/KREngine/kraken/KRModel.cpp index 58b1cdb..421b83f 100644 --- a/KREngine/kraken/KRModel.cpp +++ b/KREngine/kraken/KRModel.cpp @@ -118,7 +118,7 @@ std::string KRModel::getLightMap() void KRModel::loadModel() { if(m_models.size() == 0) { - std::vector 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 + std::vector models = m_pContext->getMeshManager()->getModel(m_model_name.c_str()); // The model manager returns the LOD levels in sorted order, with the highest detail first unordered_map > bones; if(models.size() > 0) { bool all_bones_found = true; diff --git a/KREngine/kraken/KRParticleSystemNewtonian.cpp b/KREngine/kraken/KRParticleSystemNewtonian.cpp index 8426600..2b4ef3d 100644 --- a/KREngine/kraken/KRParticleSystemNewtonian.cpp +++ b/KREngine/kraken/KRParticleSystemNewtonian.cpp @@ -79,9 +79,9 @@ void KRParticleSystemNewtonian::render(KRCamera *pCamera, std::vectorselectShader(*pCamera, pParticleShader, viewport, getModelMatrix(), point_lights, directional_lights, spot_lights, 0, renderPass, rim_color, 0.0f)) { pParticleShader->setUniform(KRShader::KRENGINE_UNIFORM_FLARE_SIZE, 1.0f); - //m_pContext->getModelManager()->bindVBO((void *)m_pContext->getModelManager()->getRandomParticles(), particle_count * 3 * sizeof(KRMeshManager::RandomParticleVertexData), NULL, 0, (1 << KRMesh::KRENGINE_ATTRIB_VERTEX) | (1 << KRMesh::KRENGINE_ATTRIB_TEXUVA), false); + //m_pContext->getMeshManager()->bindVBO((void *)m_pContext->getMeshManager()->getRandomParticles(), particle_count * 3 * sizeof(KRMeshManager::RandomParticleVertexData), NULL, 0, (1 << KRMesh::KRENGINE_ATTRIB_VERTEX) | (1 << KRMesh::KRENGINE_ATTRIB_TEXUVA), false); KRDataBlock index_data; - m_pContext->getModelManager()->bindVBO(m_pContext->getModelManager()->getRandomParticles(), index_data, (1 << KRMesh::KRENGINE_ATTRIB_VERTEX) | (1 << KRMesh::KRENGINE_ATTRIB_TEXUVA), false); + m_pContext->getMeshManager()->bindVBO(m_pContext->getMeshManager()->getRandomParticles(), index_data, (1 << KRMesh::KRENGINE_ATTRIB_VERTEX) | (1 << KRMesh::KRENGINE_ATTRIB_TEXUVA), false); GLDEBUG(glDrawArrays(GL_TRIANGLES, 0, particle_count*3)); } } @@ -109,7 +109,7 @@ void KRParticleSystemNewtonian::render(KRCamera *pCamera, std::vectorgetModelManager()->bindVBO((void *)m_pContext->getModelManager()->getRandomParticles(), particle_count * 3 * sizeof(KRMeshManager::RandomParticleVertexData), NULL, 0, (1 << KRMesh::KRENGINE_ATTRIB_VERTEX) | (1 << KRMesh::KRENGINE_ATTRIB_TEXUVA), false); +// m_pContext->getMeshManager()->bindVBO((void *)m_pContext->getMeshManager()->getRandomParticles(), particle_count * 3 * sizeof(KRMeshManager::RandomParticleVertexData), NULL, 0, (1 << KRMesh::KRENGINE_ATTRIB_VERTEX) | (1 << KRMesh::KRENGINE_ATTRIB_TEXUVA), false); // GLDEBUG(glDrawArrays(GL_TRIANGLES, 0, particle_count*3)); // } //// } diff --git a/KREngine/kraken/KRPointLight.cpp b/KREngine/kraken/KRPointLight.cpp index bd9355a..9ea31be 100644 --- a/KREngine/kraken/KRPointLight.cpp +++ b/KREngine/kraken/KRPointLight.cpp @@ -97,13 +97,13 @@ void KRPointLight::render(KRCamera *pCamera, std::vector &point_ GLDEBUG(glDisable(GL_DEPTH_TEST)); // Render a full screen quad - m_pContext->getModelManager()->bindVBO(getContext().getModelManager()->KRENGINE_VBO_2D_SQUARE_VERTICES, getContext().getModelManager()->KRENGINE_VBO_2D_SQUARE_INDEXES, getContext().getModelManager()->KRENGINE_VBO_2D_SQUARE_ATTRIBS, true); + m_pContext->getMeshManager()->bindVBO(getContext().getMeshManager()->KRENGINE_VBO_2D_SQUARE_VERTICES, getContext().getMeshManager()->KRENGINE_VBO_2D_SQUARE_INDEXES, getContext().getMeshManager()->KRENGINE_VBO_2D_SQUARE_ATTRIBS, true); GLDEBUG(glDrawArrays(GL_TRIANGLE_STRIP, 0, 4)); } else { #if GL_OES_vertex_array_object GLDEBUG(glBindVertexArrayOES(0)); #endif - m_pContext->getModelManager()->configureAttribs(1 << KRMesh::KRENGINE_ATTRIB_VERTEX); + m_pContext->getMeshManager()->configureAttribs(1 << KRMesh::KRENGINE_ATTRIB_VERTEX); // Render sphere of light's influence generateMesh(); diff --git a/KREngine/kraken/KRResource+fbx.cpp b/KREngine/kraken/KRResource+fbx.cpp index 823bdec..9b34901 100644 --- a/KREngine/kraken/KRResource+fbx.cpp +++ b/KREngine/kraken/KRResource+fbx.cpp @@ -1544,7 +1544,7 @@ void LoadMesh(KRContext &context, FbxScene* pFbxScene, FbxGeometryConverter *pGe KRMesh *new_mesh = new KRMesh(context, pSourceMesh->GetNode()->GetName()); new_mesh->LoadData(mi, true, need_tangents); - context.getModelManager()->addModel(new_mesh); + context.getMeshManager()->addModel(new_mesh); } KRNode *LoadMesh(KRNode *parent_node, FbxScene* pFbxScene, FbxGeometryConverter *pGeometryConverter, FbxNode* pNode) { diff --git a/KREngine/kraken/KRReverbZone.cpp b/KREngine/kraken/KRReverbZone.cpp index 577316e..2a64ee6 100644 --- a/KREngine/kraken/KRReverbZone.cpp +++ b/KREngine/kraken/KRReverbZone.cpp @@ -112,7 +112,7 @@ void KRReverbZone::render(KRCamera *pCamera, std::vector &point_ GLDEBUG(glEnable(GL_DEPTH_TEST)); GLDEBUG(glDepthFunc(GL_LEQUAL)); GLDEBUG(glDepthRangef(0.0, 1.0)); - std::vector sphereModels = getContext().getModelManager()->getModel("__sphere"); + std::vector sphereModels = getContext().getMeshManager()->getModel("__sphere"); if(sphereModels.size()) { for(int i=0; i < sphereModels[0]->getSubmeshCount(); i++) { sphereModels[0]->renderSubmesh(i, renderPass, getName(), "visualize_overlay"); diff --git a/KREngine/kraken/KRScene.cpp b/KREngine/kraken/KRScene.cpp index df92172..1425356 100644 --- a/KREngine/kraken/KRScene.cpp +++ b/KREngine/kraken/KRScene.cpp @@ -281,7 +281,7 @@ void KRScene::render(KROctreeNode *pOctreeNode, unordered_map &visi KRMat4 mvpmatrix = matModel * viewport.getViewProjectionMatrix(); - getContext().getModelManager()->bindVBO(getContext().getModelManager()->KRENGINE_VBO_3D_CUBE_VERTICES, getContext().getModelManager()->KRENGINE_VBO_3D_CUBE_INDEXES, getContext().getModelManager()->KRENGINE_VBO_3D_CUBE_ATTRIBS, true); + getContext().getMeshManager()->bindVBO(getContext().getMeshManager()->KRENGINE_VBO_3D_CUBE_VERTICES, getContext().getMeshManager()->KRENGINE_VBO_3D_CUBE_INDEXES, getContext().getMeshManager()->KRENGINE_VBO_3D_CUBE_ATTRIBS, true); // Enable additive blending if(renderPass != KRNode::RENDER_PASS_FORWARD_TRANSPARENT && renderPass != KRNode::RENDER_PASS_ADDITIVE_PARTICLES && renderPass != KRNode::RENDER_PASS_VOLUMETRIC_EFFECTS_ADDITIVE) { @@ -301,7 +301,7 @@ void KRScene::render(KROctreeNode *pOctreeNode, unordered_map &visi if(getContext().getShaderManager()->selectShader("occlusion_test", *pCamera, point_lights, directional_lights, spot_lights, 0, viewport, matModel, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, KRNode::RENDER_PASS_FORWARD_TRANSPARENT, KRVector3::Zero(), 0.0f)) { GLDEBUG(glDrawArrays(GL_TRIANGLE_STRIP, 0, 14)); - m_pContext->getModelManager()->log_draw_call(renderPass, "octree", "occlusion_test", 14); + m_pContext->getMeshManager()->log_draw_call(renderPass, "octree", "occlusion_test", 14); } if(renderPass == KRNode::RENDER_PASS_FORWARD_OPAQUE || diff --git a/KREngine/kraken/KRSprite.cpp b/KREngine/kraken/KRSprite.cpp index eebabcc..8d2efba 100644 --- a/KREngine/kraken/KRSprite.cpp +++ b/KREngine/kraken/KRSprite.cpp @@ -121,7 +121,7 @@ void KRSprite::render(KRCamera *pCamera, std::vector &point_ligh if(getContext().getShaderManager()->selectShader(*pCamera, pShader, viewport, getModelMatrix(), point_lights, directional_lights, spot_lights, 0, renderPass, rim_color, 0.0f)) { pShader->setUniform(KRShader::KRENGINE_UNIFORM_MATERIAL_ALPHA, m_spriteAlpha); m_pContext->getTextureManager()->selectTexture(0, m_pSpriteTexture, 0.0f, KRTexture::TEXTURE_USAGE_SPRITE); - m_pContext->getModelManager()->bindVBO(getContext().getModelManager()->KRENGINE_VBO_2D_SQUARE_VERTICES, getContext().getModelManager()->KRENGINE_VBO_2D_SQUARE_INDEXES, getContext().getModelManager()->KRENGINE_VBO_2D_SQUARE_ATTRIBS, true); + m_pContext->getMeshManager()->bindVBO(getContext().getMeshManager()->KRENGINE_VBO_2D_SQUARE_VERTICES, getContext().getMeshManager()->KRENGINE_VBO_2D_SQUARE_INDEXES, getContext().getMeshManager()->KRENGINE_VBO_2D_SQUARE_ATTRIBS, true); GLDEBUG(glDrawArrays(GL_TRIANGLE_STRIP, 0, 4)); } } diff --git a/KREngine/kraken/KRTextureManager.cpp b/KREngine/kraken/KRTextureManager.cpp index ab131be..c9fdcc7 100644 --- a/KREngine/kraken/KRTextureManager.cpp +++ b/KREngine/kraken/KRTextureManager.cpp @@ -40,7 +40,7 @@ #include "KRTextureAnimated.h" #include "KRContext.h" -KRTextureManager::KRTextureManager(KRContext &context) : KRContextObject(context), m_streamer(context) { +KRTextureManager::KRTextureManager(KRContext &context) : KRContextObject(context) { m_textureMemUsed = 0; for(int iTexture=0; iTexture 0) { - balanceTextureMemory(); + balanceTextureMemory(memoryRemaining, memoryRemainingThisFrame); m_streamerFenceMutex.lock(); m_streamerComplete = true; @@ -311,7 +311,7 @@ void KRTextureManager::doStreaming() } } -void KRTextureManager::balanceTextureMemory() +void KRTextureManager::balanceTextureMemory(long &memoryRemaining, long &memoryRemainingThisFrame) { // Balance texture memory by reducing and increasing the maximum mip-map level of both active and inactive textures // Favour performance over maximum texture resolution when memory is insufficient for textures at full resolution. @@ -336,9 +336,6 @@ void KRTextureManager::balanceTextureMemory() std::sort(m_activeTextures_streamer.begin(), m_activeTextures_streamer.end(), std::greater>()); - long memoryRemaining = getContext().KRENGINE_TARGET_TEXTURE_MEM_MAX; - long memoryRemainingThisFrame = getContext().KRENGINE_MAX_TEXTURE_MEM - getMemUsed(); - for(auto itr=m_activeTextures_streamer.begin(); itr != m_activeTextures_streamer.end(); itr++) { KRTexture *texture = (*itr).second; int min_mip_level = KRMAX(getContext().KRENGINE_MIN_TEXTURE_DIM, texture->getMinMipMap()); diff --git a/KREngine/kraken/KRTextureManager.h b/KREngine/kraken/KRTextureManager.h index 02360d0..fdc81db 100644 --- a/KREngine/kraken/KRTextureManager.h +++ b/KREngine/kraken/KRTextureManager.h @@ -39,7 +39,7 @@ #include "KREngine-common.h" #include "KRDataBlock.h" #include "KRContext.h" -#include "KRTextureStreamer.h" +#include "KRStreamer.h" class KRTextureManager : public KRContextObject { public: @@ -77,7 +77,7 @@ public: void _clearGLState(); void setMaxAnisotropy(float max_anisotropy); - void doStreaming(); + void doStreaming(long &memoryRemaining, long &memoryRemainingThisFrame); void primeTexture(KRTexture *texture); private: @@ -102,9 +102,7 @@ private: std::atomic m_textureMemUsed; void rotateBuffers(); - void balanceTextureMemory(); - - KRTextureStreamer m_streamer; + void balanceTextureMemory(long &memoryRemaining, long &memoryRemainingThisFrame); std::mutex m_streamerFenceMutex; }; From dc9bec276692f3760d3a7596094c0090cf00da39 Mon Sep 17 00:00:00 2001 From: Kearwood Gilbert Date: Tue, 13 May 2014 22:01:19 -0700 Subject: [PATCH 80/84] Refactoring of streamer code to integrate texture and vbo memory management in progress. --HG-- branch : nfb --- KREngine/kraken/KRMeshStreamer.h | 60 ------------ KREngine/kraken/KRMeshStreamer.mm | 93 ------------------- .../{KRTextureStreamer.h => KRStreamer.h} | 12 +-- .../{KRTextureStreamer.mm => KRStreamer.mm} | 22 ++--- 4 files changed, 16 insertions(+), 171 deletions(-) delete mode 100644 KREngine/kraken/KRMeshStreamer.h delete mode 100644 KREngine/kraken/KRMeshStreamer.mm rename KREngine/kraken/{KRTextureStreamer.h => KRStreamer.h} (90%) rename KREngine/kraken/{KRTextureStreamer.mm => KRStreamer.mm} (79%) diff --git a/KREngine/kraken/KRMeshStreamer.h b/KREngine/kraken/KRMeshStreamer.h deleted file mode 100644 index beb3666..0000000 --- a/KREngine/kraken/KRMeshStreamer.h +++ /dev/null @@ -1,60 +0,0 @@ -// -// KRMeshManager.h -// KREngine -// -// Copyright 2012 Kearwood Gilbert. All rights reserved. -// -// Redistribution and use in source and binary forms, with or without modification, are -// permitted provided that the following conditions are met: -// -// 1. Redistributions of source code must retain the above copyright notice, this list of -// conditions and the following disclaimer. -// -// 2. Redistributions in binary form must reproduce the above copyright notice, this list -// of conditions and the following disclaimer in the documentation and/or other materials -// provided with the distribution. -// -// THIS SOFTWARE IS PROVIDED BY KEARWOOD GILBERT ''AS IS'' AND ANY EXPRESS OR IMPLIED -// WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND -// FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL KEARWOOD GILBERT OR -// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR -// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON -// ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF -// ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -// -// The views and conclusions contained in the software and documentation are those of the -// authors and should not be interpreted as representing official policies, either expressed -// or implied, of Kearwood Gilbert. -// - -#ifndef KRMESHSTREAMER_H -#define KRMESHSTREAMER_H - -#include "KREngine-common.h" - -#include -#include - -class KRContext; - -class KRMeshStreamer -{ -public: - KRMeshStreamer(KRContext &context); - ~KRMeshStreamer(); - - void startStreamer(); - -private: - KRContext &m_context; - - std::thread m_thread; - std::atomic m_stop; - std::atomic m_running; - - void run(); -}; - -#endif /* defined(KRMESHSTREAMER_H) */ diff --git a/KREngine/kraken/KRMeshStreamer.mm b/KREngine/kraken/KRMeshStreamer.mm deleted file mode 100644 index 96e2a7b..0000000 --- a/KREngine/kraken/KRMeshStreamer.mm +++ /dev/null @@ -1,93 +0,0 @@ -// -// KRMeshStreamer.cpp -// Kraken -// -// Created by Kearwood Gilbert on 11/1/2013. -// Copyright (c) 2013 Kearwood Software. All rights reserved. -// - -#include "KRMeshStreamer.h" - -#include "KREngine-common.h" -#include "KRContext.h" - -#include - -#if TARGET_OS_IPHONE - -EAGLContext *gMeshStreamerContext = nil; - -#elif TARGET_OS_MAC - -NSOpenGLContext *gMeshStreamerContext = nil; - -#else - -#error Unsupported Platform -#endif - -KRMeshStreamer::KRMeshStreamer(KRContext &context) : m_context(context) -{ - m_running = false; - m_stop = false; -} - -void KRMeshStreamer::startStreamer() -{ - if(!m_running) { - m_running = true; - -#if TARGET_OS_IPHONE - // FIXME: need to add code check for iOS 7 and also this appears to cause crashing - gMeshStreamerContext = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES2 sharegroup: [EAGLContext currentContext].sharegroup]; - //gMeshStreamerContext.multiThreaded = TRUE; -#elif TARGET_OS_MAC - NSOpenGLPixelFormatAttribute pixelFormatAttributes[] = - { -// NSOpenGLPFAOpenGLProfile, NSOpenGLProfileVersionLegacy, - 0 - }; - NSOpenGLPixelFormat *pixelFormat = [[[NSOpenGLPixelFormat alloc] initWithAttributes:pixelFormatAttributes] autorelease]; - gMeshStreamerContext = [[NSOpenGLContext alloc] initWithFormat: pixelFormat shareContext: [NSOpenGLContext currentContext] ]; -#else - #error Unsupported Platform -#endif - - m_thread = std::thread(&KRMeshStreamer::run, this); - } -} - -KRMeshStreamer::~KRMeshStreamer() -{ - if(m_running) { - m_stop = true; - m_thread.join(); - m_running = false; - } - - [gMeshStreamerContext release]; -} - -void KRMeshStreamer::run() -{ - pthread_setname_np("Kraken - Mesh Streamer"); - - std::chrono::microseconds sleep_duration( 100 ); - -#if TARGET_OS_IPHONE - [EAGLContext setCurrentContext: gMeshStreamerContext]; -#elif TARGET_OS_MAC - [gMeshStreamerContext makeCurrentContext]; -#else - #error Unsupported Platform -#endif - - - while(!m_stop) - { - if(m_context.getStreamingEnabled()) { - - } - std::this_thread::sleep_for( sleep_duration ); - } -} diff --git a/KREngine/kraken/KRTextureStreamer.h b/KREngine/kraken/KRStreamer.h similarity index 90% rename from KREngine/kraken/KRTextureStreamer.h rename to KREngine/kraken/KRStreamer.h index 39f3705..af6d545 100644 --- a/KREngine/kraken/KRTextureStreamer.h +++ b/KREngine/kraken/KRStreamer.h @@ -29,8 +29,8 @@ // or implied, of Kearwood Gilbert. // -#ifndef KRTEXTURESTREAMER_H -#define KRTEXTURESTREAMER_H +#ifndef KRSTREAMER_H +#define KRSTREAMER_H #include "KREngine-common.h" @@ -39,11 +39,11 @@ class KRContext; -class KRTextureStreamer +class KRStreamer { public: - KRTextureStreamer(KRContext &context); - ~KRTextureStreamer(); + KRStreamer(KRContext &context); + ~KRStreamer(); void startStreamer(); @@ -57,4 +57,4 @@ private: void run(); }; -#endif /* defined(KRTEXTURESTREAMER_H) */ +#endif /* defined(KRSTREAMER_H) */ diff --git a/KREngine/kraken/KRTextureStreamer.mm b/KREngine/kraken/KRStreamer.mm similarity index 79% rename from KREngine/kraken/KRTextureStreamer.mm rename to KREngine/kraken/KRStreamer.mm index 81dd5df..a7a79f0 100644 --- a/KREngine/kraken/KRTextureStreamer.mm +++ b/KREngine/kraken/KRStreamer.mm @@ -1,5 +1,5 @@ // -// KRTextureStreamer.cpp +// KRStreamer.cpp // Kraken // // Created by Kearwood Gilbert on 11/1/2013. @@ -8,7 +8,7 @@ #include "KREngine-common.h" -#include "KRTextureStreamer.h" +#include "KRStreamer.h" #include "KRContext.h" #include @@ -27,13 +27,13 @@ NSOpenGLContext *gTextureStreamerContext = nil; #error Unsupported Platform #endif -KRTextureStreamer::KRTextureStreamer(KRContext &context) : m_context(context) +KRStreamer::KRStreamer(KRContext &context) : m_context(context) { m_running = false; m_stop = false; } -void KRTextureStreamer::startStreamer() +void KRStreamer::startStreamer() { if(!m_running) { m_running = true; @@ -61,11 +61,11 @@ void KRTextureStreamer::startStreamer() #error Unsupported Platform #endif - m_thread = std::thread(&KRTextureStreamer::run, this); + m_thread = std::thread(&KRStreamer::run, this); } } -KRTextureStreamer::~KRTextureStreamer() +KRStreamer::~KRStreamer() { if(m_running) { m_stop = true; @@ -76,10 +76,10 @@ KRTextureStreamer::~KRTextureStreamer() [gTextureStreamerContext release]; } -void KRTextureStreamer::run() +void KRStreamer::run() { - pthread_setname_np("Kraken - Texture Streamer"); - + pthread_setname_np("Kraken - Streamer"); + std::chrono::microseconds sleep_duration( 100 ); #if TARGET_OS_IPHONE @@ -92,9 +92,7 @@ void KRTextureStreamer::run() while(!m_stop) { - if(m_context.getStreamingEnabled()) { - m_context.getTextureManager()->doStreaming(); - } + m_context.doStreaming(); std::this_thread::sleep_for( sleep_duration ); } } From e96c48b59ebc4d695d8767760780116218eb41d3 Mon Sep 17 00:00:00 2001 From: Kearwood Gilbert Date: Tue, 13 May 2014 23:49:03 -0700 Subject: [PATCH 81/84] Refactoring of streamer code to integrate texture and vbo memory management in progress. --HG-- branch : nfb --- KREngine/kraken/KRMeshManager.cpp | 258 +++++++++++++++++------------- KREngine/kraken/KRMeshManager.h | 39 +++-- 2 files changed, 171 insertions(+), 126 deletions(-) diff --git a/KREngine/kraken/KRMeshManager.cpp b/KREngine/kraken/KRMeshManager.cpp index a18b01e..b550e2b 100644 --- a/KREngine/kraken/KRMeshManager.cpp +++ b/KREngine/kraken/KRMeshManager.cpp @@ -39,10 +39,7 @@ #include "KRMeshSphere.h" KRMeshManager::KRMeshManager(KRContext &context) : KRContextObject(context) { - m_currentVBO.vbo_handle = -1; - m_currentVBO.vbo_handle_indexes = -1; - m_currentVBO.vao_handle = -1; - m_currentVBO.data = NULL; + m_currentVBO = NULL; m_vboMemUsed = 0; m_memoryTransferredThisFrame = 0; @@ -140,24 +137,22 @@ unordered_multimap &KRMeshManager::getModels() { } void KRMeshManager::unbindVBO() { - if(m_currentVBO.data != NULL) { + if(m_currentVBO != NULL) { GLDEBUG(glBindBuffer(GL_ARRAY_BUFFER, 0)); GLDEBUG(glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0)); - m_currentVBO.size = 0; - m_currentVBO.data = NULL; - m_currentVBO.vbo_handle = -1; - m_currentVBO.vbo_handle_indexes = -1; - m_currentVBO.vao_handle = -1; + m_currentVBO = NULL; } } void KRMeshManager::releaseVBO(KRDataBlock &data) { - if(m_currentVBO.data == &data) { - unbindVBO(); + if(m_currentVBO) { + if(m_currentVBO->m_data == &data) { + unbindVBO(); + } } - vbo_info_type vbo_to_release; + KRVBOData *vbo_to_release = NULL; if(m_vbosActive.find(&data) != m_vbosActive.end()) { KRContext::Log(KRContext::LOG_LEVEL_WARNING, "glFinish called due to releasing a VBO that is active in the current frame."); GLDEBUG(glFinish()); @@ -171,49 +166,40 @@ void KRMeshManager::releaseVBO(KRDataBlock &data) m_vbosPool.erase(&data); } - m_vboMemUsed -= vbo_to_release.size; - -#if GL_OES_vertex_array_object - GLDEBUG(glDeleteVertexArraysOES(1, &vbo_to_release.vao_handle)); -#endif - GLDEBUG(glDeleteBuffers(1, &vbo_to_release.vbo_handle)); - if(vbo_to_release.vbo_handle_indexes != -1) { - GLDEBUG(glDeleteBuffers(1, &vbo_to_release.vbo_handle_indexes)); + if(vbo_to_release) { + m_vboMemUsed -= vbo_to_release->m_size; + + vbo_to_release->unload(); + delete vbo_to_release; } } -void KRMeshManager::bindVBO(KRDataBlock &data, KRDataBlock &index_data, int vertex_attrib_flags, bool static_vbo) { - if(m_currentVBO.data != &data) { + +void KRMeshManager::bindVBO(KRDataBlock &data, KRDataBlock &index_data, int vertex_attrib_flags, bool static_vbo) +{ + bool vbo_changed = false; + if(m_currentVBO == NULL) { + vbo_changed = true; + } else if(m_currentVBO->m_data != &data) { + vbo_changed = true; + } + + if(vbo_changed) { if(m_vbosActive.find(&data) != m_vbosActive.end()) { m_currentVBO = m_vbosActive[&data]; -#if GL_OES_vertex_array_object - GLDEBUG(glBindVertexArrayOES(m_currentVBO.vao_handle)); -#else - GLDEBUG(glBindBuffer(GL_ARRAY_BUFFER, m_currentVBO.vbo_handle)); - configureAttribs(vertex_attrib_flags); - if(m_currentVBO.vbo_handle_indexes == -1) { - GLDEBUG(glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0)); - } else { - GLDEBUG(glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, m_currentVBO.vbo_handle_indexes)); - } -#endif + + m_currentVBO->bind(); + } else if(m_vbosPool.find(&data) != m_vbosPool.end()) { m_currentVBO = m_vbosPool[&data]; m_vbosPool.erase(&data); m_vbosActive[&data] = m_currentVBO; -#if GL_OES_vertex_array_object - GLDEBUG(glBindVertexArrayOES(m_currentVBO.vao_handle)); -#else - GLDEBUG(glBindBuffer(GL_ARRAY_BUFFER, m_currentVBO.vbo_handle)); - configureAttribs(vertex_attrib_flags); - if(m_currentVBO.vbo_handle_indexes == -1) { - GLDEBUG(glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0)); - } else { - GLDEBUG(glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, m_currentVBO.vbo_handle_indexes)); - } -#endif + + m_currentVBO->bind(); + + } else { @@ -222,75 +208,22 @@ void KRMeshManager::bindVBO(KRDataBlock &data, KRDataBlock &index_data, int vert KRContext::Log(KRContext::LOG_LEVEL_WARNING, "flushBuffers due to VBO exhaustion..."); m_pContext->rotateBuffers(false); } - unordered_map::iterator first_itr = m_vbosPool.begin(); - vbo_info_type firstVBO = first_itr->second; -#if GL_OES_vertex_array_object - GLDEBUG(glDeleteVertexArraysOES(1, &firstVBO.vao_handle)); -#endif - GLDEBUG(glDeleteBuffers(1, &firstVBO.vbo_handle)); - if(firstVBO.vbo_handle_indexes != -1) { - GLDEBUG(glDeleteBuffers(1, &firstVBO.vbo_handle_indexes)); - } - m_vboMemUsed -= firstVBO.size; + unordered_map::iterator first_itr = m_vbosPool.begin(); + KRVBOData *firstVBO = first_itr->second; m_vbosPool.erase(first_itr); + + m_vboMemUsed -= firstVBO->m_size; + firstVBO->unload(); + + delete firstVBO; // fprintf(stderr, "VBO Swapping...\n"); } - m_currentVBO.vao_handle = -1; - m_currentVBO.vbo_handle = -1; - m_currentVBO.vbo_handle_indexes = -1; - GLDEBUG(glGenBuffers(1, &m_currentVBO.vbo_handle)); - if(index_data.getSize() > 0) { - GLDEBUG(glGenBuffers(1, &m_currentVBO.vbo_handle_indexes)); - } + m_currentVBO = new KRVBOData(); -#if GL_OES_vertex_array_object - GLDEBUG(glGenVertexArraysOES(1, &m_currentVBO.vao_handle)); - GLDEBUG(glBindVertexArrayOES(m_currentVBO.vao_handle)); -#endif - - GLDEBUG(glBindBuffer(GL_ARRAY_BUFFER, m_currentVBO.vbo_handle)); -#if GL_OES_mapbuffer - - GLDEBUG(glBufferData(GL_ARRAY_BUFFER, data.getSize(), NULL, static_vbo ? GL_STATIC_DRAW : GL_DYNAMIC_DRAW)); - GLDEBUG(void *map_ptr = glMapBufferOES(GL_ARRAY_BUFFER, GL_WRITE_ONLY_OES)); - data.copy(map_ptr); - //memcpy(map_ptr, data, size); - GLDEBUG(glUnmapBufferOES(GL_ARRAY_BUFFER)); -#else - data.lock(); - GLDEBUG(glBufferData(GL_ARRAY_BUFFER, data.getSize(), data.getStart(), static_vbo ? GL_STATIC_DRAW : GL_DYNAMIC_DRAW)); - data.unlock(); -#endif - m_memoryTransferredThisFrame += data.getSize(); - m_vboMemUsed += data.getSize(); - configureAttribs(vertex_attrib_flags); - - m_currentVBO.size = data.getSize(); - m_currentVBO.data = &data; - - if(index_data.getSize() == 0) { - GLDEBUG(glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0)); - } else { - GLDEBUG(glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, m_currentVBO.vbo_handle_indexes)); - -#if GL_OES_mapbuffer - - GLDEBUG(glBufferData(GL_ELEMENT_ARRAY_BUFFER, index_data.getSize(), NULL, static_vbo ? GL_STATIC_DRAW : GL_DYNAMIC_DRAW)); - GLDEBUG(void *map_ptr = glMapBufferOES(GL_ELEMENT_ARRAY_BUFFER, GL_WRITE_ONLY_OES)); - index_data.copy(map_ptr); - //memcpy(map_ptr, index_data, index_data.getSize()); - GLDEBUG(glUnmapBufferOES(GL_ELEMENT_ARRAY_BUFFER)); -#else - index_data.lock(); - GLDEBUG(glBufferData(GL_ELEMENT_ARRAY_BUFFER, index_data.getSize(), index_data.getStart(), static_vbo ? GL_STATIC_DRAW : GL_DYNAMIC_DRAW)); - index_data.unlock(); -#endif - - m_memoryTransferredThisFrame += index_data.getSize(); - m_vboMemUsed += index_data.getSize(); - m_currentVBO.size += index_data.getSize(); - } + m_currentVBO->load(data, index_data, vertex_attrib_flags, static_vbo); + m_memoryTransferredThisFrame += m_currentVBO->m_size; + m_vboMemUsed += m_currentVBO->m_size; m_vbosActive[&data] = m_currentVBO; } @@ -374,8 +307,8 @@ long KRMeshManager::getMemUsed() long KRMeshManager::getMemActive() { long mem_active = 0; - for(unordered_map::iterator itr = m_vbosActive.begin(); itr != m_vbosActive.end(); itr++) { - mem_active += (*itr).second.size; + for(unordered_map::iterator itr = m_vbosActive.begin(); itr != m_vbosActive.end(); itr++) { + mem_active += (*itr).second->m_size; } return mem_active; } @@ -384,12 +317,11 @@ void KRMeshManager::rotateBuffers(bool new_frame) { m_vbosPool.insert(m_vbosActive.begin(), m_vbosActive.end()); m_vbosActive.clear(); - if(m_currentVBO.data != NULL) { + if(m_currentVBO != NULL) { // Ensure that the currently active VBO does not get flushed to free memory - m_vbosPool.erase(m_currentVBO.data); - m_vbosActive[m_currentVBO.data] = m_currentVBO; + m_vbosPool.erase(m_currentVBO->m_data); + m_vbosActive[m_currentVBO->m_data] = m_currentVBO; } - } KRDataBlock &KRMeshManager::getVolumetricLightingVertexes() @@ -531,3 +463,99 @@ void KRMeshManager::doStreaming(long &memoryRemaining, long &memoryRemainingThis { } + +KRMeshManager::KRVBOData::KRVBOData() +{ + m_vbo_handle = -1; + m_vbo_handle_indexes = -1; + m_vao_handle = -1; + m_data = NULL; +} + +KRMeshManager::KRVBOData::~KRVBOData() +{ + +} + +void KRMeshManager::KRVBOData::unload() +{ +#if GL_OES_vertex_array_object + GLDEBUG(glDeleteVertexArraysOES(1, &m_vao_handle)); +#endif + GLDEBUG(glDeleteBuffers(1, &m_vbo_handle)); + if(m_vbo_handle_indexes != -1) { + GLDEBUG(glDeleteBuffers(1, &m_vbo_handle_indexes)); + } +} + +void KRMeshManager::KRVBOData::bind() +{ +#if GL_OES_vertex_array_object + GLDEBUG(glBindVertexArrayOES(m_vao_handle)); +#else + GLDEBUG(glBindBuffer(GL_ARRAY_BUFFER, m_vbo_handle)); + KRMeshManager::configureAttribs(m_vertex_attrib_flags); + if(m_vbo_handle_indexes == -1) { + GLDEBUG(glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0)); + } else { + GLDEBUG(glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, m_vbo_handle_indexes)); + } +#endif +} + +void KRMeshManager::KRVBOData::load(KRDataBlock &data, KRDataBlock &index_data, int vertex_attrib_flags, bool static_vbo) +{ + m_vertex_attrib_flags = vertex_attrib_flags; + m_vao_handle = -1; + m_vbo_handle = -1; + m_vbo_handle_indexes = -1; + + GLDEBUG(glGenBuffers(1, &m_vbo_handle)); + if(index_data.getSize() > 0) { + GLDEBUG(glGenBuffers(1, &m_vbo_handle_indexes)); + } + + + +#if GL_OES_vertex_array_object + GLDEBUG(glGenVertexArraysOES(1, &m_vao_handle)); + GLDEBUG(glBindVertexArrayOES(m_vao_handle)); +#endif + + GLDEBUG(glBindBuffer(GL_ARRAY_BUFFER, m_vbo_handle)); +#if GL_OES_mapbuffer + + GLDEBUG(glBufferData(GL_ARRAY_BUFFER, data.getSize(), NULL, static_vbo ? GL_STATIC_DRAW : GL_DYNAMIC_DRAW)); + GLDEBUG(void *map_ptr = glMapBufferOES(GL_ARRAY_BUFFER, GL_WRITE_ONLY_OES)); + data.copy(map_ptr); + GLDEBUG(glUnmapBufferOES(GL_ARRAY_BUFFER)); +#else + data.lock(); + GLDEBUG(glBufferData(GL_ARRAY_BUFFER, data.getSize(), data.getStart(), static_vbo ? GL_STATIC_DRAW : GL_DYNAMIC_DRAW)); + data.unlock(); +#endif + + configureAttribs(vertex_attrib_flags); + + m_size = data.getSize(); + m_data = &data; + + if(index_data.getSize() == 0) { + GLDEBUG(glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0)); + } else { + GLDEBUG(glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, m_vbo_handle_indexes)); + +#if GL_OES_mapbuffer + + GLDEBUG(glBufferData(GL_ELEMENT_ARRAY_BUFFER, index_data.getSize(), NULL, static_vbo ? GL_STATIC_DRAW : GL_DYNAMIC_DRAW)); + GLDEBUG(void *map_ptr = glMapBufferOES(GL_ELEMENT_ARRAY_BUFFER, GL_WRITE_ONLY_OES)); + index_data.copy(map_ptr); + GLDEBUG(glUnmapBufferOES(GL_ELEMENT_ARRAY_BUFFER)); +#else + index_data.lock(); + GLDEBUG(glBufferData(GL_ELEMENT_ARRAY_BUFFER, index_data.getSize(), index_data.getStart(), static_vbo ? GL_STATIC_DRAW : GL_DYNAMIC_DRAW)); + index_data.unlock(); +#endif + m_size += index_data.getSize(); + } +} diff --git a/KREngine/kraken/KRMeshManager.h b/KREngine/kraken/KRMeshManager.h index 661276f..eba0905 100644 --- a/KREngine/kraken/KRMeshManager.h +++ b/KREngine/kraken/KRMeshManager.h @@ -65,7 +65,7 @@ public: long getMemUsed(); long getMemActive(); - void configureAttribs(__int32_t attributes); + static void configureAttribs(__int32_t attributes); typedef struct { GLfloat x; @@ -120,19 +120,36 @@ public: private: unordered_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 vbo_handle; - GLuint vbo_handle_indexes; - GLuint vao_handle; - GLsizeiptr size; - KRDataBlock *data; - } vbo_info_type; + class KRVBOData { + + public: + + KRVBOData(); + ~KRVBOData(); + + GLsizeiptr m_size; + KRDataBlock *m_data; + + void load(KRDataBlock &data, KRDataBlock &index_data, int vertex_attrib_flags, bool static_vbo); + void unload(); + void bind(); + + // Disable copy constructors + KRVBOData(const KRVBOData& o) = delete; + KRVBOData(KRVBOData& o) = delete; + + private: + int m_vertex_attrib_flags; + GLuint m_vbo_handle; + GLuint m_vbo_handle_indexes; + GLuint m_vao_handle; + }; long m_vboMemUsed; - vbo_info_type m_currentVBO; + KRVBOData *m_currentVBO; - unordered_map m_vbosActive; - unordered_map m_vbosPool; + unordered_map m_vbosActive; + unordered_map m_vbosPool; KRDataBlock m_randomParticleVertexData; KRDataBlock m_volumetricLightingVertexData; From 9b58585b59d62636dd27282727d14e027b178471 Mon Sep 17 00:00:00 2001 From: Kearwood Gilbert Date: Thu, 15 May 2014 23:33:01 -0700 Subject: [PATCH 82/84] Refactoring of streamer code to integrate texture and vbo memory management in progress. --HG-- branch : nfb --- KREngine/kraken/KRCamera.cpp | 9 +- KREngine/kraken/KRDirectionalLight.cpp | 2 +- KREngine/kraken/KRLight.cpp | 2 +- KREngine/kraken/KRMesh.cpp | 2 - KREngine/kraken/KRMesh.h | 2 +- KREngine/kraken/KRMeshManager.cpp | 220 +++++++++++------- KREngine/kraken/KRMeshManager.h | 75 +++--- KREngine/kraken/KRParticleSystemNewtonian.cpp | 29 --- KREngine/kraken/KRPointLight.cpp | 2 +- KREngine/kraken/KRScene.cpp | 2 +- KREngine/kraken/KRSprite.cpp | 2 +- 11 files changed, 191 insertions(+), 156 deletions(-) diff --git a/KREngine/kraken/KRCamera.cpp b/KREngine/kraken/KRCamera.cpp index 823141b..d7d5cc6 100644 --- a/KREngine/kraken/KRCamera.cpp +++ b/KREngine/kraken/KRCamera.cpp @@ -321,7 +321,7 @@ void KRCamera::renderFrame(float deltaTime, GLint renderBufferWidth, GLint rende getContext().getTextureManager()->selectTexture(0, m_pSkyBoxTexture, 0.0f, KRTexture::TEXTURE_USAGE_SKY_CUBE); // Render a full screen quad - m_pContext->getMeshManager()->bindVBO(getContext().getMeshManager()->KRENGINE_VBO_2D_SQUARE_VERTICES, getContext().getMeshManager()->KRENGINE_VBO_2D_SQUARE_INDEXES, getContext().getMeshManager()->KRENGINE_VBO_2D_SQUARE_ATTRIBS, true); + m_pContext->getMeshManager()->bindVBO(&getContext().getMeshManager()->KRENGINE_VBO_DATA_2D_SQUARE_VERTICES); GLDEBUG(glDrawArrays(GL_TRIANGLE_STRIP, 0, 4)); } @@ -482,7 +482,7 @@ void KRCamera::renderFrame(float deltaTime, GLint renderBufferWidth, GLint rende KRShader *pVisShader = getContext().getShaderManager()->getShader("visualize_overlay", this, std::vector(), std::vector(), std::vector(), 0, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, KRNode::RENDER_PASS_FORWARD_TRANSPARENT); - m_pContext->getMeshManager()->bindVBO(getContext().getMeshManager()->KRENGINE_VBO_3D_CUBE_VERTICES, getContext().getMeshManager()->KRENGINE_VBO_3D_CUBE_INDEXES, getContext().getMeshManager()->KRENGINE_VBO_3D_CUBE_ATTRIBS, true); + m_pContext->getMeshManager()->bindVBO(&getContext().getMeshManager()->KRENGINE_VBO_DATA_3D_CUBE_VERTICES); for(unordered_map::iterator itr=m_viewport.getVisibleBounds().begin(); itr != m_viewport.getVisibleBounds().end(); itr++) { KRMat4 matModel = KRMat4(); matModel.scale((*itr).first.size() * 0.5f); @@ -716,7 +716,7 @@ void KRCamera::renderPost() } // Update attribute values. - m_pContext->getMeshManager()->bindVBO(getContext().getMeshManager()->KRENGINE_VBO_2D_SQUARE_VERTICES, getContext().getMeshManager()->KRENGINE_VBO_2D_SQUARE_INDEXES, getContext().getMeshManager()->KRENGINE_VBO_2D_SQUARE_ATTRIBS, true); + m_pContext->getMeshManager()->bindVBO(&getContext().getMeshManager()->KRENGINE_VBO_DATA_2D_SQUARE_VERTICES); GLDEBUG(glDrawArrays(GL_TRIANGLE_STRIP, 0, 4)); @@ -738,7 +738,7 @@ void KRCamera::renderPost() // viewMatrix.translate(-0.70, 0.70 - 0.45 * iShadow, 0.0); // getContext().getShaderManager()->selectShader(blitShader, KRViewport(getViewportSize(), viewMatrix, KRMat4()), shadowViewports, KRMat4(), KRVector3(), NULL, 0, KRNode::RENDER_PASS_FORWARD_TRANSPARENT); // m_pContext->getTextureManager()->selectTexture(1, NULL); -// m_pContext->getMeshManager()->bindVBO(KRENGINE_VBO_2D_SQUARE_INDICES, KRENGINE_VBO_2D_SQUARE_VERTEXES, KRENGINE_VBO_2D_SQUARE_ATTRIBS, true); +// m_pContext->getMeshManager()->bindVBO(&getContext().getMeshManager()->KRENGINE_VBO_DATA_2D_SQUARE_VERTICES); // m_pContext->getTextureManager()->_setActiveTexture(0); // GLDEBUG(glBindTexture(GL_TEXTURE_2D, shadowDepthTexture[iShadow])); //#if GL_EXT_shadow_samplers @@ -907,7 +907,6 @@ void KRCamera::renderPost() m_pContext->getTextureManager()->selectTexture(0, m_pContext->getTextureManager()->getTexture("font"), 0.0f, KRTexture::TEXTURE_USAGE_UI); KRDataBlock index_data; - //m_pContext->getMeshManager()->bindVBO((void *)m_debug_text_vertices, vertex_count * sizeof(DebugTextVertexData), NULL, 0, (1 << KRMesh::KRENGINE_ATTRIB_VERTEX) | (1 << KRMesh::KRENGINE_ATTRIB_TEXUVA), true); m_pContext->getMeshManager()->bindVBO(m_debug_text_vertices, index_data, (1 << KRMesh::KRENGINE_ATTRIB_VERTEX) | (1 << KRMesh::KRENGINE_ATTRIB_TEXUVA), true); GLDEBUG(glDrawArrays(GL_TRIANGLES, 0, vertex_count)); diff --git a/KREngine/kraken/KRDirectionalLight.cpp b/KREngine/kraken/KRDirectionalLight.cpp index b3de30d..94b86b9 100644 --- a/KREngine/kraken/KRDirectionalLight.cpp +++ b/KREngine/kraken/KRDirectionalLight.cpp @@ -123,7 +123,7 @@ void KRDirectionalLight::render(KRCamera *pCamera, std::vector & GLDEBUG(glDisable(GL_DEPTH_TEST)); // Render a full screen quad - m_pContext->getMeshManager()->bindVBO(getContext().getMeshManager()->KRENGINE_VBO_2D_SQUARE_VERTICES, getContext().getMeshManager()->KRENGINE_VBO_2D_SQUARE_INDEXES, getContext().getMeshManager()->KRENGINE_VBO_2D_SQUARE_ATTRIBS, true); + m_pContext->getMeshManager()->bindVBO(&getContext().getMeshManager()->KRENGINE_VBO_DATA_2D_SQUARE_VERTICES); GLDEBUG(glDrawArrays(GL_TRIANGLE_STRIP, 0, 4)); } } diff --git a/KREngine/kraken/KRLight.cpp b/KREngine/kraken/KRLight.cpp index 226df8e..5f4b026 100644 --- a/KREngine/kraken/KRLight.cpp +++ b/KREngine/kraken/KRLight.cpp @@ -341,7 +341,7 @@ void KRLight::render(KRCamera *pCamera, std::vector &point_light pShader->setUniform(KRShader::KRENGINE_UNIFORM_MATERIAL_ALPHA, 1.0f); pShader->setUniform(KRShader::KRENGINE_UNIFORM_FLARE_SIZE, m_flareSize); m_pContext->getTextureManager()->selectTexture(0, m_pFlareTexture, 0.0f, KRTexture::TEXTURE_USAGE_LIGHT_FLARE); - m_pContext->getMeshManager()->bindVBO(getContext().getMeshManager()->KRENGINE_VBO_2D_SQUARE_VERTICES, getContext().getMeshManager()->KRENGINE_VBO_2D_SQUARE_INDEXES, getContext().getMeshManager()->KRENGINE_VBO_2D_SQUARE_ATTRIBS, true); + m_pContext->getMeshManager()->bindVBO(&getContext().getMeshManager()->KRENGINE_VBO_DATA_2D_SQUARE_VERTICES); GLDEBUG(glDrawArrays(GL_TRIANGLE_STRIP, 0, 4)); } } diff --git a/KREngine/kraken/KRMesh.cpp b/KREngine/kraken/KRMesh.cpp index ab3a99f..61e58d0 100644 --- a/KREngine/kraken/KRMesh.cpp +++ b/KREngine/kraken/KRMesh.cpp @@ -342,7 +342,6 @@ void KRMesh::renderSubmesh(int iSubmesh, KRNode::RenderPass renderPass, const st } vbo_index++; - //m_pContext->getMeshManager()->bindVBO((unsigned char *)pVertexData + start_vertex_offset * m_vertex_size, vertex_count * m_vertex_size, index_data + start_index_offset, index_count * 2, vertex_attrib_flags, true); m_pContext->getMeshManager()->bindVBO(*vertex_data_block, *index_data_block, vertex_attrib_flags, true); @@ -375,7 +374,6 @@ void KRMesh::renderSubmesh(int iSubmesh, KRNode::RenderPass renderPass, const st } vbo_index++; - //m_pContext->getMeshManager()->bindVBO((unsigned char *)pVertexData + iBuffer * MAX_VBO_SIZE * vertex_size, vertex_size * cBufferVertexes, NULL, 0, vertex_attrib_flags, true); m_pContext->getMeshManager()->bindVBO(*vertex_data_block, *index_data_block, vertex_attrib_flags, true); diff --git a/KREngine/kraken/KRMesh.h b/KREngine/kraken/KRMesh.h index 4e426d7..d29de7f 100644 --- a/KREngine/kraken/KRMesh.h +++ b/KREngine/kraken/KRMesh.h @@ -121,7 +121,7 @@ public: virtual bool save(const std::string& path); virtual bool save(KRDataBlock &data); - void LoadData(/*std::vector<__uint16_t> vertex_indexes, std::vector > vertex_index_bases, std::vector vertices, std::vector uva, std::vector uvb, std::vector normals, std::vector tangents, std::vector submesh_starts, std::vector submesh_lengths, std::vector material_names, std::vector bone_names, std::vector bone_bind_poses, std::vector > bone_indexes, std::vector > bone_weights, model_format_t model_format, */const mesh_info &mi, bool calculate_normals, bool calculate_tangents); + void LoadData(const mesh_info &mi, bool calculate_normals, bool calculate_tangents); void loadPack(KRDataBlock *data); void convertToIndexed(); diff --git a/KREngine/kraken/KRMeshManager.cpp b/KREngine/kraken/KRMeshManager.cpp index b550e2b..fb3a765 100644 --- a/KREngine/kraken/KRMeshManager.cpp +++ b/KREngine/kraken/KRMeshManager.cpp @@ -76,6 +76,10 @@ KRMeshManager::KRMeshManager(KRContext &context) : KRContextObject(context) { memcpy(KRENGINE_VBO_3D_CUBE_VERTICES.getStart(), _KRENGINE_VBO_3D_CUBE_VERTEX_DATA, sizeof(GLfloat) * 3 * 14); KRENGINE_VBO_3D_CUBE_VERTICES.unlock(); + KRENGINE_VBO_DATA_3D_CUBE_VERTICES.init(KRENGINE_VBO_3D_CUBE_VERTICES, KRENGINE_VBO_3D_CUBE_INDEXES, KRENGINE_VBO_3D_CUBE_ATTRIBS, false, false); + + + static const GLfloat _KRENGINE_VBO_2D_SQUARE_VERTEX_DATA[] = { -1.0f, -1.0f, 0.0f, 0.0f, 0.0f, 1.0f, -1.0f, 0.0f, 1.0f, 0.0f, @@ -87,6 +91,9 @@ KRMeshManager::KRMeshManager(KRContext &context) : KRContextObject(context) { KRENGINE_VBO_2D_SQUARE_VERTICES.lock(); memcpy(KRENGINE_VBO_2D_SQUARE_VERTICES.getStart(), _KRENGINE_VBO_2D_SQUARE_VERTEX_DATA, sizeof(GLfloat) * 5 * 4); KRENGINE_VBO_2D_SQUARE_VERTICES.unlock(); + + KRENGINE_VBO_DATA_2D_SQUARE_VERTICES.init(KRENGINE_VBO_2D_SQUARE_VERTICES, KRENGINE_VBO_2D_SQUARE_INDEXES, KRENGINE_VBO_2D_SQUARE_ATTRIBS, false, false); + } KRMeshManager::~KRMeshManager() { @@ -167,35 +174,37 @@ void KRMeshManager::releaseVBO(KRDataBlock &data) } if(vbo_to_release) { - m_vboMemUsed -= vbo_to_release->m_size; + m_vboMemUsed -= vbo_to_release->getSize(); vbo_to_release->unload(); - delete vbo_to_release; + if(vbo_to_release->isTemporary()) { + delete vbo_to_release; + } } } - - -void KRMeshManager::bindVBO(KRDataBlock &data, KRDataBlock &index_data, int vertex_attrib_flags, bool static_vbo) +void KRMeshManager::bindVBO(KRVBOData *vbo_data) { bool vbo_changed = false; if(m_currentVBO == NULL) { vbo_changed = true; - } else if(m_currentVBO->m_data != &data) { + } else if(m_currentVBO->m_data != vbo_data->m_data) { vbo_changed = true; } + bool used_vbo_data = false; + if(vbo_changed) { - if(m_vbosActive.find(&data) != m_vbosActive.end()) { - m_currentVBO = m_vbosActive[&data]; + if(m_vbosActive.find(vbo_data->m_data) != m_vbosActive.end()) { + m_currentVBO = m_vbosActive[vbo_data->m_data]; m_currentVBO->bind(); - } else if(m_vbosPool.find(&data) != m_vbosPool.end()) { - m_currentVBO = m_vbosPool[&data]; - m_vbosPool.erase(&data); - m_vbosActive[&data] = m_currentVBO; + } else if(m_vbosPool.find(vbo_data->m_data) != m_vbosPool.end()) { + m_currentVBO = m_vbosPool[vbo_data->m_data]; + m_vbosPool.erase(vbo_data->m_data); + m_vbosActive[vbo_data->m_data] = m_currentVBO; m_currentVBO->bind(); @@ -203,7 +212,7 @@ void KRMeshManager::bindVBO(KRDataBlock &data, KRDataBlock &index_data, int vert } else { - while(m_vbosPool.size() + m_vbosActive.size() + 1 >= KRContext::KRENGINE_MAX_VBO_HANDLES || m_vboMemUsed + data.getSize() + index_data.getSize() >= KRContext::KRENGINE_MAX_VBO_MEM) { + while(m_vbosPool.size() + m_vbosActive.size() + 1 >= KRContext::KRENGINE_MAX_VBO_HANDLES || m_vboMemUsed + vbo_data->getSize() >= KRContext::KRENGINE_MAX_VBO_MEM) { if(m_vbosPool.empty()) { KRContext::Log(KRContext::LOG_LEVEL_WARNING, "flushBuffers due to VBO exhaustion..."); m_pContext->rotateBuffers(false); @@ -212,22 +221,34 @@ void KRMeshManager::bindVBO(KRDataBlock &data, KRDataBlock &index_data, int vert KRVBOData *firstVBO = first_itr->second; m_vbosPool.erase(first_itr); - m_vboMemUsed -= firstVBO->m_size; + m_vboMemUsed -= firstVBO->getSize(); firstVBO->unload(); - - delete firstVBO; + if(firstVBO->isTemporary()) { + delete firstVBO; + } // fprintf(stderr, "VBO Swapping...\n"); } - m_currentVBO = new KRVBOData(); + used_vbo_data = true; + m_currentVBO = vbo_data; - m_currentVBO->load(data, index_data, vertex_attrib_flags, static_vbo); - m_memoryTransferredThisFrame += m_currentVBO->m_size; - m_vboMemUsed += m_currentVBO->m_size; + m_currentVBO->load(); + m_memoryTransferredThisFrame += m_currentVBO->getSize(); + m_vboMemUsed += m_currentVBO->getSize(); - m_vbosActive[&data] = m_currentVBO; + m_vbosActive[vbo_data->m_data] = m_currentVBO; } } + + if(!used_vbo_data && vbo_data->isTemporary()) { + delete vbo_data; + } +} + +void KRMeshManager::bindVBO(KRDataBlock &data, KRDataBlock &index_data, int vertex_attrib_flags, bool static_vbo) +{ + KRVBOData *vbo_data = new KRVBOData(data, index_data, vertex_attrib_flags, static_vbo, true); + bindVBO(vbo_data); } void KRMeshManager::configureAttribs(__int32_t attributes) @@ -308,7 +329,7 @@ long KRMeshManager::getMemActive() { long mem_active = 0; for(unordered_map::iterator itr = m_vbosActive.begin(); itr != m_vbosActive.end(); itr++) { - mem_active += (*itr).second->m_size; + mem_active += (*itr).second->getSize(); } return mem_active; } @@ -466,10 +487,38 @@ void KRMeshManager::doStreaming(long &memoryRemaining, long &memoryRemainingThis KRMeshManager::KRVBOData::KRVBOData() { + m_temp_vbo = false; + m_static_vbo = false; + m_data = NULL; + m_index_data = NULL; + m_vertex_attrib_flags = 0; m_vbo_handle = -1; m_vbo_handle_indexes = -1; m_vao_handle = -1; - m_data = NULL; + m_size = 0; +} + +KRMeshManager::KRVBOData::KRVBOData(KRDataBlock &data, KRDataBlock &index_data, int vertex_attrib_flags, bool static_vbo, bool temp_vbo) +{ + init(data,index_data,vertex_attrib_flags, static_vbo, temp_vbo); +} + +void KRMeshManager::KRVBOData::init(KRDataBlock &data, KRDataBlock &index_data, int vertex_attrib_flags, bool static_vbo, bool temp_vbo) +{ + m_temp_vbo = temp_vbo; + m_static_vbo = static_vbo; + m_data = &data; + m_index_data = &index_data; + m_vertex_attrib_flags = vertex_attrib_flags; + + m_vbo_handle = -1; + m_vbo_handle_indexes = -1; + m_vao_handle = -1; + + m_size = m_data->getSize(); + if(m_index_data != NULL) { + m_size += m_index_data->getSize(); + } } KRMeshManager::KRVBOData::~KRVBOData() @@ -477,14 +526,76 @@ KRMeshManager::KRVBOData::~KRVBOData() } + + +void KRMeshManager::KRVBOData::load() +{ + m_vao_handle = -1; + m_vbo_handle = -1; + m_vbo_handle_indexes = -1; + + GLDEBUG(glGenBuffers(1, &m_vbo_handle)); + if(m_index_data->getSize() > 0) { + GLDEBUG(glGenBuffers(1, &m_vbo_handle_indexes)); + } + + + +#if GL_OES_vertex_array_object + GLDEBUG(glGenVertexArraysOES(1, &m_vao_handle)); + GLDEBUG(glBindVertexArrayOES(m_vao_handle)); +#endif + + GLDEBUG(glBindBuffer(GL_ARRAY_BUFFER, m_vbo_handle)); +#if GL_OES_mapbuffer + + GLDEBUG(glBufferData(GL_ARRAY_BUFFER, m_data->getSize(), NULL, m_static_vbo ? GL_STATIC_DRAW : GL_DYNAMIC_DRAW)); + GLDEBUG(void *map_ptr = glMapBufferOES(GL_ARRAY_BUFFER, GL_WRITE_ONLY_OES)); + m_data->copy(map_ptr); + GLDEBUG(glUnmapBufferOES(GL_ARRAY_BUFFER)); +#else + m_data->lock(); + GLDEBUG(glBufferData(GL_ARRAY_BUFFER, m_data->getSize(), m_data->getStart(), m_static_vbo ? GL_STATIC_DRAW : GL_DYNAMIC_DRAW)); + m_data->unlock(); +#endif + + configureAttribs(m_vertex_attrib_flags); + + if(m_index_data->getSize() == 0) { + GLDEBUG(glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0)); + } else { + GLDEBUG(glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, m_vbo_handle_indexes)); + +#if GL_OES_mapbuffer + + GLDEBUG(glBufferData(GL_ELEMENT_ARRAY_BUFFER, m_index_data->getSize(), NULL, m_static_vbo ? GL_STATIC_DRAW : GL_DYNAMIC_DRAW)); + GLDEBUG(void *map_ptr = glMapBufferOES(GL_ELEMENT_ARRAY_BUFFER, GL_WRITE_ONLY_OES)); + m_index_data->copy(map_ptr); + GLDEBUG(glUnmapBufferOES(GL_ELEMENT_ARRAY_BUFFER)); +#else + m_index_data->lock(); + GLDEBUG(glBufferData(GL_ELEMENT_ARRAY_BUFFER, m_index_data->getSize(), m_index_data->getStart(), m_static_vbo ? GL_STATIC_DRAW : GL_DYNAMIC_DRAW)); + m_index_data->unlock(); +#endif + } +} + void KRMeshManager::KRVBOData::unload() { #if GL_OES_vertex_array_object - GLDEBUG(glDeleteVertexArraysOES(1, &m_vao_handle)); + if(m_vao_handle != -1) { + GLDEBUG(glDeleteVertexArraysOES(1, &m_vao_handle)); + m_vao_handle = -1; + } #endif - GLDEBUG(glDeleteBuffers(1, &m_vbo_handle)); + if(m_vbo_handle != -1) { + GLDEBUG(glDeleteBuffers(1, &m_vbo_handle)); + m_vbo_handle = -1; + } + if(m_vbo_handle_indexes != -1) { GLDEBUG(glDeleteBuffers(1, &m_vbo_handle_indexes)); + m_vbo_handle_indexes = -1; } } @@ -501,61 +612,4 @@ void KRMeshManager::KRVBOData::bind() GLDEBUG(glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, m_vbo_handle_indexes)); } #endif -} - -void KRMeshManager::KRVBOData::load(KRDataBlock &data, KRDataBlock &index_data, int vertex_attrib_flags, bool static_vbo) -{ - m_vertex_attrib_flags = vertex_attrib_flags; - m_vao_handle = -1; - m_vbo_handle = -1; - m_vbo_handle_indexes = -1; - - GLDEBUG(glGenBuffers(1, &m_vbo_handle)); - if(index_data.getSize() > 0) { - GLDEBUG(glGenBuffers(1, &m_vbo_handle_indexes)); - } - - - -#if GL_OES_vertex_array_object - GLDEBUG(glGenVertexArraysOES(1, &m_vao_handle)); - GLDEBUG(glBindVertexArrayOES(m_vao_handle)); -#endif - - GLDEBUG(glBindBuffer(GL_ARRAY_BUFFER, m_vbo_handle)); -#if GL_OES_mapbuffer - - GLDEBUG(glBufferData(GL_ARRAY_BUFFER, data.getSize(), NULL, static_vbo ? GL_STATIC_DRAW : GL_DYNAMIC_DRAW)); - GLDEBUG(void *map_ptr = glMapBufferOES(GL_ARRAY_BUFFER, GL_WRITE_ONLY_OES)); - data.copy(map_ptr); - GLDEBUG(glUnmapBufferOES(GL_ARRAY_BUFFER)); -#else - data.lock(); - GLDEBUG(glBufferData(GL_ARRAY_BUFFER, data.getSize(), data.getStart(), static_vbo ? GL_STATIC_DRAW : GL_DYNAMIC_DRAW)); - data.unlock(); -#endif - - configureAttribs(vertex_attrib_flags); - - m_size = data.getSize(); - m_data = &data; - - if(index_data.getSize() == 0) { - GLDEBUG(glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0)); - } else { - GLDEBUG(glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, m_vbo_handle_indexes)); - -#if GL_OES_mapbuffer - - GLDEBUG(glBufferData(GL_ELEMENT_ARRAY_BUFFER, index_data.getSize(), NULL, static_vbo ? GL_STATIC_DRAW : GL_DYNAMIC_DRAW)); - GLDEBUG(void *map_ptr = glMapBufferOES(GL_ELEMENT_ARRAY_BUFFER, GL_WRITE_ONLY_OES)); - index_data.copy(map_ptr); - GLDEBUG(glUnmapBufferOES(GL_ELEMENT_ARRAY_BUFFER)); -#else - index_data.lock(); - GLDEBUG(glBufferData(GL_ELEMENT_ARRAY_BUFFER, index_data.getSize(), index_data.getStart(), static_vbo ? GL_STATIC_DRAW : GL_DYNAMIC_DRAW)); - index_data.unlock(); -#endif - m_size += index_data.getSize(); - } -} +} \ No newline at end of file diff --git a/KREngine/kraken/KRMeshManager.h b/KREngine/kraken/KRMeshManager.h index eba0905..5e08c74 100644 --- a/KREngine/kraken/KRMeshManager.h +++ b/KREngine/kraken/KRMeshManager.h @@ -59,6 +59,42 @@ public: std::vector getModelNames(); unordered_multimap &getModels(); + class KRVBOData { + + public: + + KRVBOData(); + KRVBOData(KRDataBlock &data, KRDataBlock &index_data, int vertex_attrib_flags, bool static_vbo, bool temp_vbo); + void init(KRDataBlock &data, KRDataBlock &index_data, int vertex_attrib_flags, bool static_vbo, bool temp_vbo); + ~KRVBOData(); + + + KRDataBlock *m_data; + KRDataBlock *m_index_data; + + + void load(); + void unload(); + void bind(); + + // Disable copy constructors + KRVBOData(const KRVBOData& o) = delete; + KRVBOData(KRVBOData& o) = delete; + + bool isTemporary() { return m_temp_vbo; } + bool getSize() { return m_size; } + + private: + int m_vertex_attrib_flags; + GLuint m_vbo_handle; + GLuint m_vbo_handle_indexes; + GLuint m_vao_handle; + bool m_static_vbo; + bool m_temp_vbo; + GLsizeiptr m_size; + }; + + void bindVBO(KRVBOData *vbo_data); void bindVBO(KRDataBlock &data, KRDataBlock &index_data, int vertex_attrib_flags, bool static_vbo); void releaseVBO(KRDataBlock &data); void unbindVBO(); @@ -108,42 +144,19 @@ public: std::vector getDrawCalls(); - KRDataBlock KRENGINE_VBO_3D_CUBE_VERTICES, KRENGINE_VBO_3D_CUBE_INDEXES; - __int32_t KRENGINE_VBO_3D_CUBE_ATTRIBS; - - KRDataBlock KRENGINE_VBO_2D_SQUARE_VERTICES, KRENGINE_VBO_2D_SQUARE_INDEXES; - __int32_t KRENGINE_VBO_2D_SQUARE_ATTRIBS; - + + KRVBOData KRENGINE_VBO_DATA_3D_CUBE_VERTICES; + KRVBOData KRENGINE_VBO_DATA_2D_SQUARE_VERTICES; void doStreaming(long &memoryRemaining, long &memoryRemainingThisFrame); private: - unordered_multimap m_models; // Multiple models with the same name/key may be inserted, representing multiple LOD levels of the model + KRDataBlock KRENGINE_VBO_3D_CUBE_VERTICES, KRENGINE_VBO_3D_CUBE_INDEXES; + __int32_t KRENGINE_VBO_3D_CUBE_ATTRIBS; + KRDataBlock KRENGINE_VBO_2D_SQUARE_VERTICES, KRENGINE_VBO_2D_SQUARE_INDEXES; + __int32_t KRENGINE_VBO_2D_SQUARE_ATTRIBS; - class KRVBOData { - - public: - - KRVBOData(); - ~KRVBOData(); - - GLsizeiptr m_size; - KRDataBlock *m_data; - - void load(KRDataBlock &data, KRDataBlock &index_data, int vertex_attrib_flags, bool static_vbo); - void unload(); - void bind(); - - // Disable copy constructors - KRVBOData(const KRVBOData& o) = delete; - KRVBOData(KRVBOData& o) = delete; - - private: - int m_vertex_attrib_flags; - GLuint m_vbo_handle; - GLuint m_vbo_handle_indexes; - GLuint m_vao_handle; - }; + unordered_multimap m_models; // Multiple models with the same name/key may be inserted, representing multiple LOD levels of the model long m_vboMemUsed; KRVBOData *m_currentVBO; diff --git a/KREngine/kraken/KRParticleSystemNewtonian.cpp b/KREngine/kraken/KRParticleSystemNewtonian.cpp index 2b4ef3d..4df6832 100644 --- a/KREngine/kraken/KRParticleSystemNewtonian.cpp +++ b/KREngine/kraken/KRParticleSystemNewtonian.cpp @@ -79,7 +79,6 @@ void KRParticleSystemNewtonian::render(KRCamera *pCamera, std::vectorselectShader(*pCamera, pParticleShader, viewport, getModelMatrix(), point_lights, directional_lights, spot_lights, 0, renderPass, rim_color, 0.0f)) { pParticleShader->setUniform(KRShader::KRENGINE_UNIFORM_FLARE_SIZE, 1.0f); - //m_pContext->getMeshManager()->bindVBO((void *)m_pContext->getMeshManager()->getRandomParticles(), particle_count * 3 * sizeof(KRMeshManager::RandomParticleVertexData), NULL, 0, (1 << KRMesh::KRENGINE_ATTRIB_VERTEX) | (1 << KRMesh::KRENGINE_ATTRIB_TEXUVA), false); KRDataBlock index_data; m_pContext->getMeshManager()->bindVBO(m_pContext->getMeshManager()->getRandomParticles(), index_data, (1 << KRMesh::KRENGINE_ATTRIB_VERTEX) | (1 << KRMesh::KRENGINE_ATTRIB_TEXUVA), false); GLDEBUG(glDrawArrays(GL_TRIANGLES, 0, particle_count*3)); @@ -87,31 +86,3 @@ void KRParticleSystemNewtonian::render(KRCamera *pCamera, std::vectorselectShader(pParticleShader, m_viewport, particleModelMatrix, lightDirection, shadowmvpmatrix, shadowDepthTexture, m_cShadowBuffers, KRNode::RENDER_PASS_ADDITIVE_PARTICLES)) { -// GLDEBUG(glUniform1f( -// pParticleShader->m_uniforms[KRShader::KRENGINE_UNIFORM_FLARE_SIZE], -// 1.0f -// )); -// -// m_pContext->getMeshManager()->bindVBO((void *)m_pContext->getMeshManager()->getRandomParticles(), particle_count * 3 * sizeof(KRMeshManager::RandomParticleVertexData), NULL, 0, (1 << KRMesh::KRENGINE_ATTRIB_VERTEX) | (1 << KRMesh::KRENGINE_ATTRIB_TEXUVA), false); -// GLDEBUG(glDrawArrays(GL_TRIANGLES, 0, particle_count*3)); -// } -//// } -//// } -//// } \ No newline at end of file diff --git a/KREngine/kraken/KRPointLight.cpp b/KREngine/kraken/KRPointLight.cpp index 9ea31be..21883a8 100644 --- a/KREngine/kraken/KRPointLight.cpp +++ b/KREngine/kraken/KRPointLight.cpp @@ -97,7 +97,7 @@ void KRPointLight::render(KRCamera *pCamera, std::vector &point_ GLDEBUG(glDisable(GL_DEPTH_TEST)); // Render a full screen quad - m_pContext->getMeshManager()->bindVBO(getContext().getMeshManager()->KRENGINE_VBO_2D_SQUARE_VERTICES, getContext().getMeshManager()->KRENGINE_VBO_2D_SQUARE_INDEXES, getContext().getMeshManager()->KRENGINE_VBO_2D_SQUARE_ATTRIBS, true); + m_pContext->getMeshManager()->bindVBO(&m_pContext->getMeshManager()->KRENGINE_VBO_DATA_2D_SQUARE_VERTICES); GLDEBUG(glDrawArrays(GL_TRIANGLE_STRIP, 0, 4)); } else { #if GL_OES_vertex_array_object diff --git a/KREngine/kraken/KRScene.cpp b/KREngine/kraken/KRScene.cpp index 1425356..5c7a199 100644 --- a/KREngine/kraken/KRScene.cpp +++ b/KREngine/kraken/KRScene.cpp @@ -281,7 +281,7 @@ void KRScene::render(KROctreeNode *pOctreeNode, unordered_map &visi KRMat4 mvpmatrix = matModel * viewport.getViewProjectionMatrix(); - getContext().getMeshManager()->bindVBO(getContext().getMeshManager()->KRENGINE_VBO_3D_CUBE_VERTICES, getContext().getMeshManager()->KRENGINE_VBO_3D_CUBE_INDEXES, getContext().getMeshManager()->KRENGINE_VBO_3D_CUBE_ATTRIBS, true); + getContext().getMeshManager()->bindVBO(&getContext().getMeshManager()->KRENGINE_VBO_DATA_3D_CUBE_VERTICES); // Enable additive blending if(renderPass != KRNode::RENDER_PASS_FORWARD_TRANSPARENT && renderPass != KRNode::RENDER_PASS_ADDITIVE_PARTICLES && renderPass != KRNode::RENDER_PASS_VOLUMETRIC_EFFECTS_ADDITIVE) { diff --git a/KREngine/kraken/KRSprite.cpp b/KREngine/kraken/KRSprite.cpp index 8d2efba..b35d028 100644 --- a/KREngine/kraken/KRSprite.cpp +++ b/KREngine/kraken/KRSprite.cpp @@ -121,7 +121,7 @@ void KRSprite::render(KRCamera *pCamera, std::vector &point_ligh if(getContext().getShaderManager()->selectShader(*pCamera, pShader, viewport, getModelMatrix(), point_lights, directional_lights, spot_lights, 0, renderPass, rim_color, 0.0f)) { pShader->setUniform(KRShader::KRENGINE_UNIFORM_MATERIAL_ALPHA, m_spriteAlpha); m_pContext->getTextureManager()->selectTexture(0, m_pSpriteTexture, 0.0f, KRTexture::TEXTURE_USAGE_SPRITE); - m_pContext->getMeshManager()->bindVBO(getContext().getMeshManager()->KRENGINE_VBO_2D_SQUARE_VERTICES, getContext().getMeshManager()->KRENGINE_VBO_2D_SQUARE_INDEXES, getContext().getMeshManager()->KRENGINE_VBO_2D_SQUARE_ATTRIBS, true); + m_pContext->getMeshManager()->bindVBO(&m_pContext->getMeshManager()->KRENGINE_VBO_DATA_2D_SQUARE_VERTICES); GLDEBUG(glDrawArrays(GL_TRIANGLE_STRIP, 0, 4)); } } From 50de07b6f6639ce9cec03dfc93a9340d8aeaf1b3 Mon Sep 17 00:00:00 2001 From: Kearwood Gilbert Date: Fri, 16 May 2014 00:03:56 -0700 Subject: [PATCH 83/84] Refactoring of streamer code to integrate texture and vbo memory management in progress. --HG-- branch : nfb --- KREngine/kraken/KRMesh.cpp | 28 +++++++++++++++------------- KREngine/kraken/KRMesh.h | 9 +++++++-- 2 files changed, 22 insertions(+), 15 deletions(-) diff --git a/KREngine/kraken/KRMesh.cpp b/KREngine/kraken/KRMesh.cpp index 61e58d0..82f57d5 100644 --- a/KREngine/kraken/KRMesh.cpp +++ b/KREngine/kraken/KRMesh.cpp @@ -329,20 +329,20 @@ void KRMesh::renderSubmesh(int iSubmesh, KRNode::RenderPass renderPass, const st int start_index_offset, start_vertex_offset, index_count, vertex_count; getIndexedRange(index_group++, start_index_offset, start_vertex_offset, index_count, vertex_count); - KRDataBlock *vertex_data_block = NULL; - KRDataBlock *index_data_block = NULL; + KRMeshManager::KRVBOData *vbo_data_block = NULL; if(m_submeshes[iSubmesh]->vertex_data_blocks.size() <= vbo_index) { - vertex_data_block = m_pData->getSubBlock(vertex_data_offset + start_vertex_offset * m_vertex_size, vertex_count * m_vertex_size); - index_data_block = m_pData->getSubBlock(index_data_offset + start_index_offset * 2, index_count * 2); + KRDataBlock *vertex_data_block = m_pData->getSubBlock(vertex_data_offset + start_vertex_offset * m_vertex_size, vertex_count * m_vertex_size); + KRDataBlock *index_data_block = m_pData->getSubBlock(index_data_offset + start_index_offset * 2, index_count * 2); + vbo_data_block = new KRMeshManager::KRVBOData(*vertex_data_block, *index_data_block, vertex_attrib_flags, true, false); m_submeshes[iSubmesh]->vertex_data_blocks.push_back(vertex_data_block); m_submeshes[iSubmesh]->index_data_blocks.push_back(index_data_block); + m_submeshes[iSubmesh]->vbo_data_blocks.push_back(vbo_data_block); } else { - vertex_data_block = m_submeshes[iSubmesh]->vertex_data_blocks[vbo_index]; - index_data_block = m_submeshes[iSubmesh]->index_data_blocks[vbo_index]; + vbo_data_block = m_submeshes[iSubmesh]->vbo_data_blocks[vbo_index]; } vbo_index++; - m_pContext->getMeshManager()->bindVBO(*vertex_data_block, *index_data_block, vertex_attrib_flags, true); + m_pContext->getMeshManager()->bindVBO(vbo_data_block); int vertex_draw_count = cVertexes; @@ -363,18 +363,20 @@ void KRMesh::renderSubmesh(int iSubmesh, KRNode::RenderPass renderPass, const st GLsizei cBufferVertexes = iBuffer < cBuffers - 1 ? MAX_VBO_SIZE : vertex_count % MAX_VBO_SIZE; int vertex_size = m_vertex_size; - KRDataBlock *vertex_data_block = NULL; - KRDataBlock *index_data_block = NULL; + + KRMeshManager::KRVBOData *vbo_data_block = NULL; if(m_submeshes[iSubmesh]->vertex_data_blocks.size() <= vbo_index) { - vertex_data_block = m_pData->getSubBlock(vertex_data_offset + iBuffer * MAX_VBO_SIZE * vertex_size, vertex_size * cBufferVertexes); - + KRDataBlock *index_data_block = NULL; + KRDataBlock *vertex_data_block = m_pData->getSubBlock(vertex_data_offset + iBuffer * MAX_VBO_SIZE * vertex_size, vertex_size * cBufferVertexes); + vbo_data_block = new KRMeshManager::KRVBOData(*vertex_data_block, *index_data_block, vertex_attrib_flags, true, false); m_submeshes[iSubmesh]->vertex_data_blocks.push_back(vertex_data_block); + m_submeshes[iSubmesh]->vbo_data_blocks.push_back(vbo_data_block); } else { - vertex_data_block = m_submeshes[iSubmesh]->vertex_data_blocks[vbo_index]; + vbo_data_block = m_submeshes[iSubmesh]->vbo_data_blocks[vbo_index]; } vbo_index++; - m_pContext->getMeshManager()->bindVBO(*vertex_data_block, *index_data_block, vertex_attrib_flags, true); + m_pContext->getMeshManager()->bindVBO(vbo_data_block); if(iVertex + cVertexes >= MAX_VBO_SIZE) { diff --git a/KREngine/kraken/KRMesh.h b/KREngine/kraken/KRMesh.h index d29de7f..d62b215 100644 --- a/KREngine/kraken/KRMesh.h +++ b/KREngine/kraken/KRMesh.h @@ -34,6 +34,7 @@ #include "KRMat4.h" #include "KRContext.h" #include "KRBone.h" +#include "KRMeshManager.h" #include "KREngine-common.h" @@ -141,10 +142,13 @@ public: public: Submesh() {}; ~Submesh() { - for(std::vector::iterator itr = vertex_data_blocks.begin(); itr != vertex_data_blocks.end(); itr++) { + for(auto itr = vbo_data_blocks.begin(); itr != vbo_data_blocks.end(); itr++) { delete (*itr); } - for(std::vector::iterator itr = index_data_blocks.begin(); itr != index_data_blocks.end(); itr++) { + for(auto itr = vertex_data_blocks.begin(); itr != vertex_data_blocks.end(); itr++) { + delete (*itr); + } + for(auto itr = index_data_blocks.begin(); itr != index_data_blocks.end(); itr++) { delete (*itr); } }; @@ -154,6 +158,7 @@ public: char szMaterialName[KRENGINE_MAX_NAME_LENGTH]; vector vertex_data_blocks; vector index_data_blocks; + vector vbo_data_blocks; }; typedef struct { From 125b8e0a044f17873bb4a8e63d3943b1ee8efa6a Mon Sep 17 00:00:00 2001 From: Kearwood Gilbert Date: Tue, 20 May 2014 23:05:43 -0700 Subject: [PATCH 84/84] Refactoring of streamer code to integrate texture and vbo memory management in progress. --HG-- branch : nfb --- KREngine/kraken/KRMeshManager.cpp | 3 +++ KREngine/kraken/KRMeshManager.h | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/KREngine/kraken/KRMeshManager.cpp b/KREngine/kraken/KRMeshManager.cpp index fb3a765..8a039d9 100644 --- a/KREngine/kraken/KRMeshManager.cpp +++ b/KREngine/kraken/KRMeshManager.cpp @@ -530,6 +530,9 @@ KRMeshManager::KRVBOData::~KRVBOData() void KRMeshManager::KRVBOData::load() { + if(isLoaded()) { + return; + } m_vao_handle = -1; m_vbo_handle = -1; m_vbo_handle_indexes = -1; diff --git a/KREngine/kraken/KRMeshManager.h b/KREngine/kraken/KRMeshManager.h index 5e08c74..dcbd584 100644 --- a/KREngine/kraken/KRMeshManager.h +++ b/KREngine/kraken/KRMeshManager.h @@ -72,7 +72,7 @@ public: KRDataBlock *m_data; KRDataBlock *m_index_data; - + bool isLoaded() { return m_vbo_handle != -1; } void load(); void unload(); void bind();