소스 검색

Implementation of DetourCrowd and DetourTileCache

Added Components:
- DynamicNavigationMesh (uses DetourTileCache)
- Obstacle (interacts with DynamicNavigationMesh)
- DetourCrowdManager (uses DetourCrowd for crowd control)
- CrowdAgent (interacts with DetourCrowdManager)
- NavArea (marks area types)

Changes:
- Refactor NavigationMesh build process
- Addition of cylinder debug rendering
- Exposed Area Type Costs in navigation

CrowdNavigation sample for Lua, Angelscript, and C++ using
DynamicNavigationMesh, Obstacle, CrowdAgent, and DetourCrowdManager
components.
JSandusky 10 년 전
부모
커밋
567a29d74e
64개의 변경된 파일12874개의 추가작업 그리고 155개의 파일을 삭제
  1. 2 2
      License.txt
  2. 2 0
      Source/CMakeLists.txt
  3. 33 0
      Source/Samples/39_CrowdNavigation/CMakeLists.txt
  4. 462 0
      Source/Samples/39_CrowdNavigation/CrowdNavigation.cpp
  5. 164 0
      Source/Samples/39_CrowdNavigation/CrowdNavigation.h
  6. 1 0
      Source/Samples/CMakeLists.txt
  7. 39 0
      Source/ThirdParty/DetourCrowd/CMakeLists.txt
  8. 18 0
      Source/ThirdParty/DetourCrowd/License.txt
  9. 62 0
      Source/ThirdParty/DetourCrowd/README.md
  10. 120 0
      Source/ThirdParty/DetourCrowd/Readme.txt
  11. 20 0
      Source/ThirdParty/DetourCrowd/TODO.txt
  12. 450 0
      Source/ThirdParty/DetourCrowd/include/DetourCrowd.h
  13. 61 0
      Source/ThirdParty/DetourCrowd/include/DetourLocalBoundary.h
  14. 154 0
      Source/ThirdParty/DetourCrowd/include/DetourObstacleAvoidance.h
  15. 146 0
      Source/ThirdParty/DetourCrowd/include/DetourPathCorridor.h
  16. 75 0
      Source/ThirdParty/DetourCrowd/include/DetourPathQueue.h
  17. 70 0
      Source/ThirdParty/DetourCrowd/include/DetourProximityGrid.h
  18. 1446 0
      Source/ThirdParty/DetourCrowd/source/DetourCrowd.cpp
  19. 137 0
      Source/ThirdParty/DetourCrowd/source/DetourLocalBoundary.cpp
  20. 544 0
      Source/ThirdParty/DetourCrowd/source/DetourObstacleAvoidance.cpp
  21. 597 0
      Source/ThirdParty/DetourCrowd/source/DetourPathCorridor.cpp
  22. 200 0
      Source/ThirdParty/DetourCrowd/source/DetourPathQueue.cpp
  23. 194 0
      Source/ThirdParty/DetourCrowd/source/DetourProximityGrid.cpp
  24. 37 0
      Source/ThirdParty/DetourTileCache/CMakeLists.txt
  25. 18 0
      Source/ThirdParty/DetourTileCache/License.txt
  26. 62 0
      Source/ThirdParty/DetourTileCache/README.md
  27. 120 0
      Source/ThirdParty/DetourTileCache/Readme.txt
  28. 20 0
      Source/ThirdParty/DetourTileCache/TODO.txt
  29. 212 0
      Source/ThirdParty/DetourTileCache/include/DetourTileCache.h
  30. 152 0
      Source/ThirdParty/DetourTileCache/include/DetourTileCacheBuilder.h
  31. 704 0
      Source/ThirdParty/DetourTileCache/source/DetourTileCache.cpp
  32. 2151 0
      Source/ThirdParty/DetourTileCache/source/DetourTileCacheBuilder.cpp
  33. 6 0
      Source/Urho3D/CMakeLists.txt
  34. 19 0
      Source/Urho3D/Graphics/DebugRenderer.cpp
  35. 2 0
      Source/Urho3D/Graphics/DebugRenderer.h
  36. 54 0
      Source/Urho3D/LuaScript/pkgs/Navigation/CrowdAgent.pkg
  37. 31 0
      Source/Urho3D/LuaScript/pkgs/Navigation/DetourCrowdManager.pkg
  38. 7 0
      Source/Urho3D/LuaScript/pkgs/Navigation/DynamicNavigationMesh.pkg
  39. 0 0
      Source/Urho3D/LuaScript/pkgs/Navigation/NavArea.pkg
  40. 16 0
      Source/Urho3D/LuaScript/pkgs/Navigation/Obstacle.pkg
  41. 6 0
      Source/Urho3D/LuaScript/pkgs/NavigationLuaAPI.pkg
  42. 470 0
      Source/Urho3D/Navigation/CrowdAgent.cpp
  43. 171 0
      Source/Urho3D/Navigation/CrowdAgent.h
  44. 521 0
      Source/Urho3D/Navigation/DetourCrowdManager.cpp
  45. 139 0
      Source/Urho3D/Navigation/DetourCrowdManager.h
  46. 868 0
      Source/Urho3D/Navigation/DynamicNavigationMesh.cpp
  47. 104 0
      Source/Urho3D/Navigation/DynamicNavigationMesh.h
  48. 85 0
      Source/Urho3D/Navigation/NavArea.cpp
  49. 66 0
      Source/Urho3D/Navigation/NavArea.h
  50. 100 0
      Source/Urho3D/Navigation/NavBuildData.cpp
  51. 107 0
      Source/Urho3D/Navigation/NavBuildData.h
  52. 77 0
      Source/Urho3D/Navigation/NavigationEvents.h
  53. 148 100
      Source/Urho3D/Navigation/NavigationMesh.cpp
  54. 48 10
      Source/Urho3D/Navigation/NavigationMesh.h
  55. 135 0
      Source/Urho3D/Navigation/Obstacle.cpp
  56. 81 0
      Source/Urho3D/Navigation/Obstacle.h
  57. 16 0
      Source/Urho3D/Navigation/OffMeshConnection.cpp
  58. 12 0
      Source/Urho3D/Navigation/OffMeshConnection.h
  59. 164 41
      Source/Urho3D/Script/NavigationAPI.cpp
  60. 441 0
      bin/Data/LuaScripts/39_CrowdNavigation.lua
  61. 477 0
      bin/Data/Scripts/39_CrowdNavigation.as
  62. 6 2
      bin/Data/Scripts/Editor/EditorScene.as
  63. BIN
      bin/Data/Textures/Editor/EditorIcons.png
  64. 24 0
      bin/Data/UI/EditorIcons.xml

+ 2 - 2
License.txt

@@ -1,4 +1,4 @@
-Urho3D license
+Urho3D license
 --------------
 
 Copyright (c) 2008-2015 the Urho3D project.
@@ -478,7 +478,7 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 THE SOFTWARE.
 
 
-Recast/Detour license
+Recast/Detour/DetourCrowd/DetourTileCache license
 ---------------------
 
 Copyright (c) 2009 Mikko Mononen [email protected]

+ 2 - 0
Source/CMakeLists.txt

@@ -71,6 +71,8 @@ endif ()
 
 if (URHO3D_NAVIGATION)
     add_subdirectory (ThirdParty/Detour)
+    add_subdirectory (ThirdParty/DetourCrowd)
+    add_subdirectory (ThirdParty/DetourTileCache)
     add_subdirectory (ThirdParty/Recast)
 endif ()
 

+ 33 - 0
Source/Samples/39_CrowdNavigation/CMakeLists.txt

@@ -0,0 +1,33 @@
+#
+# Copyright (c) 2008-2015 the Urho3D project.
+#
+# Permission is hereby granted, free of charge, to any person obtaining a copy
+# of this software and associated documentation files (the "Software"), to deal
+# in the Software without restriction, including without limitation the rights
+# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+# copies of the Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be included in
+# all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+# THE SOFTWARE.
+#
+
+# Define target name
+set (TARGET_NAME 39_CrowdNavigation)
+
+# Define source files
+define_source_files (EXTRA_H_FILES ${COMMON_SAMPLE_H_FILES})
+
+# Setup target with resource copying
+setup_main_executable ()
+
+# Setup test cases
+setup_test ()

+ 462 - 0
Source/Samples/39_CrowdNavigation/CrowdNavigation.cpp

@@ -0,0 +1,462 @@
+//
+// Copyright (c) 2008-2015 the Urho3D project.
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+//
+
+#include <Urho3D/Urho3D.h>
+
+#include <Urho3D/Graphics/AnimatedModel.h>
+#include <Urho3D/Graphics/Camera.h>
+#include <Urho3D/Core/CoreEvents.h>
+#include <Urho3D/UI/Cursor.h>
+#include <Urho3D/Graphics/DebugRenderer.h>
+#include <Urho3D/Engine/Engine.h>
+#include <Urho3D/UI/Font.h>
+#include <Urho3D/Graphics/Graphics.h>
+#include <Urho3D/Input/Input.h>
+#include <Urho3D/Graphics/Light.h>
+#include <Urho3D/Graphics/Material.h>
+#include <Urho3D/Math/MathDefs.h>
+#include <Urho3D/Graphics/Model.h>
+#include <Urho3D/Navigation/CrowdAgent.h>
+#include <Urho3D/Navigation/DetourCrowdManager.h>
+#include <Urho3D/Navigation/DynamicNavigationMesh.h>
+#include <Urho3D/Navigation/Navigable.h>
+#include <Urho3D/Navigation/Obstacle.h>
+#include <Urho3D/Graphics/Octree.h>
+#include <Urho3D/Physics/PhysicsWorld.h>
+#include <Urho3D/Graphics/Renderer.h>
+#include <Urho3D/Resource/ResourceCache.h>
+#include <Urho3D/Scene/Scene.h>
+#include <Urho3D/Graphics/StaticModel.h>
+#include <Urho3D/UI/Text.h>
+#include <Urho3D/UI/UI.h>
+#include <Urho3D/Resource/XMLFile.h>
+#include <Urho3D/Graphics/Zone.h>
+
+#include "CrowdNavigation.h"
+
+#include <Urho3D/DebugNew.h>
+
+DEFINE_APPLICATION_MAIN(CrowdNavigation)
+
+CrowdNavigation::CrowdNavigation(Context* context) :
+    Sample(context),
+    drawDebug_(false)
+{
+}
+
+void CrowdNavigation::Start()
+{
+    // Execute base class startup
+    Sample::Start();
+
+    // Create the scene content
+    CreateScene();
+    
+    // Create the UI content
+    CreateUI();
+    
+    // Setup the viewport for displaying the scene
+    SetupViewport();
+
+    // Hook up to the frame update and render post-update events
+    SubscribeToEvents();
+}
+
+void CrowdNavigation::CreateScene()
+{
+    ResourceCache* cache = GetSubsystem<ResourceCache>();
+    
+    scene_ = new Scene(context_);
+    
+    // Create octree, use default volume (-1000, -1000, -1000) to (1000, 1000, 1000)
+    // Also create a DebugRenderer component so that we can draw debug geometry
+    scene_->CreateComponent<Octree>();
+    scene_->CreateComponent<DebugRenderer>();
+	scene_->CreateComponent<PhysicsWorld>();
+    
+    // Create scene node & StaticModel component for showing a static plane
+    Node* planeNode = scene_->CreateChild("Plane");
+    planeNode->SetScale(Vector3(100.0f, 1.0f, 100.0f));
+    StaticModel* planeObject = planeNode->CreateComponent<StaticModel>();
+    planeObject->SetModel(cache->GetResource<Model>("Models/Plane.mdl"));
+    planeObject->SetMaterial(cache->GetResource<Material>("Materials/StoneTiled.xml"));
+    
+    // Create a Zone component for ambient lighting & fog control
+    Node* zoneNode = scene_->CreateChild("Zone");
+    Zone* zone = zoneNode->CreateComponent<Zone>();
+    zone->SetBoundingBox(BoundingBox(-1000.0f, 1000.0f));
+    zone->SetAmbientColor(Color(0.15f, 0.15f, 0.15f));
+    zone->SetFogColor(Color(0.5f, 0.5f, 0.7f));
+    zone->SetFogStart(100.0f);
+    zone->SetFogEnd(300.0f);
+    
+    // Create a directional light to the world. Enable cascaded shadows on it
+    Node* lightNode = scene_->CreateChild("DirectionalLight");
+    lightNode->SetDirection(Vector3(0.6f, -1.0f, 0.8f));
+    Light* light = lightNode->CreateComponent<Light>();
+    light->SetLightType(LIGHT_DIRECTIONAL);
+    light->SetCastShadows(true);
+    light->SetShadowBias(BiasParameters(0.00025f, 0.5f));
+    // Set cascade splits at 10, 50 and 200 world units, fade shadows out at 80% of maximum shadow distance
+    light->SetShadowCascade(CascadeParameters(10.0f, 50.0f, 200.0f, 0.0f, 0.8f));
+    
+    // Create randomly sized boxes. If boxes are big enough, make them occluders
+    const unsigned NUM_BOXES = 20;
+    for (unsigned i = 0; i < NUM_BOXES; ++i)
+    {
+        Node* boxNode = scene_->CreateChild("Box");
+        float size = 1.0f + Random(10.0f);
+        boxNode->SetPosition(Vector3(Random(80.0f) - 40.0f, size * 0.5f, Random(80.0f) - 40.0f));
+        boxNode->SetScale(size);
+        StaticModel* boxObject = boxNode->CreateComponent<StaticModel>();
+        boxObject->SetModel(cache->GetResource<Model>("Models/Box.mdl"));
+        boxObject->SetMaterial(cache->GetResource<Material>("Materials/Stone.xml"));
+        boxObject->SetCastShadows(true);
+        if (size >= 3.0f)
+            boxObject->SetOccluder(true);
+    }
+
+    // Create a DynamicNavigationMesh component to the scene root
+    DynamicNavigationMesh* navMesh = scene_->CreateComponent<DynamicNavigationMesh>();
+	navMesh->SetTileSize(64);
+    // Create a Navigable component to the scene root. This tags all of the geometry in the scene as being part of the
+    // navigation mesh. By default this is recursive, but the recursion could be turned off from Navigable
+    scene_->CreateComponent<Navigable>();
+    // Add padding to the navigation mesh in Y-direction so that we can add objects on top of the tallest boxes
+    // in the scene and still update the mesh correctly
+    navMesh->SetPadding(Vector3(0.0f, 10.0f, 0.0f));
+    // Now build the navigation geometry. This will take some time. Note that the navigation mesh will prefer to use
+    // physics geometry from the scene nodes, as it often is simpler, but if it can not find any (like in this example)
+    // it will use renderable geometry instead
+    navMesh->Build();
+    
+    DetourCrowdManager* crowdManager = scene_->CreateComponent<DetourCrowdManager>();
+    
+    // Create Jack node that will follow the path
+    CreateJack(Vector3(-5.0f, 0.0f, 20.0f));
+    
+    // Create some mushrooms
+    const unsigned NUM_MUSHROOMS = 75;
+    for (unsigned i = 0; i < NUM_MUSHROOMS; ++i)
+        CreateMushroom(Vector3(Random(90.0f) - 45.0f, 0.0f, Random(90.0f) - 45.0f));
+    
+    // Create the camera. Limit far clip distance to match the fog
+    cameraNode_ = scene_->CreateChild("Camera");
+    Camera* camera = cameraNode_->CreateComponent<Camera>();
+    camera->SetFarClip(300.0f);
+    
+    // Set an initial position for the camera scene node above the plane
+    cameraNode_->SetPosition(Vector3(0.0f, 5.0f, 0.0f));
+}
+
+void CrowdNavigation::CreateUI()
+{
+    ResourceCache* cache = GetSubsystem<ResourceCache>();
+    UI* ui = GetSubsystem<UI>();
+    
+    // Create a Cursor UI element because we want to be able to hide and show it at will. When hidden, the mouse cursor will
+    // control the camera, and when visible, it will point the raycast target
+    XMLFile* style = cache->GetResource<XMLFile>("UI/DefaultStyle.xml");
+    SharedPtr<Cursor> cursor(new Cursor(context_));
+    cursor->SetStyleAuto(style);
+    ui->SetCursor(cursor);
+
+    // Set starting position of the cursor at the rendering window center
+    Graphics* graphics = GetSubsystem<Graphics>();
+    cursor->SetPosition(graphics->GetWidth() / 2, graphics->GetHeight() / 2);
+    
+    // Construct new Text object, set string to display and font to use
+    Text* instructionText = ui->GetRoot()->CreateChild<Text>();
+    instructionText->SetText(
+        "Use WASD keys to move, RMB to rotate view\n"
+        "LMB to set destination, SHIFT+LMB to spawn a Jack\n"
+        "MMB to add or remove obstacles\n"
+        "F5 to save scene, F7 to load\n"
+        "Space to toggle debug geometry"
+    );
+    instructionText->SetFont(cache->GetResource<Font>("Fonts/Anonymous Pro.ttf"), 15);
+    // The text has multiple rows. Center them in relation to each other
+    instructionText->SetTextAlignment(HA_CENTER);
+    
+    // Position the text relative to the screen center
+    instructionText->SetHorizontalAlignment(HA_CENTER);
+    instructionText->SetVerticalAlignment(VA_CENTER);
+    instructionText->SetPosition(0, ui->GetRoot()->GetHeight() / 4);
+}
+
+void CrowdNavigation::SetupViewport()
+{
+    Renderer* renderer = GetSubsystem<Renderer>();
+    
+    // Set up a viewport to the Renderer subsystem so that the 3D scene can be seen
+    SharedPtr<Viewport> viewport(new Viewport(context_, scene_, cameraNode_->GetComponent<Camera>()));
+    renderer->SetViewport(0, viewport);
+}
+
+void CrowdNavigation::SubscribeToEvents()
+{
+    // Subscribe HandleUpdate() function for processing update events
+    SubscribeToEvent(E_UPDATE, HANDLER(CrowdNavigation, HandleUpdate));
+    
+    // Subscribe HandlePostRenderUpdate() function for processing the post-render update event, during which we request
+    // debug geometry
+    SubscribeToEvent(E_POSTRENDERUPDATE, HANDLER(CrowdNavigation, HandlePostRenderUpdate));
+}
+
+void CrowdNavigation::MoveCamera(float timeStep)
+{
+    // Right mouse button controls mouse cursor visibility: hide when pressed
+    UI* ui = GetSubsystem<UI>();
+    Input* input = GetSubsystem<Input>();
+    ui->GetCursor()->SetVisible(!input->GetMouseButtonDown(MOUSEB_RIGHT));
+    
+    // Do not move if the UI has a focused element (the console)
+    if (ui->GetFocusElement())
+        return;
+    
+    // Movement speed as world units per second
+    const float MOVE_SPEED = 20.0f;
+    // Mouse sensitivity as degrees per pixel
+    const float MOUSE_SENSITIVITY = 0.1f;
+    
+    // Use this frame's mouse motion to adjust camera node yaw and pitch. Clamp the pitch between -90 and 90 degrees
+    // Only move the camera when the cursor is hidden
+    if (!ui->GetCursor()->IsVisible())
+    {
+        IntVector2 mouseMove = input->GetMouseMove();
+        yaw_ += MOUSE_SENSITIVITY * mouseMove.x_;
+        pitch_ += MOUSE_SENSITIVITY * mouseMove.y_;
+        pitch_ = Clamp(pitch_, -90.0f, 90.0f);
+        
+        // Construct new orientation for the camera scene node from yaw and pitch. Roll is fixed to zero
+        cameraNode_->SetRotation(Quaternion(pitch_, yaw_, 0.0f));
+    }
+    
+    // Read WASD keys and move the camera scene node to the corresponding direction if they are pressed
+    if (input->GetKeyDown('W'))
+        cameraNode_->Translate(Vector3::FORWARD * MOVE_SPEED * timeStep);
+    if (input->GetKeyDown('S'))
+        cameraNode_->Translate(Vector3::BACK * MOVE_SPEED * timeStep);
+    if (input->GetKeyDown('A'))
+        cameraNode_->Translate(Vector3::LEFT * MOVE_SPEED * timeStep);
+    if (input->GetKeyDown('D'))
+        cameraNode_->Translate(Vector3::RIGHT * MOVE_SPEED * timeStep);
+    
+    // Set destination or spawn a new jack with left mouse button
+    if (input->GetMouseButtonPress(MOUSEB_LEFT))
+        SetPathPoint();
+    // Add or remove objects with middle mouse button, then rebuild navigation mesh partially
+    if (input->GetMouseButtonPress(MOUSEB_MIDDLE))
+        AddOrRemoveObject();
+        
+    // Check for loading/saving the scene. Save the scene to the file Data/Scenes/CrowdNavigation.xml relative to the executable
+    // directory
+    if (input->GetKeyPress(KEY_F5))
+    {
+        File saveFile(context_, GetSubsystem<FileSystem>()->GetProgramDir() + "Data/Scenes/CrowdNavigation.xml", FILE_WRITE);
+        scene_->SaveXML(saveFile);
+    }
+    if (input->GetKeyPress(KEY_F7))
+    {
+        File loadFile(context_, GetSubsystem<FileSystem>()->GetProgramDir() + "Data/Scenes/CrowdNavigation.xml", FILE_READ);
+        scene_->LoadXML(loadFile);
+        
+        // Redetermine which nodes are "jacks" and "mushrooms"
+        jackNodes_.Clear();
+		PODVector<Node*> nodes;
+		scene_->GetChildrenWithComponent<CrowdAgent>(nodes, true);
+        for (unsigned i = 0; i < nodes.Size(); ++i)
+        {
+            jackNodes_.Push(SharedPtr<Node>(nodes[i]));
+        }
+        mushroomNodes_.Clear();
+        scene_->GetChildrenWithComponent<Obstacle>(nodes, true);
+        for (unsigned i = 0; i < nodes.Size(); ++i)
+        {
+            mushroomNodes_.Push(SharedPtr<Node>(nodes[i]));
+        }
+    }
+        
+    // Toggle debug geometry with space
+    if (input->GetKeyPress(KEY_SPACE))
+        drawDebug_ = !drawDebug_;
+}
+
+void CrowdNavigation::SetPathPoint()
+{
+    Vector3 hitPos;
+    Drawable* hitDrawable;
+    DynamicNavigationMesh* navMesh = scene_->GetComponent<DynamicNavigationMesh>();
+    
+    if (Raycast(250.0f, hitPos, hitDrawable))
+    {
+        Vector3 pathPos = navMesh->FindNearestPoint(hitPos, Vector3(1.0f, 1.0f, 1.0f));
+
+        if (GetSubsystem<Input>()->GetQualifierDown(QUAL_SHIFT))
+        {
+            // spawn a jack at the target position
+            CreateJack(pathPos);
+        }
+        else
+        {
+            // Set crowd agents to move to the target
+			for (unsigned i = 0; i < jackNodes_.Size(); ++i)
+			{
+				CrowdAgent* agent = jackNodes_[i]->GetComponent<CrowdAgent>();
+				// The first agent will always move to the exact position, all other agents will select a random point nearby
+				if (i == 0)
+				{
+					agent->SetMoveTarget(pathPos);
+				}
+				else
+				{
+					// Keep the random point somewhere on the navigation mesh
+					Vector3 targetPos = navMesh->FindNearestPoint(pathPos + Vector3(Random(7.0f), 0.0f, Random(7.0f)), Vector3(1.0f, 1.0f, 1.0f));
+					agent->SetMoveTarget(targetPos);
+				}
+            }            
+        }
+    }
+}
+
+void CrowdNavigation::AddOrRemoveObject()
+{
+    // Raycast and check if we hit a mushroom node. If yes, remove it, if no, create a new one
+    Vector3 hitPos;
+    Drawable* hitDrawable;
+    
+    if (Raycast(250.0f, hitPos, hitDrawable))
+    {        
+        Node* hitNode = hitDrawable->GetNode();
+        if (hitNode->GetName() == "Mushroom")
+        {
+            mushroomNodes_.Remove(hitNode);
+            hitNode->Remove();
+        }
+        else
+        {
+            CreateMushroom(hitPos);
+        }
+    }
+}
+
+void CrowdNavigation::CreateJack(const Vector3& pos)
+{
+	ResourceCache* cache = GetSubsystem<ResourceCache>();
+	SharedPtr<Node> jackNode(scene_->CreateChild("Jack"));
+    jackNode->SetPosition(pos);
+    AnimatedModel* modelObject = jackNode->CreateComponent<AnimatedModel>();
+    modelObject->SetModel(cache->GetResource<Model>("Models/Jack.mdl"));
+    modelObject->SetMaterial(cache->GetResource<Material>("Materials/Jack.xml"));
+    modelObject->SetCastShadows(true);
+
+	// Create the CrowdAgent
+    CrowdAgent* agent = jackNode->CreateComponent<CrowdAgent>();
+    jackNodes_.Push(jackNode);
+}
+
+Node* CrowdNavigation::CreateMushroom(const Vector3& pos)
+{
+    ResourceCache* cache = GetSubsystem<ResourceCache>();
+    
+    Node* mushroomNode = scene_->CreateChild("Mushroom");
+    mushroomNode->SetPosition(pos);
+    mushroomNode->SetRotation(Quaternion(0.0f, Random(360.0f), 0.0f));
+    mushroomNode->SetScale(2.0f + Random(0.5f));
+    StaticModel* mushroomObject = mushroomNode->CreateComponent<StaticModel>();
+    mushroomObject->SetModel(cache->GetResource<Model>("Models/Mushroom.mdl"));
+    mushroomObject->SetMaterial(cache->GetResource<Material>("Materials/Mushroom.xml"));
+    mushroomObject->SetCastShadows(true);
+	// Create the navigation obstacle
+    Obstacle* obstacle = mushroomNode->CreateComponent<Obstacle>();
+    obstacle->SetRadius(2.0f);
+    mushroomNodes_.Push(mushroomNode);
+    
+    return mushroomNode;
+}
+
+bool CrowdNavigation::Raycast(float maxDistance, Vector3& hitPos, Drawable*& hitDrawable)
+{
+    hitDrawable = 0;
+    
+    UI* ui = GetSubsystem<UI>();
+    IntVector2 pos = ui->GetCursorPosition();
+    // Check the cursor is visible and there is no UI element in front of the cursor
+    if (!ui->GetCursor()->IsVisible() || ui->GetElementAt(pos, true))
+        return false;
+    
+    Graphics* graphics = GetSubsystem<Graphics>();
+    Camera* camera = cameraNode_->GetComponent<Camera>();
+    Ray cameraRay = camera->GetScreenRay((float)pos.x_ / graphics->GetWidth(), (float)pos.y_ / graphics->GetHeight());
+    // Pick only geometry objects, not eg. zones or lights, only get the first (closest) hit
+    PODVector<RayQueryResult> results;
+    RayOctreeQuery query(results, cameraRay, RAY_TRIANGLE, maxDistance, DRAWABLE_GEOMETRY);
+    scene_->GetComponent<Octree>()->RaycastSingle(query);
+    if (results.Size())
+    {
+        RayQueryResult& result = results[0];
+        hitPos = result.position_;
+        hitDrawable = result.drawable_;
+        return true;
+    }
+    
+    return false;
+}
+
+void CrowdNavigation::HandleUpdate(StringHash eventType, VariantMap& eventData)
+{
+    using namespace Update;
+
+    // Take the frame time step, which is stored as a float
+    float timeStep = eventData[P_TIMESTEP].GetFloat();
+    
+    // Move the camera, scale movement with time step
+    MoveCamera(timeStep);
+
+	for (unsigned i = 0; i < jackNodes_.Size(); ++i)
+	{
+		CrowdAgent* agent = jackNodes_[i]->GetComponent<CrowdAgent>();
+		Vector3 vel = agent->GetActualVelocity();
+		jackNodes_[i]->SetWorldDirection(vel);
+	}
+}
+
+void CrowdNavigation::HandlePostRenderUpdate(StringHash eventType, VariantMap& eventData)
+{
+    // If draw debug mode is enabled, draw navigation mesh debug geometry
+    if (drawDebug_)
+    {
+        scene_->GetComponent<DynamicNavigationMesh>()->DrawDebugGeometry(true);
+    
+        for (unsigned i = 0; i < jackNodes_.Size(); ++i)
+        {
+            CrowdAgent* agent = jackNodes_[i]->GetComponent<CrowdAgent>();
+            agent->DrawDebugGeometry(true);
+        }
+        
+        for (unsigned i = 0; i < mushroomNodes_.Size(); ++i)
+        {
+            Obstacle* obstacle = mushroomNodes_[i]->GetComponent<Obstacle>();
+            obstacle->DrawDebugGeometry(true);
+        }
+    }
+}

+ 164 - 0
Source/Samples/39_CrowdNavigation/CrowdNavigation.h

@@ -0,0 +1,164 @@
+//
+// Copyright (c) 2008-2015 the Urho3D project.
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+//
+
+#pragma once
+
+#include "Sample.h"
+
+namespace Urho3D
+{
+
+class Drawable;
+class Node;
+class Scene;
+
+}
+
+/// Navigation example.
+/// This sample demonstrates:
+///     - Generating a navigation mesh into the scene
+///     - Performing path queries to the navigation mesh
+///     - Rebuilding the navigation mesh partially when adding or removing objects
+///     - Visualizing custom debug geometry
+///     - Raycasting drawable components
+///     - Making a node follow the Detour path
+class CrowdNavigation : public Sample
+{
+    OBJECT(CrowdNavigation);
+
+public:
+    /// Construct.
+    CrowdNavigation(Context* context);
+
+    /// Setup after engine initialization and before running the main loop.
+    virtual void Start();
+
+protected:
+    /// Return XML patch instructions for screen joystick layout for a specific sample app, if any.
+    virtual String GetScreenJoystickPatchString() const { return
+        "<patch>"
+        "    <add sel=\"/element\">"
+        "        <element type=\"Button\">"
+        "            <attribute name=\"Name\" value=\"Button3\" />"
+        "            <attribute name=\"Position\" value=\"-120 -120\" />"
+        "            <attribute name=\"Size\" value=\"96 96\" />"
+        "            <attribute name=\"Horiz Alignment\" value=\"Right\" />"
+        "            <attribute name=\"Vert Alignment\" value=\"Bottom\" />"
+        "            <attribute name=\"Texture\" value=\"Texture2D;Textures/TouchInput.png\" />"
+        "            <attribute name=\"Image Rect\" value=\"96 0 192 96\" />"
+        "            <attribute name=\"Hover Image Offset\" value=\"0 0\" />"
+        "            <attribute name=\"Pressed Image Offset\" value=\"0 0\" />"
+        "            <element type=\"Text\">"
+        "                <attribute name=\"Name\" value=\"Label\" />"
+        "                <attribute name=\"Horiz Alignment\" value=\"Center\" />"
+        "                <attribute name=\"Vert Alignment\" value=\"Center\" />"
+        "                <attribute name=\"Color\" value=\"0 0 0 1\" />"
+        "                <attribute name=\"Text\" value=\"Teleport\" />"
+        "            </element>"
+        "            <element type=\"Text\">"
+        "                <attribute name=\"Name\" value=\"KeyBinding\" />"
+        "                <attribute name=\"Text\" value=\"LSHIFT\" />"
+        "            </element>"
+        "            <element type=\"Text\">"
+        "                <attribute name=\"Name\" value=\"MouseButtonBinding\" />"
+        "                <attribute name=\"Text\" value=\"LEFT\" />"
+        "            </element>"
+        "        </element>"
+        "        <element type=\"Button\">"
+        "            <attribute name=\"Name\" value=\"Button4\" />"
+        "            <attribute name=\"Position\" value=\"-120 -12\" />"
+        "            <attribute name=\"Size\" value=\"96 96\" />"
+        "            <attribute name=\"Horiz Alignment\" value=\"Right\" />"
+        "            <attribute name=\"Vert Alignment\" value=\"Bottom\" />"
+        "            <attribute name=\"Texture\" value=\"Texture2D;Textures/TouchInput.png\" />"
+        "            <attribute name=\"Image Rect\" value=\"96 0 192 96\" />"
+        "            <attribute name=\"Hover Image Offset\" value=\"0 0\" />"
+        "            <attribute name=\"Pressed Image Offset\" value=\"0 0\" />"
+        "            <element type=\"Text\">"
+        "                <attribute name=\"Name\" value=\"Label\" />"
+        "                <attribute name=\"Horiz Alignment\" value=\"Center\" />"
+        "                <attribute name=\"Vert Alignment\" value=\"Center\" />"
+        "                <attribute name=\"Color\" value=\"0 0 0 1\" />"
+        "                <attribute name=\"Text\" value=\"Obstacles\" />"
+        "            </element>"
+        "            <element type=\"Text\">"
+        "                <attribute name=\"Name\" value=\"MouseButtonBinding\" />"
+        "                <attribute name=\"Text\" value=\"MIDDLE\" />"
+        "            </element>"
+        "        </element>"
+        "    </add>"
+        "    <remove sel=\"/element/element[./attribute[@name='Name' and @value='Button0']]/attribute[@name='Is Visible']\" />"
+        "    <replace sel=\"/element/element[./attribute[@name='Name' and @value='Button0']]/element[./attribute[@name='Name' and @value='Label']]/attribute[@name='Text']/@value\">Set</replace>"
+        "    <add sel=\"/element/element[./attribute[@name='Name' and @value='Button0']]\">"
+        "        <element type=\"Text\">"
+        "            <attribute name=\"Name\" value=\"MouseButtonBinding\" />"
+        "            <attribute name=\"Text\" value=\"LEFT\" />"
+        "        </element>"
+        "    </add>"
+        "    <remove sel=\"/element/element[./attribute[@name='Name' and @value='Button1']]/attribute[@name='Is Visible']\" />"
+        "    <replace sel=\"/element/element[./attribute[@name='Name' and @value='Button1']]/element[./attribute[@name='Name' and @value='Label']]/attribute[@name='Text']/@value\">Debug</replace>"
+        "    <add sel=\"/element/element[./attribute[@name='Name' and @value='Button1']]\">"
+        "        <element type=\"Text\">"
+        "            <attribute name=\"Name\" value=\"KeyBinding\" />"
+        "            <attribute name=\"Text\" value=\"SPACE\" />"
+        "        </element>"
+        "    </add>"
+        "</patch>";
+    }
+
+private:
+    /// Construct the scene content.
+    void CreateScene();
+    /// Construct user interface elements.
+    void CreateUI();
+    /// Set up a viewport for displaying the scene.
+    void SetupViewport();
+    /// Subscribe to application-wide logic update and post-render update events.
+    void SubscribeToEvents();
+    /// Read input and moves the camera.
+    void MoveCamera(float timeStep);
+    /// Set path start or end point.
+    void SetPathPoint();
+    /// Add or remove object.
+    void AddOrRemoveObject();
+    /// Create a "Jack" object at position
+    void CreateJack(const Vector3& pos);
+    /// Create a mushroom object at position.
+    Node* CreateMushroom(const Vector3& pos);
+    /// Utility function to raycast to the cursor position. Return true if hit
+    bool Raycast(float maxDistance, Vector3& hitPos, Drawable*& hitDrawable);
+    /// Handle the logic update event.
+    void HandleUpdate(StringHash eventType, VariantMap& eventData);
+    /// Handle the post-render update event.
+    void HandlePostRenderUpdate(StringHash eventType, VariantMap& eventData);
+
+    /// Last calculated path.
+    PODVector<Vector3> currentPath_;
+    /// Path end position.
+    Vector3 endPos_;
+    /// Jack scene node.
+    Vector< SharedPtr<Node> > jackNodes_;
+    /// Mushroom nodes
+    Vector< Node* > mushroomNodes_;
+    /// Flag for drawing debug geometry.
+    bool drawDebug_;
+};

+ 1 - 0
Source/Samples/CMakeLists.txt

@@ -65,6 +65,7 @@ endif ()
 add_subdirectory (14_SoundEffects)
 if (URHO3D_NAVIGATION)
     add_subdirectory (15_Navigation)
+    add_subdirectory (39_CrowdNavigation)
 endif ()
 if (URHO3D_NETWORK)
     add_subdirectory (16_Chat)

+ 39 - 0
Source/ThirdParty/DetourCrowd/CMakeLists.txt

@@ -0,0 +1,39 @@
+#
+# Copyright (c) 2008-2015 the Urho3D project.
+#
+# Permission is hereby granted, free of charge, to any person obtaining a copy
+# of this software and associated documentation files (the "Software"), to deal
+# in the Software without restriction, including without limitation the rights
+# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+# copies of the Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be included in
+# all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+# THE SOFTWARE.
+#
+
+# Define target name
+set (TARGET_NAME DetourCrowd)
+
+# Define source files
+define_source_files (GLOB_CPP_PATTERNS source/*.cpp GLOB_H_PATTERNS include/*.h)
+
+# Define dependency libs
+set (INCLUDE_DIRS include ../Detour/include)
+
+# Define dependency libs
+
+
+# Setup target
+setup_library ()
+
+# Install headers for building the Urho3D library
+install_header_files (DIRECTORY include/ DESTINATION ${DEST_INCLUDE_DIR}/ThirdParty/DetourCrowd FILES_MATCHING PATTERN *.h BUILD_TREE_ONLY)  # Note: the trailing slash is significant

+ 18 - 0
Source/ThirdParty/DetourCrowd/License.txt

@@ -0,0 +1,18 @@
+Copyright (c) 2009 Mikko Mononen [email protected]
+
+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.
+

+ 62 - 0
Source/ThirdParty/DetourCrowd/README.md

@@ -0,0 +1,62 @@
+
+Recast & Detour
+===============
+
+[![Bitdeli Badge](https://d2weczhvl823v0.cloudfront.net/memononen/recastnavigation/trend.png)](https://bitdeli.com/free "Bitdeli Badge")
+
+![screenshot of a navmesh baked with the sample program](/RecastDemo/screenshot.png?raw=true)
+
+## Recast
+
+Recast is state of the art navigation mesh construction toolset for games.
+
+* It is automatic, which means that you can throw any level geometry at it and you will get robust mesh out
+* It is fast which means swift turnaround times for level designers
+* It is open source so it comes with full source and you can customize it to your heart's content. 
+
+The Recast process starts with constructing a voxel mold from a level geometry 
+and then casting a navigation mesh over it. The process consists of three steps, 
+building the voxel mold, partitioning the mold into simple regions, peeling off 
+the regions as simple polygons.
+
+1. The voxel mold is build from the input triangle mesh by rasterizing the triangles into a multi-layer heightfield. Some simple filters are  then applied to the mold to prune out locations where the character would not be able to move.
+2. The walkable areas described by the mold are divided into simple overlayed 2D regions. The resulting regions have only one non-overlapping contour, which simplifies the final step of the process tremendously.
+3. The navigation polygons are peeled off from the regions by first tracing the boundaries and then simplifying them. The resulting polygons are finally converted to convex polygons which makes them perfect for pathfinding and spatial reasoning about the level. 
+
+
+## Detour
+
+Recast is accompanied with Detour, path-finding and spatial reasoning toolkit. You can use any navigation mesh with Detour, but of course the data generated with Recast fits perfectly.
+
+Detour offers simple static navigation mesh which is suitable for many simple cases, as well as tiled navigation mesh which allows you to plug in and out pieces of the mesh. The tiled mesh allows you to create systems where you stream new navigation data in and out as the player progresses the level, or you may regenerate tiles as the world changes. 
+
+
+## Recast Demo
+
+You can find a comprehensive demo project in RecastDemo folder. It is a kitchen sink demo containing all the functionality of the library. If you are new to Recast & Detour, check out [Sample_SoloMesh.cpp](/RecastDemo/Source/Sample_SoloMesh.cpp) to get started with building navmeshes and [NavMeshTesterTool.cpp](/RecastDemo/Source/NavMeshTesterTool.cpp) to see how Detour can be used to find paths.
+
+### Building RecastDemo
+
+RecastDemo uses [premake4](http://industriousone.com/premake) to build platform specific projects, now is good time to install it if you don't have it already. To build *RecasDemo*, in your favorite terminal navigate into the `RecastDemo` folder, then:
+
+- *OS X*: `premake4 xcode4`
+- *Windows*: `premake4 vs2010`
+- *Linux*: `premake4 gmake`
+
+See premake4 documentation for full list of supported build file types. The projects will be created in `RecastDemo/Build` folder. And after you have compiled the project, the *RecastDemo* executable will be located in `RecastDemo/Bin` folder.
+
+
+## Integrating with your own project
+
+It is recommended to add the source directories `DebugUtils`, `Detour`, `DetourCrowd`, `DetourTileCache`, and `Recast` into your own project depending on which parts of the project you need. For example your level building tool could include DebugUtils, Recast, and Detour, and your game runtime could just include Detour.
+
+
+## Discuss
+
+- Discuss Recast & Detour: http://groups.google.com/group/recastnavigation
+- Development blog: http://digestingduck.blogspot.com/
+
+
+## License
+
+Recast & Detour is licensed under ZLib license, see License.txt for more information.

+ 120 - 0
Source/ThirdParty/DetourCrowd/Readme.txt

@@ -0,0 +1,120 @@
+
+Recast & Detour Version 1.4
+
+
+Recast
+
+Recast is state of the art navigation mesh construction toolset for games.
+
+    * It is automatic, which means that you can throw any level geometry
+      at it and you will get robust mesh out
+    * It is fast which means swift turnaround times for level designers
+    * It is open source so it comes with full source and you can
+      customize it to your hearts content. 
+
+The Recast process starts with constructing a voxel mold from a level geometry 
+and then casting a navigation mesh over it. The process consists of three steps, 
+building the voxel mold, partitioning the mold into simple regions, peeling off 
+the regions as simple polygons.
+
+   1. The voxel mold is build from the input triangle mesh by rasterizing 
+      the triangles into a multi-layer heightfield. Some simple filters are 
+      then applied to the mold to prune out locations where the character 
+      would not be able to move.
+   2. The walkable areas described by the mold are divided into simple 
+      overlayed 2D regions. The resulting regions have only one non-overlapping 
+      contour, which simplifies the final step of the process tremendously.
+   3. The navigation polygons are peeled off from the regions by first tracing 
+      the boundaries and then simplifying them. The resulting polygons are 
+      finally converted to convex polygons which makes them perfect for 
+      pathfinding and spatial reasoning about the level. 
+
+The toolset code is located in the Recast folder and demo application using the Recast
+toolset is located in the RecastDemo folder.
+
+The project files with this distribution can be compiled with Microsoft Visual C++ 2008
+(you can download it for free) and XCode 3.1.
+
+
+Detour
+
+Recast is accompanied with Detour, path-finding and spatial reasoning toolkit. You can use any navigation mesh with Detour, but of course the data generated with Recast fits perfectly.
+
+Detour offers simple static navigation mesh which is suitable for many simple cases, as well as tiled navigation mesh which allows you to plug in and out pieces of the mesh. The tiled mesh allows to create systems where you stream new navigation data in and out as the player progresses the level, or you may regenerate tiles as the world changes. 
+
+
+Latest code available at http://code.google.com/p/recastnavigation/
+
+
+--
+
+Release Notes
+
+----------------
+* Recast 1.4
+  Released August 24th, 2009
+
+- Added detail height mesh generation (RecastDetailMesh.cpp) for single,
+  tiled statmeshes as well as tilemesh.
+- Added feature to contour tracing which detects extra vertices along
+  tile edges which should be removed later.
+- Changed the tiled stat mesh preprocess, so that it first generated
+  polymeshes per tile and finally combines them.
+- Fixed bug in the GUI code where invisible buttons could be pressed.
+
+----------------
+* Recast 1.31
+  Released July 24th, 2009
+
+- Better cost and heuristic functions.
+- Fixed tile navmesh raycast on tile borders.
+
+----------------
+* Recast 1.3
+  Released July 14th, 2009
+
+- Added dtTileNavMesh which allows to dynamically add and remove navmesh pieces at runtime.
+- Renamed stat navmesh types to dtStat* (i.e. dtPoly is now dtStatPoly).
+- Moved common code used by tile and stat navmesh to DetourNode.h/cpp and DetourCommon.h/cpp.
+- Refactores the demo code.
+
+----------------
+* Recast 1.2
+  Released June 17th, 2009
+
+- Added tiled mesh generation. The tiled generation allows to generate navigation for
+  much larger worlds, it removes some of the artifacts that comes from distance fields
+  in open areas, and allows later streaming and dynamic runtime generation
+- Improved and added some debug draw modes
+- API change: The helper function rcBuildNavMesh does not exists anymore,
+  had to change few internal things to cope with the tiled processing,
+  similar API functionality will be added later once the tiled process matures
+- The demo is getting way too complicated, need to split demos
+- Fixed several filtering functions so that the mesh is tighter to the geometry,
+  sometimes there could be up error up to tow voxel units close to walls,
+  now it should be just one.
+
+----------------
+* Recast 1.1
+  Released April 11th, 2009
+
+This is the first release of Detour.
+
+----------------
+* Recast 1.0
+  Released March 29th, 2009
+
+This is the first release of Recast.
+
+The process is not always as robust as I would wish. The watershed phase sometimes swallows tiny islands
+which are close to edges. These droppings are handled in rcBuildContours, but the code is not
+particularly robust either.
+
+Another non-robust case is when portal contours (contours shared between two regions) are always
+assumed to be straight. That can lead to overlapping contours specially when the level has
+large open areas.
+
+
+
+Mikko Mononen
[email protected]

+ 20 - 0
Source/ThirdParty/DetourCrowd/TODO.txt

@@ -0,0 +1,20 @@
+TODO/Roadmap
+
+Summer/Autumn 2009
+
+- Off mesh links (jump links)
+- Area annotations
+- Embed extra data per polygon
+- Height conforming navmesh
+
+
+Autumn/Winter 2009/2010
+
+- Detour path following
+- More dynamic example with tile navmesh
+- Faster small tile process
+
+
+More info at http://digestingduck.blogspot.com/2009/07/recast-and-detour-roadmap.html
+
+-

+ 450 - 0
Source/ThirdParty/DetourCrowd/include/DetourCrowd.h

@@ -0,0 +1,450 @@
+//
+// Copyright (c) 2009-2010 Mikko Mononen [email protected]
+//
+// 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 DETOURCROWD_H
+#define DETOURCROWD_H
+
+#include "DetourNavMeshQuery.h"
+#include "DetourObstacleAvoidance.h"
+#include "DetourLocalBoundary.h"
+#include "DetourPathCorridor.h"
+#include "DetourProximityGrid.h"
+#include "DetourPathQueue.h"
+
+/// The maximum number of neighbors that a crowd agent can take into account
+/// for steering decisions.
+/// @ingroup crowd
+static const int DT_CROWDAGENT_MAX_NEIGHBOURS = 6;
+
+/// The maximum number of corners a crowd agent will look ahead in the path.
+/// This value is used for sizing the crowd agent corner buffers.
+/// Due to the behavior of the crowd manager, the actual number of useful
+/// corners will be one less than this number.
+/// @ingroup crowd
+static const int DT_CROWDAGENT_MAX_CORNERS = 4;
+
+/// The maximum number of crowd avoidance configurations supported by the
+/// crowd manager.
+/// @ingroup crowd
+/// @see dtObstacleAvoidanceParams, dtCrowd::setObstacleAvoidanceParams(), dtCrowd::getObstacleAvoidanceParams(),
+///		 dtCrowdAgentParams::obstacleAvoidanceType
+static const int DT_CROWD_MAX_OBSTAVOIDANCE_PARAMS = 8;
+
+/// The maximum number of query filter types supported by the crowd manager.
+/// @ingroup crowd
+/// @see dtQueryFilter, dtCrowd::getFilter() dtCrowd::getEditableFilter(),
+///		dtCrowdAgentParams::queryFilterType
+static const int DT_CROWD_MAX_QUERY_FILTER_TYPE = 16;
+
+/// Provides neighbor data for agents managed by the crowd.
+/// @ingroup crowd
+/// @see dtCrowdAgent::neis, dtCrowd
+struct dtCrowdNeighbour
+{
+	int idx;		///< The index of the neighbor in the crowd.
+	float dist;		///< The distance between the current agent and the neighbor.
+};
+
+/// The type of navigation mesh polygon the agent is currently traversing.
+/// @ingroup crowd
+enum CrowdAgentState
+{
+	DT_CROWDAGENT_STATE_INVALID,		///< The agent is not in a valid state.
+	DT_CROWDAGENT_STATE_WALKING,		///< The agent is traversing a normal navigation mesh polygon.
+	DT_CROWDAGENT_STATE_OFFMESH,		///< The agent is traversing an off-mesh connection.
+};
+
+/// Configuration parameters for a crowd agent.
+/// @ingroup crowd
+struct dtCrowdAgentParams
+{
+	float radius;						///< Agent radius. [Limit: >= 0]
+	float height;						///< Agent height. [Limit: > 0]
+	float maxAcceleration;				///< Maximum allowed acceleration. [Limit: >= 0]
+	float maxSpeed;						///< Maximum allowed speed. [Limit: >= 0]
+
+	/// Defines how close a collision element must be before it is considered for steering behaviors. [Limits: > 0]
+	float collisionQueryRange;
+
+	float pathOptimizationRange;		///< The path visibility optimization range. [Limit: > 0]
+
+	/// How aggresive the agent manager should be at avoiding collisions with this agent. [Limit: >= 0]
+	float separationWeight;
+
+	/// Flags that impact steering behavior. (See: #UpdateFlags)
+	unsigned char updateFlags;
+
+	/// The index of the avoidance configuration to use for the agent. 
+	/// [Limits: 0 <= value <= #DT_CROWD_MAX_OBSTAVOIDANCE_PARAMS]
+	unsigned char obstacleAvoidanceType;	
+
+	/// The index of the query filter used by this agent.
+	unsigned char queryFilterType;
+
+	/// User defined data attached to the agent.
+	void* userData;
+};
+
+enum MoveRequestState
+{
+	DT_CROWDAGENT_TARGET_NONE = 0,
+	DT_CROWDAGENT_TARGET_FAILED,
+	DT_CROWDAGENT_TARGET_VALID,
+	DT_CROWDAGENT_TARGET_REQUESTING,
+	DT_CROWDAGENT_TARGET_WAITING_FOR_QUEUE,
+	DT_CROWDAGENT_TARGET_WAITING_FOR_PATH,
+	DT_CROWDAGENT_TARGET_VELOCITY,
+};
+
+/// Represents an agent managed by a #dtCrowd object.
+/// @ingroup crowd
+struct dtCrowdAgent
+{
+	/// True if the agent is active, false if the agent is in an unused slot in the agent pool.
+	bool active;
+
+	/// The type of mesh polygon the agent is traversing. (See: #CrowdAgentState)
+	unsigned char state;
+
+	/// True if the agent has valid path (targetState == DT_CROWDAGENT_TARGET_VALID) and the path does not lead to the requested position, else false.
+	bool partial;
+
+	/// The path corridor the agent is using.
+	dtPathCorridor corridor;
+
+	/// The local boundary data for the agent.
+	dtLocalBoundary boundary;
+	
+	/// Time since the agent's path corridor was optimized.
+	float topologyOptTime;
+	
+	/// The known neighbors of the agent.
+	dtCrowdNeighbour neis[DT_CROWDAGENT_MAX_NEIGHBOURS];
+
+	/// The number of neighbors.
+	int nneis;
+	
+	/// The desired speed.
+	float desiredSpeed;
+
+	float npos[3];		///< The current agent position. [(x, y, z)]
+	float disp[3];
+	float dvel[3];		///< The desired velocity of the agent. [(x, y, z)]
+	float nvel[3];
+	float vel[3];		///< The actual velocity of the agent. [(x, y, z)]
+
+	/// The agent's configuration parameters.
+	dtCrowdAgentParams params;
+
+	/// The local path corridor corners for the agent. (Staight path.) [(x, y, z) * #ncorners]
+	float cornerVerts[DT_CROWDAGENT_MAX_CORNERS*3];
+
+	/// The local path corridor corner flags. (See: #dtStraightPathFlags) [(flags) * #ncorners]
+	unsigned char cornerFlags[DT_CROWDAGENT_MAX_CORNERS];
+
+	/// The reference id of the polygon being entered at the corner. [(polyRef) * #ncorners]
+	dtPolyRef cornerPolys[DT_CROWDAGENT_MAX_CORNERS];
+
+	/// The number of corners.
+	int ncorners;
+	
+	unsigned char targetState;			///< State of the movement request.
+	dtPolyRef targetRef;				///< Target polyref of the movement request.
+	float targetPos[3];					///< Target position of the movement request (or velocity in case of DT_CROWDAGENT_TARGET_VELOCITY).
+	dtPathQueueRef targetPathqRef;		///< Path finder ref.
+	bool targetReplan;					///< Flag indicating that the current path is being replanned.
+	float targetReplanTime;				/// <Time since the agent's target was replanned.
+};
+
+struct dtCrowdAgentAnimation
+{
+	bool active;
+	float initPos[3], startPos[3], endPos[3];
+	dtPolyRef polyRef;
+	float t, tmax;
+};
+
+/// Crowd agent update flags.
+/// @ingroup crowd
+/// @see dtCrowdAgentParams::updateFlags
+enum UpdateFlags
+{
+	DT_CROWD_ANTICIPATE_TURNS = 1,
+	DT_CROWD_OBSTACLE_AVOIDANCE = 2,
+	DT_CROWD_SEPARATION = 4,
+	DT_CROWD_OPTIMIZE_VIS = 8,			///< Use #dtPathCorridor::optimizePathVisibility() to optimize the agent path.
+	DT_CROWD_OPTIMIZE_TOPO = 16,		///< Use dtPathCorridor::optimizePathTopology() to optimize the agent path.
+};
+
+struct dtCrowdAgentDebugInfo
+{
+	int idx;
+	float optStart[3], optEnd[3];
+	dtObstacleAvoidanceDebugData* vod;
+};
+
+/// Provides local steering behaviors for a group of agents. 
+/// @ingroup crowd
+class dtCrowd
+{
+	int m_maxAgents;
+	dtCrowdAgent* m_agents;
+	dtCrowdAgent** m_activeAgents;
+	dtCrowdAgentAnimation* m_agentAnims;
+	
+	dtPathQueue m_pathq;
+
+	dtObstacleAvoidanceParams m_obstacleQueryParams[DT_CROWD_MAX_OBSTAVOIDANCE_PARAMS];
+	dtObstacleAvoidanceQuery* m_obstacleQuery;
+	
+	dtProximityGrid* m_grid;
+	
+	dtPolyRef* m_pathResult;
+	int m_maxPathResult;
+	
+	float m_ext[3];
+
+	dtQueryFilter m_filters[DT_CROWD_MAX_QUERY_FILTER_TYPE];
+
+	float m_maxAgentRadius;
+
+	int m_velocitySampleCount;
+
+	dtNavMeshQuery* m_navquery;
+
+	void updateTopologyOptimization(dtCrowdAgent** agents, const int nagents, const float dt);
+	void updateMoveRequest(const float dt);
+	void checkPathValidity(dtCrowdAgent** agents, const int nagents, const float dt);
+
+	inline int getAgentIndex(const dtCrowdAgent* agent) const  { return (int)(agent - m_agents); }
+
+	bool requestMoveTargetReplan(const int idx, dtPolyRef ref, const float* pos);
+
+	void purge();
+	
+public:
+	dtCrowd();
+	~dtCrowd();
+	
+	/// Initializes the crowd.  
+	///  @param[in]		maxAgents		The maximum number of agents the crowd can manage. [Limit: >= 1]
+	///  @param[in]		maxAgentRadius	The maximum radius of any agent that will be added to the crowd. [Limit: > 0]
+	///  @param[in]		nav				The navigation mesh to use for planning.
+	/// @return True if the initialization succeeded.
+	bool init(const int maxAgents, const float maxAgentRadius, dtNavMesh* nav);
+	
+	/// Sets the shared avoidance configuration for the specified index.
+	///  @param[in]		idx		The index. [Limits: 0 <= value < #DT_CROWD_MAX_OBSTAVOIDANCE_PARAMS]
+	///  @param[in]		params	The new configuration.
+	void setObstacleAvoidanceParams(const int idx, const dtObstacleAvoidanceParams* params);
+
+	/// Gets the shared avoidance configuration for the specified index.
+	///  @param[in]		idx		The index of the configuration to retreive. 
+	///							[Limits:  0 <= value < #DT_CROWD_MAX_OBSTAVOIDANCE_PARAMS]
+	/// @return The requested configuration.
+	const dtObstacleAvoidanceParams* getObstacleAvoidanceParams(const int idx) const;
+	
+	/// Gets the specified agent from the pool.
+	///	 @param[in]		idx		The agent index. [Limits: 0 <= value < #getAgentCount()]
+	/// @return The requested agent.
+	const dtCrowdAgent* getAgent(const int idx);
+
+	/// Gets the specified agent from the pool.
+	///	 @param[in]		idx		The agent index. [Limits: 0 <= value < #getAgentCount()]
+	/// @return The requested agent.
+	dtCrowdAgent* getEditableAgent(const int idx);
+
+	/// The maximum number of agents that can be managed by the object.
+	/// @return The maximum number of agents.
+	int getAgentCount() const;
+	
+	/// Adds a new agent to the crowd.
+	///  @param[in]		pos		The requested position of the agent. [(x, y, z)]
+	///  @param[in]		params	The configutation of the agent.
+	/// @return The index of the agent in the agent pool. Or -1 if the agent could not be added.
+	int addAgent(const float* pos, const dtCrowdAgentParams* params);
+
+	/// Updates the specified agent's configuration.
+	///  @param[in]		idx		The agent index. [Limits: 0 <= value < #getAgentCount()]
+	///  @param[in]		params	The new agent configuration.
+	void updateAgentParameters(const int idx, const dtCrowdAgentParams* params);
+
+	/// Removes the agent from the crowd.
+	///  @param[in]		idx		The agent index. [Limits: 0 <= value < #getAgentCount()]
+	void removeAgent(const int idx);
+	
+	/// Submits a new move request for the specified agent.
+	///  @param[in]		idx		The agent index. [Limits: 0 <= value < #getAgentCount()]
+	///  @param[in]		ref		The position's polygon reference.
+	///  @param[in]		pos		The position within the polygon. [(x, y, z)]
+	/// @return True if the request was successfully submitted.
+	bool requestMoveTarget(const int idx, dtPolyRef ref, const float* pos);
+
+	/// Submits a new move request for the specified agent.
+	///  @param[in]		idx		The agent index. [Limits: 0 <= value < #getAgentCount()]
+	///  @param[in]		vel		The movement velocity. [(x, y, z)]
+	/// @return True if the request was successfully submitted.
+	bool requestMoveVelocity(const int idx, const float* vel);
+
+	/// Resets any request for the specified agent.
+	///  @param[in]		idx		The agent index. [Limits: 0 <= value < #getAgentCount()]
+	/// @return True if the request was successfully reseted.
+	bool resetMoveTarget(const int idx);
+
+	/// Gets the active agents int the agent pool.
+	///  @param[out]	agents		An array of agent pointers. [(#dtCrowdAgent *) * maxAgents]
+	///  @param[in]		maxAgents	The size of the crowd agent array.
+	/// @return The number of agents returned in @p agents.
+	int getActiveAgents(dtCrowdAgent** agents, const int maxAgents);
+
+	/// Updates the steering and positions of all agents.
+	///  @param[in]		dt		The time, in seconds, to update the simulation. [Limit: > 0]
+	///  @param[out]	debug	A debug object to load with debug information. [Opt]
+	void update(const float dt, dtCrowdAgentDebugInfo* debug);
+	
+	/// Gets the filter used by the crowd.
+	/// @return The filter used by the crowd.
+	inline const dtQueryFilter* getFilter(const int i) const { return (i >= 0 && i < DT_CROWD_MAX_QUERY_FILTER_TYPE) ? &m_filters[i] : 0; }
+	
+	/// Gets the filter used by the crowd.
+	/// @return The filter used by the crowd.
+	inline dtQueryFilter* getEditableFilter(const int i) { return (i >= 0 && i < DT_CROWD_MAX_QUERY_FILTER_TYPE) ? &m_filters[i] : 0; }
+
+	/// Gets the search extents [(x, y, z)] used by the crowd for query operations. 
+	/// @return The search extents used by the crowd. [(x, y, z)]
+	const float* getQueryExtents() const { return m_ext; }
+	
+	/// Gets the velocity sample count.
+	/// @return The velocity sample count.
+	inline int getVelocitySampleCount() const { return m_velocitySampleCount; }
+	
+	/// Gets the crowd's proximity grid.
+	/// @return The crowd's proximity grid.
+	const dtProximityGrid* getGrid() const { return m_grid; }
+
+	/// Gets the crowd's path request queue.
+	/// @return The crowd's path request queue.
+	const dtPathQueue* getPathQueue() const { return &m_pathq; }
+
+	/// Gets the query object used by the crowd.
+	const dtNavMeshQuery* getNavMeshQuery() const { return m_navquery; }
+};
+
+/// Allocates a crowd object using the Detour allocator.
+/// @return A crowd object that is ready for initialization, or null on failure.
+///  @ingroup crowd
+dtCrowd* dtAllocCrowd();
+
+/// Frees the specified crowd object using the Detour allocator.
+///  @param[in]		ptr		A crowd object allocated using #dtAllocCrowd
+///  @ingroup crowd
+void dtFreeCrowd(dtCrowd* ptr);
+
+
+#endif // DETOURCROWD_H
+
+///////////////////////////////////////////////////////////////////////////
+
+// This section contains detailed documentation for members that don't have
+// a source file. It reduces clutter in the main section of the header.
+
+/**
+
+@defgroup crowd Crowd
+
+Members in this module implement local steering and dynamic avoidance features.
+
+The crowd is the big beast of the navigation features. It not only handles a 
+lot of the path management for you, but also local steering and dynamic 
+avoidance between members of the crowd. I.e. It can keep your agents from 
+running into each other.
+
+Main class: #dtCrowd
+
+The #dtNavMeshQuery and #dtPathCorridor classes provide perfectly good, easy 
+to use path planning features. But in the end they only give you points that 
+your navigation client should be moving toward. When it comes to deciding things 
+like agent velocity and steering to avoid other agents, that is up to you to 
+implement. Unless, of course, you decide to use #dtCrowd.
+
+Basically, you add an agent to the crowd, providing various configuration 
+settings such as maximum speed and acceleration. You also provide a local 
+target to more toward. The crowd manager then provides, with every update, the 
+new agent position and velocity for the frame. The movement will be 
+constrained to the navigation mesh, and steering will be applied to ensure 
+agents managed by the crowd do not collide with each other.
+
+This is very powerful feature set. But it comes with limitations.
+
+The biggest limitation is that you must give control of the agent's position 
+completely over to the crowd manager. You can update things like maximum speed 
+and acceleration. But in order for the crowd manager to do its thing, it can't 
+allow you to constantly be giving it overrides to position and velocity. So 
+you give up direct control of the agent's movement. It belongs to the crowd.
+
+The second biggest limitation revolves around the fact that the crowd manager 
+deals with local planning. So the agent's target should never be more than 
+256 polygons aways from its current position. If it is, you risk 
+your agent failing to reach its target. So you may still need to do long 
+distance planning and provide the crowd manager with intermediate targets.
+
+Other significant limitations:
+
+- All agents using the crowd manager will use the same #dtQueryFilter.
+- Crowd management is relatively expensive. The maximum agents under crowd 
+  management at any one time is between 20 and 30.  A good place to start
+  is a maximum of 25 agents for 0.5ms per frame.
+
+@note This is a summary list of members.  Use the index or search 
+feature to find minor members.
+
+@struct dtCrowdAgentParams
+@see dtCrowdAgent, dtCrowd::addAgent(), dtCrowd::updateAgentParameters()
+
+@var dtCrowdAgentParams::obstacleAvoidanceType
+@par
+
+#dtCrowd permits agents to use different avoidance configurations.  This value 
+is the index of the #dtObstacleAvoidanceParams within the crowd.
+
+@see dtObstacleAvoidanceParams, dtCrowd::setObstacleAvoidanceParams(), 
+	 dtCrowd::getObstacleAvoidanceParams()
+
+@var dtCrowdAgentParams::collisionQueryRange
+@par
+
+Collision elements include other agents and navigation mesh boundaries.
+
+This value is often based on the agent radius and/or maximum speed. E.g. radius * 8
+
+@var dtCrowdAgentParams::pathOptimizationRange
+@par
+
+Only applicalbe if #updateFlags includes the #DT_CROWD_OPTIMIZE_VIS flag.
+
+This value is often based on the agent radius. E.g. radius * 30
+
+@see dtPathCorridor::optimizePathVisibility()
+
+@var dtCrowdAgentParams::separationWeight
+@par
+
+A higher value will result in agents trying to stay farther away from each other at 
+the cost of more difficult steering in tight spaces.
+
+*/

+ 61 - 0
Source/ThirdParty/DetourCrowd/include/DetourLocalBoundary.h

@@ -0,0 +1,61 @@
+//
+// Copyright (c) 2009-2010 Mikko Mononen [email protected]
+//
+// 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 DETOURLOCALBOUNDARY_H
+#define DETOURLOCALBOUNDARY_H
+
+#include "DetourNavMeshQuery.h"
+
+
+class dtLocalBoundary
+{
+	static const int MAX_LOCAL_SEGS = 8;
+	static const int MAX_LOCAL_POLYS = 16;
+	
+	struct Segment
+	{
+		float s[6];	///< Segment start/end
+		float d;	///< Distance for pruning.
+	};
+	
+	float m_center[3];
+	Segment m_segs[MAX_LOCAL_SEGS];
+	int m_nsegs;
+	
+	dtPolyRef m_polys[MAX_LOCAL_POLYS];
+	int m_npolys;
+
+	void addSegment(const float dist, const float* seg);
+	
+public:
+	dtLocalBoundary();
+	~dtLocalBoundary();
+	
+	void reset();
+	
+	void update(dtPolyRef ref, const float* pos, const float collisionQueryRange,
+				dtNavMeshQuery* navquery, const dtQueryFilter* filter);
+	
+	bool isValid(dtNavMeshQuery* navquery, const dtQueryFilter* filter);
+	
+	inline const float* getCenter() const { return m_center; }
+	inline int getSegmentCount() const { return m_nsegs; }
+	inline const float* getSegment(int i) const { return m_segs[i].s; }
+};
+
+#endif // DETOURLOCALBOUNDARY_H

+ 154 - 0
Source/ThirdParty/DetourCrowd/include/DetourObstacleAvoidance.h

@@ -0,0 +1,154 @@
+//
+// Copyright (c) 2009-2010 Mikko Mononen [email protected]
+//
+// 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 DETOUROBSTACLEAVOIDANCE_H
+#define DETOUROBSTACLEAVOIDANCE_H
+
+struct dtObstacleCircle
+{
+	float p[3];				///< Position of the obstacle
+	float vel[3];			///< Velocity of the obstacle
+	float dvel[3];			///< Velocity of the obstacle
+	float rad;				///< Radius of the obstacle
+	float dp[3], np[3];		///< Use for side selection during sampling.
+};
+
+struct dtObstacleSegment
+{
+	float p[3], q[3];		///< End points of the obstacle segment
+	bool touch;
+};
+
+
+class dtObstacleAvoidanceDebugData
+{
+public:
+	dtObstacleAvoidanceDebugData();
+	~dtObstacleAvoidanceDebugData();
+	
+	bool init(const int maxSamples);
+	void reset();
+	void addSample(const float* vel, const float ssize, const float pen,
+				   const float vpen, const float vcpen, const float spen, const float tpen);
+	
+	void normalizeSamples();
+	
+	inline int getSampleCount() const { return m_nsamples; }
+	inline const float* getSampleVelocity(const int i) const { return &m_vel[i*3]; }
+	inline float getSampleSize(const int i) const { return m_ssize[i]; }
+	inline float getSamplePenalty(const int i) const { return m_pen[i]; }
+	inline float getSampleDesiredVelocityPenalty(const int i) const { return m_vpen[i]; }
+	inline float getSampleCurrentVelocityPenalty(const int i) const { return m_vcpen[i]; }
+	inline float getSamplePreferredSidePenalty(const int i) const { return m_spen[i]; }
+	inline float getSampleCollisionTimePenalty(const int i) const { return m_tpen[i]; }
+
+private:
+	int m_nsamples;
+	int m_maxSamples;
+	float* m_vel;
+	float* m_ssize;
+	float* m_pen;
+	float* m_vpen;
+	float* m_vcpen;
+	float* m_spen;
+	float* m_tpen;
+};
+
+dtObstacleAvoidanceDebugData* dtAllocObstacleAvoidanceDebugData();
+void dtFreeObstacleAvoidanceDebugData(dtObstacleAvoidanceDebugData* ptr);
+
+
+static const int DT_MAX_PATTERN_DIVS = 32;	///< Max numver of adaptive divs.
+static const int DT_MAX_PATTERN_RINGS = 4;	///< Max number of adaptive rings.
+
+struct dtObstacleAvoidanceParams
+{
+	float velBias;
+	float weightDesVel;
+	float weightCurVel;
+	float weightSide;
+	float weightToi;
+	float horizTime;
+	unsigned char gridSize;	///< grid
+	unsigned char adaptiveDivs;	///< adaptive
+	unsigned char adaptiveRings;	///< adaptive
+	unsigned char adaptiveDepth;	///< adaptive
+};
+
+class dtObstacleAvoidanceQuery
+{
+public:
+	dtObstacleAvoidanceQuery();
+	~dtObstacleAvoidanceQuery();
+	
+	bool init(const int maxCircles, const int maxSegments);
+	
+	void reset();
+
+	void addCircle(const float* pos, const float rad,
+				   const float* vel, const float* dvel);
+				   
+	void addSegment(const float* p, const float* q);
+
+	int sampleVelocityGrid(const float* pos, const float rad, const float vmax,
+						   const float* vel, const float* dvel, float* nvel,
+						   const dtObstacleAvoidanceParams* params,
+						   dtObstacleAvoidanceDebugData* debug = 0);
+
+	int sampleVelocityAdaptive(const float* pos, const float rad, const float vmax,
+							   const float* vel, const float* dvel, float* nvel,
+							   const dtObstacleAvoidanceParams* params, 
+							   dtObstacleAvoidanceDebugData* debug = 0);
+	
+	inline int getObstacleCircleCount() const { return m_ncircles; }
+	const dtObstacleCircle* getObstacleCircle(const int i) { return &m_circles[i]; }
+
+	inline int getObstacleSegmentCount() const { return m_nsegments; }
+	const dtObstacleSegment* getObstacleSegment(const int i) { return &m_segments[i]; }
+
+private:
+
+	void prepare(const float* pos, const float* dvel);
+
+	float processSample(const float* vcand, const float cs,
+						const float* pos, const float rad,
+						const float* vel, const float* dvel,
+						dtObstacleAvoidanceDebugData* debug);
+
+	dtObstacleCircle* insertCircle(const float dist);
+	dtObstacleSegment* insertSegment(const float dist);
+
+	dtObstacleAvoidanceParams m_params;
+	float m_invHorizTime;
+	float m_vmax;
+	float m_invVmax;
+
+	int m_maxCircles;
+	dtObstacleCircle* m_circles;
+	int m_ncircles;
+
+	int m_maxSegments;
+	dtObstacleSegment* m_segments;
+	int m_nsegments;
+};
+
+dtObstacleAvoidanceQuery* dtAllocObstacleAvoidanceQuery();
+void dtFreeObstacleAvoidanceQuery(dtObstacleAvoidanceQuery* ptr);
+
+
+#endif // DETOUROBSTACLEAVOIDANCE_H

+ 146 - 0
Source/ThirdParty/DetourCrowd/include/DetourPathCorridor.h

@@ -0,0 +1,146 @@
+//
+// Copyright (c) 2009-2010 Mikko Mononen [email protected]
+//
+// 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 DETOUTPATHCORRIDOR_H
+#define DETOUTPATHCORRIDOR_H
+
+#include "DetourNavMeshQuery.h"
+
+/// Represents a dynamic polygon corridor used to plan agent movement.
+/// @ingroup crowd, detour
+class dtPathCorridor
+{
+	float m_pos[3];
+	float m_target[3];
+	
+	dtPolyRef* m_path;
+	int m_npath;
+	int m_maxPath;
+	
+public:
+	dtPathCorridor();
+	~dtPathCorridor();
+	
+	/// Allocates the corridor's path buffer. 
+	///  @param[in]		maxPath		The maximum path size the corridor can handle.
+	/// @return True if the initialization succeeded.
+	bool init(const int maxPath);
+	
+	/// Resets the path corridor to the specified position.
+	///  @param[in]		ref		The polygon reference containing the position.
+	///  @param[in]		pos		The new position in the corridor. [(x, y, z)]
+	void reset(dtPolyRef ref, const float* pos);
+	
+	/// Finds the corners in the corridor from the position toward the target. (The straightened path.)
+	///  @param[out]	cornerVerts		The corner vertices. [(x, y, z) * cornerCount] [Size: <= maxCorners]
+	///  @param[out]	cornerFlags		The flag for each corner. [(flag) * cornerCount] [Size: <= maxCorners]
+	///  @param[out]	cornerPolys		The polygon reference for each corner. [(polyRef) * cornerCount] 
+	///  								[Size: <= @p maxCorners]
+	///  @param[in]		maxCorners		The maximum number of corners the buffers can hold.
+	///  @param[in]		navquery		The query object used to build the corridor.
+	///  @param[in]		filter			The filter to apply to the operation.
+	/// @return The number of corners returned in the corner buffers. [0 <= value <= @p maxCorners]
+	int findCorners(float* cornerVerts, unsigned char* cornerFlags,
+					dtPolyRef* cornerPolys, const int maxCorners,
+					dtNavMeshQuery* navquery, const dtQueryFilter* filter);
+	
+	/// Attempts to optimize the path if the specified point is visible from the current position.
+	///  @param[in]		next					The point to search toward. [(x, y, z])
+	///  @param[in]		pathOptimizationRange	The maximum range to search. [Limit: > 0]
+	///  @param[in]		navquery				The query object used to build the corridor.
+	///  @param[in]		filter					The filter to apply to the operation.			
+	void optimizePathVisibility(const float* next, const float pathOptimizationRange,
+								dtNavMeshQuery* navquery, const dtQueryFilter* filter);
+	
+	/// Attempts to optimize the path using a local area search. (Partial replanning.) 
+	///  @param[in]		navquery	The query object used to build the corridor.
+	///  @param[in]		filter		The filter to apply to the operation.	
+	bool optimizePathTopology(dtNavMeshQuery* navquery, const dtQueryFilter* filter);
+	
+	bool moveOverOffmeshConnection(dtPolyRef offMeshConRef, dtPolyRef* refs,
+								   float* startPos, float* endPos,
+								   dtNavMeshQuery* navquery);
+
+	bool fixPathStart(dtPolyRef safeRef, const float* safePos);
+
+	bool trimInvalidPath(dtPolyRef safeRef, const float* safePos,
+						 dtNavMeshQuery* navquery, const dtQueryFilter* filter);
+	
+	/// Checks the current corridor path to see if its polygon references remain valid. 
+	///  @param[in]		maxLookAhead	The number of polygons from the beginning of the corridor to search.
+	///  @param[in]		navquery		The query object used to build the corridor.
+	///  @param[in]		filter			The filter to apply to the operation.	
+	bool isValid(const int maxLookAhead, dtNavMeshQuery* navquery, const dtQueryFilter* filter);
+	
+	/// Moves the position from the current location to the desired location, adjusting the corridor 
+	/// as needed to reflect the change.
+	///  @param[in]		npos		The desired new position. [(x, y, z)]
+	///  @param[in]		navquery	The query object used to build the corridor.
+	///  @param[in]		filter		The filter to apply to the operation.
+	/// @return Returns true if move succeeded.
+	bool movePosition(const float* npos, dtNavMeshQuery* navquery, const dtQueryFilter* filter);
+
+	/// Moves the target from the curent location to the desired location, adjusting the corridor
+	/// as needed to reflect the change. 
+	///  @param[in]		npos		The desired new target position. [(x, y, z)]
+	///  @param[in]		navquery	The query object used to build the corridor.
+	///  @param[in]		filter		The filter to apply to the operation.
+	/// @return Returns true if move succeeded.
+	bool moveTargetPosition(const float* npos, dtNavMeshQuery* navquery, const dtQueryFilter* filter);
+	
+	/// Loads a new path and target into the corridor.
+	///  @param[in]		target		The target location within the last polygon of the path. [(x, y, z)]
+	///  @param[in]		path		The path corridor. [(polyRef) * @p npolys]
+	///  @param[in]		npath		The number of polygons in the path.
+	void setCorridor(const float* target, const dtPolyRef* polys, const int npath);
+	
+	/// Gets the current position within the corridor. (In the first polygon.)
+	/// @return The current position within the corridor.
+	inline const float* getPos() const { return m_pos; }
+
+	/// Gets the current target within the corridor. (In the last polygon.)
+	/// @return The current target within the corridor.
+	inline const float* getTarget() const { return m_target; }
+	
+	/// The polygon reference id of the first polygon in the corridor, the polygon containing the position.
+	/// @return The polygon reference id of the first polygon in the corridor. (Or zero if there is no path.)
+	inline dtPolyRef getFirstPoly() const { return m_npath ? m_path[0] : 0; }
+
+	/// The polygon reference id of the last polygon in the corridor, the polygon containing the target.
+	/// @return The polygon reference id of the last polygon in the corridor. (Or zero if there is no path.)
+	inline dtPolyRef getLastPoly() const { return m_npath ? m_path[m_npath-1] : 0; }
+	
+	/// The corridor's path.
+	/// @return The corridor's path. [(polyRef) * #getPathCount()]
+	inline const dtPolyRef* getPath() const { return m_path; }
+
+	/// The number of polygons in the current corridor path.
+	/// @return The number of polygons in the current corridor path.
+	inline int getPathCount() const { return m_npath; } 	
+};
+
+int dtMergeCorridorStartMoved(dtPolyRef* path, const int npath, const int maxPath,
+							  const dtPolyRef* visited, const int nvisited);
+
+int dtMergeCorridorEndMoved(dtPolyRef* path, const int npath, const int maxPath,
+							const dtPolyRef* visited, const int nvisited);
+
+int dtMergeCorridorStartShortcut(dtPolyRef* path, const int npath, const int maxPath,
+								 const dtPolyRef* visited, const int nvisited);
+
+#endif // DETOUTPATHCORRIDOR_H

+ 75 - 0
Source/ThirdParty/DetourCrowd/include/DetourPathQueue.h

@@ -0,0 +1,75 @@
+//
+// Copyright (c) 2009-2010 Mikko Mononen [email protected]
+//
+// 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 DETOURPATHQUEUE_H
+#define DETOURPATHQUEUE_H
+
+#include "DetourNavMesh.h"
+#include "DetourNavMeshQuery.h"
+
+static const unsigned int DT_PATHQ_INVALID = 0;
+
+typedef unsigned int dtPathQueueRef;
+
+class dtPathQueue
+{
+	struct PathQuery
+	{
+		dtPathQueueRef ref;
+		/// Path find start and end location.
+		float startPos[3], endPos[3];
+		dtPolyRef startRef, endRef;
+		/// Result.
+		dtPolyRef* path;
+		int npath;
+		/// State.
+		dtStatus status;
+		int keepAlive;
+		const dtQueryFilter* filter; ///< TODO: This is potentially dangerous!
+	};
+	
+	static const int MAX_QUEUE = 8;
+	PathQuery m_queue[MAX_QUEUE];
+	dtPathQueueRef m_nextHandle;
+	int m_maxPathSize;
+	int m_queueHead;
+	dtNavMeshQuery* m_navquery;
+	
+	void purge();
+	
+public:
+	dtPathQueue();
+	~dtPathQueue();
+	
+	bool init(const int maxPathSize, const int maxSearchNodeCount, dtNavMesh* nav);
+	
+	void update(const int maxIters);
+	
+	dtPathQueueRef request(dtPolyRef startRef, dtPolyRef endRef,
+						   const float* startPos, const float* endPos, 
+						   const dtQueryFilter* filter);
+	
+	dtStatus getRequestStatus(dtPathQueueRef ref) const;
+	
+	dtStatus getPathResult(dtPathQueueRef ref, dtPolyRef* path, int* pathSize, const int maxPath);
+	
+	inline const dtNavMeshQuery* getNavQuery() const { return m_navquery; }
+
+};
+
+#endif // DETOURPATHQUEUE_H

+ 70 - 0
Source/ThirdParty/DetourCrowd/include/DetourProximityGrid.h

@@ -0,0 +1,70 @@
+//
+// Copyright (c) 2009-2010 Mikko Mononen [email protected]
+//
+// 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 DETOURPROXIMITYGRID_H
+#define DETOURPROXIMITYGRID_H
+
+class dtProximityGrid
+{
+	int m_maxItems;
+	float m_cellSize;
+	float m_invCellSize;
+	
+	struct Item
+	{
+		unsigned short id;
+		short x,y;
+		unsigned short next;
+	};
+	Item* m_pool;
+	int m_poolHead;
+	int m_poolSize;
+	
+	unsigned short* m_buckets;
+	int m_bucketsSize;
+	
+	int m_bounds[4];
+	
+public:
+	dtProximityGrid();
+	~dtProximityGrid();
+	
+	bool init(const int maxItems, const float cellSize);
+	
+	void clear();
+	
+	void addItem(const unsigned short id,
+				 const float minx, const float miny,
+				 const float maxx, const float maxy);
+	
+	int queryItems(const float minx, const float miny,
+				   const float maxx, const float maxy,
+				   unsigned short* ids, const int maxIds) const;
+	
+	int getItemCountAt(const int x, const int y) const;
+	
+	inline const int* getBounds() const { return m_bounds; }
+	inline float getCellSize() const { return m_cellSize; }
+};
+
+dtProximityGrid* dtAllocProximityGrid();
+void dtFreeProximityGrid(dtProximityGrid* ptr);
+
+
+#endif // DETOURPROXIMITYGRID_H
+

+ 1446 - 0
Source/ThirdParty/DetourCrowd/source/DetourCrowd.cpp

@@ -0,0 +1,1446 @@
+//
+// Copyright (c) 2009-2010 Mikko Mononen [email protected]
+//
+// 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 <string.h>
+#include <float.h>
+#include <stdlib.h>
+#include <new>
+#include "DetourCrowd.h"
+#include "DetourNavMesh.h"
+#include "DetourNavMeshQuery.h"
+#include "DetourObstacleAvoidance.h"
+#include "DetourCommon.h"
+#include "DetourMath.h"
+#include "DetourAssert.h"
+#include "DetourAlloc.h"
+
+
+dtCrowd* dtAllocCrowd()
+{
+	void* mem = dtAlloc(sizeof(dtCrowd), DT_ALLOC_PERM);
+	if (!mem) return 0;
+	return new(mem) dtCrowd;
+}
+
+void dtFreeCrowd(dtCrowd* ptr)
+{
+	if (!ptr) return;
+	ptr->~dtCrowd();
+	dtFree(ptr);
+}
+
+
+static const int MAX_ITERS_PER_UPDATE = 100;
+
+static const int MAX_PATHQUEUE_NODES = 4096;
+static const int MAX_COMMON_NODES = 512;
+
+inline float tween(const float t, const float t0, const float t1)
+{
+	return dtClamp((t-t0) / (t1-t0), 0.0f, 1.0f);
+}
+
+static void integrate(dtCrowdAgent* ag, const float dt)
+{
+	// Fake dynamic constraint.
+	const float maxDelta = ag->params.maxAcceleration * dt;
+	float dv[3];
+	dtVsub(dv, ag->nvel, ag->vel);
+	float ds = dtVlen(dv);
+	if (ds > maxDelta)
+		dtVscale(dv, dv, maxDelta/ds);
+	dtVadd(ag->vel, ag->vel, dv);
+	
+	// Integrate
+	if (dtVlen(ag->vel) > 0.0001f)
+		dtVmad(ag->npos, ag->npos, ag->vel, dt);
+	else
+		dtVset(ag->vel,0,0,0);
+}
+
+static bool overOffmeshConnection(const dtCrowdAgent* ag, const float radius)
+{
+	if (!ag->ncorners)
+		return false;
+	
+	const bool offMeshConnection = (ag->cornerFlags[ag->ncorners-1] & DT_STRAIGHTPATH_OFFMESH_CONNECTION) ? true : false;
+	if (offMeshConnection)
+	{
+		const float distSq = dtVdist2DSqr(ag->npos, &ag->cornerVerts[(ag->ncorners-1)*3]);
+		if (distSq < radius*radius)
+			return true;
+	}
+	
+	return false;
+}
+
+static float getDistanceToGoal(const dtCrowdAgent* ag, const float range)
+{
+	if (!ag->ncorners)
+		return range;
+	
+	const bool endOfPath = (ag->cornerFlags[ag->ncorners-1] & DT_STRAIGHTPATH_END) ? true : false;
+	if (endOfPath)
+		return dtMin(dtVdist2D(ag->npos, &ag->cornerVerts[(ag->ncorners-1)*3]), range);
+	
+	return range;
+}
+
+static void calcSmoothSteerDirection(const dtCrowdAgent* ag, float* dir)
+{
+	if (!ag->ncorners)
+	{
+		dtVset(dir, 0,0,0);
+		return;
+	}
+	
+	const int ip0 = 0;
+	const int ip1 = dtMin(1, ag->ncorners-1);
+	const float* p0 = &ag->cornerVerts[ip0*3];
+	const float* p1 = &ag->cornerVerts[ip1*3];
+	
+	float dir0[3], dir1[3];
+	dtVsub(dir0, p0, ag->npos);
+	dtVsub(dir1, p1, ag->npos);
+	dir0[1] = 0;
+	dir1[1] = 0;
+	
+	float len0 = dtVlen(dir0);
+	float len1 = dtVlen(dir1);
+	if (len1 > 0.001f)
+		dtVscale(dir1,dir1,1.0f/len1);
+	
+	dir[0] = dir0[0] - dir1[0]*len0*0.5f;
+	dir[1] = 0;
+	dir[2] = dir0[2] - dir1[2]*len0*0.5f;
+	
+	dtVnormalize(dir);
+}
+
+static void calcStraightSteerDirection(const dtCrowdAgent* ag, float* dir)
+{
+	if (!ag->ncorners)
+	{
+		dtVset(dir, 0,0,0);
+		return;
+	}
+	dtVsub(dir, &ag->cornerVerts[0], ag->npos);
+	dir[1] = 0;
+	dtVnormalize(dir);
+}
+
+static int addNeighbour(const int idx, const float dist,
+						dtCrowdNeighbour* neis, const int nneis, const int maxNeis)
+{
+	// Insert neighbour based on the distance.
+	dtCrowdNeighbour* nei = 0;
+	if (!nneis)
+	{
+		nei = &neis[nneis];
+	}
+	else if (dist >= neis[nneis-1].dist)
+	{
+		if (nneis >= maxNeis)
+			return nneis;
+		nei = &neis[nneis];
+	}
+	else
+	{
+		int i;
+		for (i = 0; i < nneis; ++i)
+			if (dist <= neis[i].dist)
+				break;
+		
+		const int tgt = i+1;
+		const int n = dtMin(nneis-i, maxNeis-tgt);
+		
+		dtAssert(tgt+n <= maxNeis);
+		
+		if (n > 0)
+			memmove(&neis[tgt], &neis[i], sizeof(dtCrowdNeighbour)*n);
+		nei = &neis[i];
+	}
+	
+	memset(nei, 0, sizeof(dtCrowdNeighbour));
+	
+	nei->idx = idx;
+	nei->dist = dist;
+	
+	return dtMin(nneis+1, maxNeis);
+}
+
+static int getNeighbours(const float* pos, const float height, const float range,
+						 const dtCrowdAgent* skip, dtCrowdNeighbour* result, const int maxResult,
+						 dtCrowdAgent** agents, const int /*nagents*/, dtProximityGrid* grid)
+{
+	int n = 0;
+	
+	static const int MAX_NEIS = 32;
+	unsigned short ids[MAX_NEIS];
+	int nids = grid->queryItems(pos[0]-range, pos[2]-range,
+								pos[0]+range, pos[2]+range,
+								ids, MAX_NEIS);
+	
+	for (int i = 0; i < nids; ++i)
+	{
+		const dtCrowdAgent* ag = agents[ids[i]];
+		
+		if (ag == skip) continue;
+		
+		// Check for overlap.
+		float diff[3];
+		dtVsub(diff, pos, ag->npos);
+		if (dtMathFabs(diff[1]) >= (height+ag->params.height)/2.0f)
+			continue;
+		diff[1] = 0;
+		const float distSqr = dtVlenSqr(diff);
+		if (distSqr > dtSqr(range))
+			continue;
+		
+		n = addNeighbour(ids[i], distSqr, result, n, maxResult);
+	}
+	return n;
+}
+
+static int addToOptQueue(dtCrowdAgent* newag, dtCrowdAgent** agents, const int nagents, const int maxAgents)
+{
+	// Insert neighbour based on greatest time.
+	int slot = 0;
+	if (!nagents)
+	{
+		slot = nagents;
+	}
+	else if (newag->topologyOptTime <= agents[nagents-1]->topologyOptTime)
+	{
+		if (nagents >= maxAgents)
+			return nagents;
+		slot = nagents;
+	}
+	else
+	{
+		int i;
+		for (i = 0; i < nagents; ++i)
+			if (newag->topologyOptTime >= agents[i]->topologyOptTime)
+				break;
+		
+		const int tgt = i+1;
+		const int n = dtMin(nagents-i, maxAgents-tgt);
+		
+		dtAssert(tgt+n <= maxAgents);
+		
+		if (n > 0)
+			memmove(&agents[tgt], &agents[i], sizeof(dtCrowdAgent*)*n);
+		slot = i;
+	}
+	
+	agents[slot] = newag;
+	
+	return dtMin(nagents+1, maxAgents);
+}
+
+static int addToPathQueue(dtCrowdAgent* newag, dtCrowdAgent** agents, const int nagents, const int maxAgents)
+{
+	// Insert neighbour based on greatest time.
+	int slot = 0;
+	if (!nagents)
+	{
+		slot = nagents;
+	}
+	else if (newag->targetReplanTime <= agents[nagents-1]->targetReplanTime)
+	{
+		if (nagents >= maxAgents)
+			return nagents;
+		slot = nagents;
+	}
+	else
+	{
+		int i;
+		for (i = 0; i < nagents; ++i)
+			if (newag->targetReplanTime >= agents[i]->targetReplanTime)
+				break;
+		
+		const int tgt = i+1;
+		const int n = dtMin(nagents-i, maxAgents-tgt);
+		
+		dtAssert(tgt+n <= maxAgents);
+		
+		if (n > 0)
+			memmove(&agents[tgt], &agents[i], sizeof(dtCrowdAgent*)*n);
+		slot = i;
+	}
+	
+	agents[slot] = newag;
+	
+	return dtMin(nagents+1, maxAgents);
+}
+
+
+/**
+@class dtCrowd
+@par
+
+This is the core class of the @ref crowd module.  See the @ref crowd documentation for a summary
+of the crowd features.
+
+A common method for setting up the crowd is as follows:
+
+-# Allocate the crowd using #dtAllocCrowd.
+-# Initialize the crowd using #init().
+-# Set the avoidance configurations using #setObstacleAvoidanceParams().
+-# Add agents using #addAgent() and make an initial movement request using #requestMoveTarget().
+
+A common process for managing the crowd is as follows:
+
+-# Call #update() to allow the crowd to manage its agents.
+-# Retrieve agent information using #getActiveAgents().
+-# Make movement requests using #requestMoveTarget() when movement goal changes.
+-# Repeat every frame.
+
+Some agent configuration settings can be updated using #updateAgentParameters().  But the crowd owns the
+agent position.  So it is not possible to update an active agent's position.  If agent position
+must be fed back into the crowd, the agent must be removed and re-added.
+
+Notes: 
+
+- Path related information is available for newly added agents only after an #update() has been
+  performed.
+- Agent objects are kept in a pool and re-used.  So it is important when using agent objects to check the value of
+  #dtCrowdAgent::active to determine if the agent is actually in use or not.
+- This class is meant to provide 'local' movement. There is a limit of 256 polygons in the path corridor.  
+  So it is not meant to provide automatic pathfinding services over long distances.
+
+@see dtAllocCrowd(), dtFreeCrowd(), init(), dtCrowdAgent
+
+*/
+
+dtCrowd::dtCrowd() :
+	m_maxAgents(0),
+	m_agents(0),
+	m_activeAgents(0),
+	m_agentAnims(0),
+	m_obstacleQuery(0),
+	m_grid(0),
+	m_pathResult(0),
+	m_maxPathResult(0),
+	m_maxAgentRadius(0),
+	m_velocitySampleCount(0),
+	m_navquery(0)
+{
+}
+
+dtCrowd::~dtCrowd()
+{
+	purge();
+}
+
+void dtCrowd::purge()
+{
+	for (int i = 0; i < m_maxAgents; ++i)
+		m_agents[i].~dtCrowdAgent();
+	dtFree(m_agents);
+	m_agents = 0;
+	m_maxAgents = 0;
+	
+	dtFree(m_activeAgents);
+	m_activeAgents = 0;
+
+	dtFree(m_agentAnims);
+	m_agentAnims = 0;
+	
+	dtFree(m_pathResult);
+	m_pathResult = 0;
+	
+	dtFreeProximityGrid(m_grid);
+	m_grid = 0;
+
+	dtFreeObstacleAvoidanceQuery(m_obstacleQuery);
+	m_obstacleQuery = 0;
+	
+	dtFreeNavMeshQuery(m_navquery);
+	m_navquery = 0;
+}
+
+/// @par
+///
+/// May be called more than once to purge and re-initialize the crowd.
+bool dtCrowd::init(const int maxAgents, const float maxAgentRadius, dtNavMesh* nav)
+{
+	purge();
+	
+	m_maxAgents = maxAgents;
+	m_maxAgentRadius = maxAgentRadius;
+
+	dtVset(m_ext, m_maxAgentRadius*2.0f,m_maxAgentRadius*1.5f,m_maxAgentRadius*2.0f);
+	
+	m_grid = dtAllocProximityGrid();
+	if (!m_grid)
+		return false;
+	if (!m_grid->init(m_maxAgents*4, maxAgentRadius*3))
+		return false;
+	
+	m_obstacleQuery = dtAllocObstacleAvoidanceQuery();
+	if (!m_obstacleQuery)
+		return false;
+	if (!m_obstacleQuery->init(6, 8))
+		return false;
+
+	// Init obstacle query params.
+	memset(m_obstacleQueryParams, 0, sizeof(m_obstacleQueryParams));
+	for (int i = 0; i < DT_CROWD_MAX_OBSTAVOIDANCE_PARAMS; ++i)
+	{
+		dtObstacleAvoidanceParams* params = &m_obstacleQueryParams[i];
+		params->velBias = 0.4f;
+		params->weightDesVel = 2.0f;
+		params->weightCurVel = 0.75f;
+		params->weightSide = 0.75f;
+		params->weightToi = 2.5f;
+		params->horizTime = 2.5f;
+		params->gridSize = 33;
+		params->adaptiveDivs = 7;
+		params->adaptiveRings = 2;
+		params->adaptiveDepth = 5;
+	}
+	
+	// Allocate temp buffer for merging paths.
+	m_maxPathResult = 256;
+	m_pathResult = (dtPolyRef*)dtAlloc(sizeof(dtPolyRef)*m_maxPathResult, DT_ALLOC_PERM);
+	if (!m_pathResult)
+		return false;
+	
+	if (!m_pathq.init(m_maxPathResult, MAX_PATHQUEUE_NODES, nav))
+		return false;
+	
+	m_agents = (dtCrowdAgent*)dtAlloc(sizeof(dtCrowdAgent)*m_maxAgents, DT_ALLOC_PERM);
+	if (!m_agents)
+		return false;
+	
+	m_activeAgents = (dtCrowdAgent**)dtAlloc(sizeof(dtCrowdAgent*)*m_maxAgents, DT_ALLOC_PERM);
+	if (!m_activeAgents)
+		return false;
+
+	m_agentAnims = (dtCrowdAgentAnimation*)dtAlloc(sizeof(dtCrowdAgentAnimation)*m_maxAgents, DT_ALLOC_PERM);
+	if (!m_agentAnims)
+		return false;
+	
+	for (int i = 0; i < m_maxAgents; ++i)
+	{
+		new(&m_agents[i]) dtCrowdAgent();
+		m_agents[i].active = false;
+		if (!m_agents[i].corridor.init(m_maxPathResult))
+			return false;
+	}
+
+	for (int i = 0; i < m_maxAgents; ++i)
+	{
+		m_agentAnims[i].active = false;
+	}
+
+	// The navquery is mostly used for local searches, no need for large node pool.
+	m_navquery = dtAllocNavMeshQuery();
+	if (!m_navquery)
+		return false;
+	if (dtStatusFailed(m_navquery->init(nav, MAX_COMMON_NODES)))
+		return false;
+	
+	return true;
+}
+
+void dtCrowd::setObstacleAvoidanceParams(const int idx, const dtObstacleAvoidanceParams* params)
+{
+	if (idx >= 0 && idx < DT_CROWD_MAX_OBSTAVOIDANCE_PARAMS)
+		memcpy(&m_obstacleQueryParams[idx], params, sizeof(dtObstacleAvoidanceParams));
+}
+
+const dtObstacleAvoidanceParams* dtCrowd::getObstacleAvoidanceParams(const int idx) const
+{
+	if (idx >= 0 && idx < DT_CROWD_MAX_OBSTAVOIDANCE_PARAMS)
+		return &m_obstacleQueryParams[idx];
+	return 0;
+}
+
+int dtCrowd::getAgentCount() const
+{
+	return m_maxAgents;
+}
+
+/// @par
+/// 
+/// Agents in the pool may not be in use.  Check #dtCrowdAgent.active before using the returned object.
+const dtCrowdAgent* dtCrowd::getAgent(const int idx)
+{
+	if (idx < 0 || idx >= m_maxAgents)
+		return 0;
+	return &m_agents[idx];
+}
+
+/// 
+/// Agents in the pool may not be in use.  Check #dtCrowdAgent.active before using the returned object.
+dtCrowdAgent* dtCrowd::getEditableAgent(const int idx)
+{
+	if (idx < 0 || idx >= m_maxAgents)
+		return 0;
+	return &m_agents[idx];
+}
+
+void dtCrowd::updateAgentParameters(const int idx, const dtCrowdAgentParams* params)
+{
+	if (idx < 0 || idx >= m_maxAgents)
+		return;
+	memcpy(&m_agents[idx].params, params, sizeof(dtCrowdAgentParams));
+}
+
+/// @par
+///
+/// The agent's position will be constrained to the surface of the navigation mesh.
+int dtCrowd::addAgent(const float* pos, const dtCrowdAgentParams* params)
+{
+	// Find empty slot.
+	int idx = -1;
+	for (int i = 0; i < m_maxAgents; ++i)
+	{
+		if (!m_agents[i].active)
+		{
+			idx = i;
+			break;
+		}
+	}
+	if (idx == -1)
+		return -1;
+	
+	dtCrowdAgent* ag = &m_agents[idx];		
+
+	updateAgentParameters(idx, params);
+	
+	// Find nearest position on navmesh and place the agent there.
+	float nearest[3];
+	dtPolyRef ref = 0;
+	dtVcopy(nearest, pos);
+	dtStatus status = m_navquery->findNearestPoly(pos, m_ext, &m_filters[ag->params.queryFilterType], &ref, nearest);
+	if (dtStatusFailed(status))
+	{
+		dtVcopy(nearest, pos);
+		ref = 0;
+	}
+	
+	ag->corridor.reset(ref, nearest);
+	ag->boundary.reset();
+	ag->partial = false;
+
+	ag->topologyOptTime = 0;
+	ag->targetReplanTime = 0;
+	ag->nneis = 0;
+	
+	dtVset(ag->dvel, 0,0,0);
+	dtVset(ag->nvel, 0,0,0);
+	dtVset(ag->vel, 0,0,0);
+	dtVcopy(ag->npos, nearest);
+	
+	ag->desiredSpeed = 0;
+
+	if (ref)
+		ag->state = DT_CROWDAGENT_STATE_WALKING;
+	else
+		ag->state = DT_CROWDAGENT_STATE_INVALID;
+	
+	ag->targetState = DT_CROWDAGENT_TARGET_NONE;
+	
+	ag->active = true;
+
+	return idx;
+}
+
+/// @par
+///
+/// The agent is deactivated and will no longer be processed.  Its #dtCrowdAgent object
+/// is not removed from the pool.  It is marked as inactive so that it is available for reuse.
+void dtCrowd::removeAgent(const int idx)
+{
+	if (idx >= 0 && idx < m_maxAgents)
+	{
+		m_agents[idx].active = false;
+	}
+}
+
+bool dtCrowd::requestMoveTargetReplan(const int idx, dtPolyRef ref, const float* pos)
+{
+	if (idx < 0 || idx >= m_maxAgents)
+		return false;
+	
+	dtCrowdAgent* ag = &m_agents[idx];
+	
+	// Initialize request.
+	ag->targetRef = ref;
+	dtVcopy(ag->targetPos, pos);
+	ag->targetPathqRef = DT_PATHQ_INVALID;
+	ag->targetReplan = true;
+	if (ag->targetRef)
+		ag->targetState = DT_CROWDAGENT_TARGET_REQUESTING;
+	else
+		ag->targetState = DT_CROWDAGENT_TARGET_FAILED;
+	
+	return true;
+}
+
+/// @par
+/// 
+/// This method is used when a new target is set.
+/// 
+/// The position will be constrained to the surface of the navigation mesh.
+///
+/// The request will be processed during the next #update().
+bool dtCrowd::requestMoveTarget(const int idx, dtPolyRef ref, const float* pos)
+{
+	if (idx < 0 || idx >= m_maxAgents)
+		return false;
+	if (!ref)
+		return false;
+
+	dtCrowdAgent* ag = &m_agents[idx];
+	
+	// Initialize request.
+	ag->targetRef = ref;
+	dtVcopy(ag->targetPos, pos);
+	ag->targetPathqRef = DT_PATHQ_INVALID;
+	ag->targetReplan = false;
+	if (ag->targetRef)
+		ag->targetState = DT_CROWDAGENT_TARGET_REQUESTING;
+	else
+		ag->targetState = DT_CROWDAGENT_TARGET_FAILED;
+
+	return true;
+}
+
+bool dtCrowd::requestMoveVelocity(const int idx, const float* vel)
+{
+	if (idx < 0 || idx >= m_maxAgents)
+		return false;
+	
+	dtCrowdAgent* ag = &m_agents[idx];
+	
+	// Initialize request.
+	ag->targetRef = 0;
+	dtVcopy(ag->targetPos, vel);
+	ag->targetPathqRef = DT_PATHQ_INVALID;
+	ag->targetReplan = false;
+	ag->targetState = DT_CROWDAGENT_TARGET_VELOCITY;
+	
+	return true;
+}
+
+bool dtCrowd::resetMoveTarget(const int idx)
+{
+	if (idx < 0 || idx >= m_maxAgents)
+		return false;
+	
+	dtCrowdAgent* ag = &m_agents[idx];
+	
+	// Initialize request.
+	ag->targetRef = 0;
+	dtVset(ag->targetPos, 0,0,0);
+	ag->targetPathqRef = DT_PATHQ_INVALID;
+	ag->targetReplan = false;
+	ag->targetState = DT_CROWDAGENT_TARGET_NONE;
+	
+	return true;
+}
+
+int dtCrowd::getActiveAgents(dtCrowdAgent** agents, const int maxAgents)
+{
+	int n = 0;
+	for (int i = 0; i < m_maxAgents; ++i)
+	{
+		if (!m_agents[i].active) continue;
+		if (n < maxAgents)
+			agents[n++] = &m_agents[i];
+	}
+	return n;
+}
+
+
+void dtCrowd::updateMoveRequest(const float /*dt*/)
+{
+	const int PATH_MAX_AGENTS = 8;
+	dtCrowdAgent* queue[PATH_MAX_AGENTS];
+	int nqueue = 0;
+	
+	// Fire off new requests.
+	for (int i = 0; i < m_maxAgents; ++i)
+	{
+		dtCrowdAgent* ag = &m_agents[i];
+		if (!ag->active)
+			continue;
+		if (ag->state == DT_CROWDAGENT_STATE_INVALID)
+			continue;
+		if (ag->targetState == DT_CROWDAGENT_TARGET_NONE || ag->targetState == DT_CROWDAGENT_TARGET_VELOCITY)
+			continue;
+
+		if (ag->targetState == DT_CROWDAGENT_TARGET_REQUESTING)
+		{
+			const dtPolyRef* path = ag->corridor.getPath();
+			const int npath = ag->corridor.getPathCount();
+			dtAssert(npath);
+
+			static const int MAX_RES = 32;
+			float reqPos[3];
+			dtPolyRef reqPath[MAX_RES];	// The path to the request location
+			int reqPathCount = 0;
+
+			// Quick search towards the goal.
+			static const int MAX_ITER = 20;
+			m_navquery->initSlicedFindPath(path[0], ag->targetRef, ag->npos, ag->targetPos, &m_filters[ag->params.queryFilterType]);
+			m_navquery->updateSlicedFindPath(MAX_ITER, 0);
+			dtStatus status = 0;
+			if (ag->targetReplan) // && npath > 10)
+			{
+				// Try to use existing steady path during replan if possible.
+				status = m_navquery->finalizeSlicedFindPathPartial(path, npath, reqPath, &reqPathCount, MAX_RES);
+			}
+			else
+			{
+				// Try to move towards target when goal changes.
+				status = m_navquery->finalizeSlicedFindPath(reqPath, &reqPathCount, MAX_RES);
+			}
+
+			if (!dtStatusFailed(status) && reqPathCount > 0)
+			{
+				// In progress or succeed.
+				if (reqPath[reqPathCount-1] != ag->targetRef)
+				{
+					// Partial path, constrain target position inside the last polygon.
+					status = m_navquery->closestPointOnPoly(reqPath[reqPathCount-1], ag->targetPos, reqPos, 0);
+					if (dtStatusFailed(status))
+						reqPathCount = 0;
+				}
+				else
+				{
+					dtVcopy(reqPos, ag->targetPos);
+				}
+			}
+			else
+			{
+				reqPathCount = 0;
+			}
+				
+			if (!reqPathCount)
+			{
+				// Could not find path, start the request from current location.
+				dtVcopy(reqPos, ag->npos);
+				reqPath[0] = path[0];
+				reqPathCount = 1;
+			}
+
+			ag->corridor.setCorridor(reqPos, reqPath, reqPathCount);
+			ag->boundary.reset();
+			ag->partial = false;
+
+			if (reqPath[reqPathCount-1] == ag->targetRef)
+			{
+				ag->targetState = DT_CROWDAGENT_TARGET_VALID;
+				ag->targetReplanTime = 0.0;
+			}
+			else
+			{
+				// The path is longer or potentially unreachable, full plan.
+				ag->targetState = DT_CROWDAGENT_TARGET_WAITING_FOR_QUEUE;
+			}
+		}
+		
+		if (ag->targetState == DT_CROWDAGENT_TARGET_WAITING_FOR_QUEUE)
+		{
+			nqueue = addToPathQueue(ag, queue, nqueue, PATH_MAX_AGENTS);
+		}
+	}
+
+	for (int i = 0; i < nqueue; ++i)
+	{
+		dtCrowdAgent* ag = queue[i];
+		ag->targetPathqRef = m_pathq.request(ag->corridor.getLastPoly(), ag->targetRef,
+											 ag->corridor.getTarget(), ag->targetPos, &m_filters[ag->params.queryFilterType]);
+		if (ag->targetPathqRef != DT_PATHQ_INVALID)
+			ag->targetState = DT_CROWDAGENT_TARGET_WAITING_FOR_PATH;
+	}
+
+	
+	// Update requests.
+	m_pathq.update(MAX_ITERS_PER_UPDATE);
+
+	dtStatus status;
+
+	// Process path results.
+	for (int i = 0; i < m_maxAgents; ++i)
+	{
+		dtCrowdAgent* ag = &m_agents[i];
+		if (!ag->active)
+			continue;
+		if (ag->targetState == DT_CROWDAGENT_TARGET_NONE || ag->targetState == DT_CROWDAGENT_TARGET_VELOCITY)
+			continue;
+		
+		if (ag->targetState == DT_CROWDAGENT_TARGET_WAITING_FOR_PATH)
+		{
+			// Poll path queue.
+			status = m_pathq.getRequestStatus(ag->targetPathqRef);
+			if (dtStatusFailed(status))
+			{
+				// Path find failed, retry if the target location is still valid.
+				ag->targetPathqRef = DT_PATHQ_INVALID;
+				if (ag->targetRef)
+					ag->targetState = DT_CROWDAGENT_TARGET_REQUESTING;
+				else
+					ag->targetState = DT_CROWDAGENT_TARGET_FAILED;
+				ag->targetReplanTime = 0.0;
+			}
+			else if (dtStatusSucceed(status))
+			{
+				const dtPolyRef* path = ag->corridor.getPath();
+				const int npath = ag->corridor.getPathCount();
+				dtAssert(npath);
+				
+				// Apply results.
+				float targetPos[3];
+				dtVcopy(targetPos, ag->targetPos);
+				
+				dtPolyRef* res = m_pathResult;
+				bool valid = true;
+				int nres = 0;
+				status = m_pathq.getPathResult(ag->targetPathqRef, res, &nres, m_maxPathResult);
+				if (dtStatusFailed(status) || !nres)
+					valid = false;
+
+				if (dtStatusDetail(status, DT_PARTIAL_RESULT))
+					ag->partial = true;
+				else
+					ag->partial = false;
+
+				// Merge result and existing path.
+				// The agent might have moved whilst the request is
+				// being processed, so the path may have changed.
+				// We assume that the end of the path is at the same location
+				// where the request was issued.
+				
+				// The last ref in the old path should be the same as
+				// the location where the request was issued..
+				if (valid && path[npath-1] != res[0])
+					valid = false;
+				
+				if (valid)
+				{
+					// Put the old path infront of the old path.
+					if (npath > 1)
+					{
+						// Make space for the old path.
+						if ((npath-1)+nres > m_maxPathResult)
+							nres = m_maxPathResult - (npath-1);
+						
+						memmove(res+npath-1, res, sizeof(dtPolyRef)*nres);
+						// Copy old path in the beginning.
+						memcpy(res, path, sizeof(dtPolyRef)*(npath-1));
+						nres += npath-1;
+						
+						// Remove trackbacks
+						for (int j = 0; j < nres; ++j)
+						{
+							if (j-1 >= 0 && j+1 < nres)
+							{
+								if (res[j-1] == res[j+1])
+								{
+									memmove(res+(j-1), res+(j+1), sizeof(dtPolyRef)*(nres-(j+1)));
+									nres -= 2;
+									j -= 2;
+								}
+							}
+						}
+						
+					}
+					
+					// Check for partial path.
+					if (res[nres-1] != ag->targetRef)
+					{
+						// Partial path, constrain target position inside the last polygon.
+						float nearest[3];
+						status = m_navquery->closestPointOnPoly(res[nres-1], targetPos, nearest, 0);
+						if (dtStatusSucceed(status))
+							dtVcopy(targetPos, nearest);
+						else
+							valid = false;
+					}
+				}
+				
+				if (valid)
+				{
+					// Set current corridor.
+					ag->corridor.setCorridor(targetPos, res, nres);
+					// Force to update boundary.
+					ag->boundary.reset();
+					ag->targetState = DT_CROWDAGENT_TARGET_VALID;
+				}
+				else
+				{
+					// Something went wrong.
+					ag->targetState = DT_CROWDAGENT_TARGET_FAILED;
+				}
+
+				ag->targetReplanTime = 0.0;
+			}
+		}
+	}
+	
+}
+
+
+void dtCrowd::updateTopologyOptimization(dtCrowdAgent** agents, const int nagents, const float dt)
+{
+	if (!nagents)
+		return;
+	
+	const float OPT_TIME_THR = 0.5f; // seconds
+	const int OPT_MAX_AGENTS = 1;
+	dtCrowdAgent* queue[OPT_MAX_AGENTS];
+	int nqueue = 0;
+	
+	for (int i = 0; i < nagents; ++i)
+	{
+		dtCrowdAgent* ag = agents[i];
+		if (ag->state != DT_CROWDAGENT_STATE_WALKING)
+			continue;
+		if (ag->targetState == DT_CROWDAGENT_TARGET_NONE || ag->targetState == DT_CROWDAGENT_TARGET_VELOCITY)
+			continue;
+		if ((ag->params.updateFlags & DT_CROWD_OPTIMIZE_TOPO) == 0)
+			continue;
+		ag->topologyOptTime += dt;
+		if (ag->topologyOptTime >= OPT_TIME_THR)
+			nqueue = addToOptQueue(ag, queue, nqueue, OPT_MAX_AGENTS);
+	}
+
+	for (int i = 0; i < nqueue; ++i)
+	{
+		dtCrowdAgent* ag = queue[i];
+		ag->corridor.optimizePathTopology(m_navquery, &m_filters[ag->params.queryFilterType]);
+		ag->topologyOptTime = 0;
+	}
+
+}
+
+void dtCrowd::checkPathValidity(dtCrowdAgent** agents, const int nagents, const float dt)
+{
+	static const int CHECK_LOOKAHEAD = 10;
+	static const float TARGET_REPLAN_DELAY = 1.0; // seconds
+	
+	for (int i = 0; i < nagents; ++i)
+	{
+		dtCrowdAgent* ag = agents[i];
+		
+		if (ag->state != DT_CROWDAGENT_STATE_WALKING)
+			continue;
+			
+		ag->targetReplanTime += dt;
+
+		bool replan = false;
+
+		// First check that the current location is valid.
+		const int idx = getAgentIndex(ag);
+		float agentPos[3];
+		dtPolyRef agentRef = ag->corridor.getFirstPoly();
+		dtVcopy(agentPos, ag->npos);
+		if (!m_navquery->isValidPolyRef(agentRef, &m_filters[ag->params.queryFilterType]))
+		{
+			// Current location is not valid, try to reposition.
+			// TODO: this can snap agents, how to handle that?
+			float nearest[3];
+			dtVcopy(nearest, agentPos);
+			agentRef = 0;
+			m_navquery->findNearestPoly(ag->npos, m_ext, &m_filters[ag->params.queryFilterType], &agentRef, nearest);
+			dtVcopy(agentPos, nearest);
+
+			if (!agentRef)
+			{
+				// Could not find location in navmesh, set state to invalid.
+				ag->corridor.reset(0, agentPos);
+				ag->partial = false;
+				ag->boundary.reset();
+				ag->state = DT_CROWDAGENT_STATE_INVALID;
+				continue;
+			}
+
+			// Make sure the first polygon is valid, but leave other valid
+			// polygons in the path so that replanner can adjust the path better.
+			ag->corridor.fixPathStart(agentRef, agentPos);
+//			ag->corridor.trimInvalidPath(agentRef, agentPos, m_navquery, &m_filter);
+			ag->boundary.reset();
+			dtVcopy(ag->npos, agentPos);
+
+			replan = true;
+		}
+
+		// If the agent does not have move target or is controlled by velocity, no need to recover the target nor replan.
+		if (ag->targetState == DT_CROWDAGENT_TARGET_NONE || ag->targetState == DT_CROWDAGENT_TARGET_VELOCITY)
+			continue;
+
+		// Try to recover move request position.
+		if (ag->targetState != DT_CROWDAGENT_TARGET_NONE && ag->targetState != DT_CROWDAGENT_TARGET_FAILED)
+		{
+			if (!m_navquery->isValidPolyRef(ag->targetRef, &m_filters[ag->params.queryFilterType]))
+			{
+				// Current target is not valid, try to reposition.
+				float nearest[3];
+				dtVcopy(nearest, ag->targetPos);
+				ag->targetRef = 0;
+				m_navquery->findNearestPoly(ag->targetPos, m_ext, &m_filters[ag->params.queryFilterType], &ag->targetRef, nearest);
+				dtVcopy(ag->targetPos, nearest);
+				replan = true;
+			}
+			if (!ag->targetRef)
+			{
+				// Failed to reposition target, fail moverequest.
+				ag->corridor.reset(agentRef, agentPos);
+				ag->partial = false;
+				ag->targetState = DT_CROWDAGENT_TARGET_NONE;
+			}
+		}
+
+		// If nearby corridor is not valid, replan.
+		if (!ag->corridor.isValid(CHECK_LOOKAHEAD, m_navquery, &m_filters[ag->params.queryFilterType]))
+		{
+			// Fix current path.
+//			ag->corridor.trimInvalidPath(agentRef, agentPos, m_navquery, &m_filter);
+//			ag->boundary.reset();
+			replan = true;
+		}
+		
+		// If the end of the path is near and it is not the requested location, replan.
+		if (ag->targetState == DT_CROWDAGENT_TARGET_VALID)
+		{
+			if (ag->targetReplanTime > TARGET_REPLAN_DELAY &&
+				ag->corridor.getPathCount() < CHECK_LOOKAHEAD &&
+				ag->corridor.getLastPoly() != ag->targetRef)
+				replan = true;
+		}
+
+		// Try to replan path to goal.
+		if (replan)
+		{
+			if (ag->targetState != DT_CROWDAGENT_TARGET_NONE)
+			{
+				requestMoveTargetReplan(idx, ag->targetRef, ag->targetPos);
+			}
+		}
+	}
+}
+	
+void dtCrowd::update(const float dt, dtCrowdAgentDebugInfo* debug)
+{
+	m_velocitySampleCount = 0;
+	
+	const int debugIdx = debug ? debug->idx : -1;
+	
+	dtCrowdAgent** agents = m_activeAgents;
+	int nagents = getActiveAgents(agents, m_maxAgents);
+
+	// Check that all agents still have valid paths.
+	checkPathValidity(agents, nagents, dt);
+	
+	// Update async move request and path finder.
+	updateMoveRequest(dt);
+
+	// Optimize path topology.
+	updateTopologyOptimization(agents, nagents, dt);
+	
+	// Register agents to proximity grid.
+	m_grid->clear();
+	for (int i = 0; i < nagents; ++i)
+	{
+		dtCrowdAgent* ag = agents[i];
+		const float* p = ag->npos;
+		const float r = ag->params.radius;
+		m_grid->addItem((unsigned short)i, p[0]-r, p[2]-r, p[0]+r, p[2]+r);
+	}
+	
+	// Get nearby navmesh segments and agents to collide with.
+	for (int i = 0; i < nagents; ++i)
+	{
+		dtCrowdAgent* ag = agents[i];
+		if (ag->state != DT_CROWDAGENT_STATE_WALKING)
+			continue;
+
+		// Update the collision boundary after certain distance has been passed or
+		// if it has become invalid.
+		const float updateThr = ag->params.collisionQueryRange*0.25f;
+		if (dtVdist2DSqr(ag->npos, ag->boundary.getCenter()) > dtSqr(updateThr) ||
+			!ag->boundary.isValid(m_navquery, &m_filters[ag->params.queryFilterType]))
+		{
+			ag->boundary.update(ag->corridor.getFirstPoly(), ag->npos, ag->params.collisionQueryRange,
+								m_navquery, &m_filters[ag->params.queryFilterType]);
+		}
+		// Query neighbour agents
+		ag->nneis = getNeighbours(ag->npos, ag->params.height, ag->params.collisionQueryRange,
+								  ag, ag->neis, DT_CROWDAGENT_MAX_NEIGHBOURS,
+								  agents, nagents, m_grid);
+		for (int j = 0; j < ag->nneis; j++)
+			ag->neis[j].idx = getAgentIndex(agents[ag->neis[j].idx]);
+	}
+	
+	// Find next corner to steer to.
+	for (int i = 0; i < nagents; ++i)
+	{
+		dtCrowdAgent* ag = agents[i];
+		
+		if (ag->state != DT_CROWDAGENT_STATE_WALKING)
+			continue;
+		if (ag->targetState == DT_CROWDAGENT_TARGET_NONE || ag->targetState == DT_CROWDAGENT_TARGET_VELOCITY)
+			continue;
+		
+		// Find corners for steering
+		ag->ncorners = ag->corridor.findCorners(ag->cornerVerts, ag->cornerFlags, ag->cornerPolys,
+												DT_CROWDAGENT_MAX_CORNERS, m_navquery, &m_filters[ag->params.queryFilterType]);
+		
+		// Check to see if the corner after the next corner is directly visible,
+		// and short cut to there.
+		if ((ag->params.updateFlags & DT_CROWD_OPTIMIZE_VIS) && ag->ncorners > 0)
+		{
+			const float* target = &ag->cornerVerts[dtMin(1,ag->ncorners-1)*3];
+			ag->corridor.optimizePathVisibility(target, ag->params.pathOptimizationRange, m_navquery, &m_filters[ag->params.queryFilterType]);
+			
+			// Copy data for debug purposes.
+			if (debugIdx == i)
+			{
+				dtVcopy(debug->optStart, ag->corridor.getPos());
+				dtVcopy(debug->optEnd, target);
+			}
+		}
+		else
+		{
+			// Copy data for debug purposes.
+			if (debugIdx == i)
+			{
+				dtVset(debug->optStart, 0,0,0);
+				dtVset(debug->optEnd, 0,0,0);
+			}
+		}
+	}
+	
+	// Trigger off-mesh connections (depends on corners).
+	for (int i = 0; i < nagents; ++i)
+	{
+		dtCrowdAgent* ag = agents[i];
+		
+		if (ag->state != DT_CROWDAGENT_STATE_WALKING)
+			continue;
+		if (ag->targetState == DT_CROWDAGENT_TARGET_NONE || ag->targetState == DT_CROWDAGENT_TARGET_VELOCITY)
+			continue;
+		
+		// Check 
+		const float triggerRadius = ag->params.radius*2.25f;
+		if (overOffmeshConnection(ag, triggerRadius))
+		{
+			// Prepare to off-mesh connection.
+			const int idx = (int)(ag - m_agents);
+			dtCrowdAgentAnimation* anim = &m_agentAnims[idx];
+			
+			// Adjust the path over the off-mesh connection.
+			dtPolyRef refs[2];
+			if (ag->corridor.moveOverOffmeshConnection(ag->cornerPolys[ag->ncorners-1], refs,
+													   anim->startPos, anim->endPos, m_navquery))
+			{
+				dtVcopy(anim->initPos, ag->npos);
+				anim->polyRef = refs[1];
+				anim->active = true;
+				anim->t = 0.0f;
+				anim->tmax = (dtVdist2D(anim->startPos, anim->endPos) / ag->params.maxSpeed) * 0.5f;
+				
+				ag->state = DT_CROWDAGENT_STATE_OFFMESH;
+				ag->ncorners = 0;
+				ag->nneis = 0;
+				continue;
+			}
+			else
+			{
+				// Path validity check will ensure that bad/blocked connections will be replanned.
+			}
+		}
+	}
+		
+	// Calculate steering.
+	for (int i = 0; i < nagents; ++i)
+	{
+		dtCrowdAgent* ag = agents[i];
+
+		if (ag->state != DT_CROWDAGENT_STATE_WALKING)
+			continue;
+		if (ag->targetState == DT_CROWDAGENT_TARGET_NONE)
+			continue;
+		
+		float dvel[3] = {0,0,0};
+
+		if (ag->targetState == DT_CROWDAGENT_TARGET_VELOCITY)
+		{
+			dtVcopy(dvel, ag->targetPos);
+			ag->desiredSpeed = dtVlen(ag->targetPos);
+		}
+		else
+		{
+			// Calculate steering direction.
+			if (ag->params.updateFlags & DT_CROWD_ANTICIPATE_TURNS)
+				calcSmoothSteerDirection(ag, dvel);
+			else
+				calcStraightSteerDirection(ag, dvel);
+			
+			// Calculate speed scale, which tells the agent to slowdown at the end of the path.
+			const float slowDownRadius = ag->params.radius*2;	// TODO: make less hacky.
+			const float speedScale = getDistanceToGoal(ag, slowDownRadius) / slowDownRadius;
+				
+			ag->desiredSpeed = ag->params.maxSpeed;
+			dtVscale(dvel, dvel, ag->desiredSpeed * speedScale);
+		}
+
+		// Separation
+		if (ag->params.updateFlags & DT_CROWD_SEPARATION)
+		{
+			const float separationDist = ag->params.collisionQueryRange; 
+			const float invSeparationDist = 1.0f / separationDist; 
+			const float separationWeight = ag->params.separationWeight;
+			
+			float w = 0;
+			float disp[3] = {0,0,0};
+			
+			for (int j = 0; j < ag->nneis; ++j)
+			{
+				const dtCrowdAgent* nei = &m_agents[ag->neis[j].idx];
+				
+				float diff[3];
+				dtVsub(diff, ag->npos, nei->npos);
+				diff[1] = 0;
+				
+				const float distSqr = dtVlenSqr(diff);
+				if (distSqr < 0.00001f)
+					continue;
+				if (distSqr > dtSqr(separationDist))
+					continue;
+				const float dist = dtMathSqrtf(distSqr);
+				const float weight = separationWeight * (1.0f - dtSqr(dist*invSeparationDist));
+				
+				dtVmad(disp, disp, diff, weight/dist);
+				w += 1.0f;
+			}
+			
+			if (w > 0.0001f)
+			{
+				// Adjust desired velocity.
+				dtVmad(dvel, dvel, disp, 1.0f/w);
+				// Clamp desired velocity to desired speed.
+				const float speedSqr = dtVlenSqr(dvel);
+				const float desiredSqr = dtSqr(ag->desiredSpeed);
+				if (speedSqr > desiredSqr)
+					dtVscale(dvel, dvel, desiredSqr/speedSqr);
+			}
+		}
+		
+		// Set the desired velocity.
+		dtVcopy(ag->dvel, dvel);
+	}
+	
+	// Velocity planning.	
+	for (int i = 0; i < nagents; ++i)
+	{
+		dtCrowdAgent* ag = agents[i];
+		
+		if (ag->state != DT_CROWDAGENT_STATE_WALKING)
+			continue;
+		
+		if (ag->params.updateFlags & DT_CROWD_OBSTACLE_AVOIDANCE)
+		{
+			m_obstacleQuery->reset();
+			
+			// Add neighbours as obstacles.
+			for (int j = 0; j < ag->nneis; ++j)
+			{
+				const dtCrowdAgent* nei = &m_agents[ag->neis[j].idx];
+				m_obstacleQuery->addCircle(nei->npos, nei->params.radius, nei->vel, nei->dvel);
+			}
+
+			// Append neighbour segments as obstacles.
+			for (int j = 0; j < ag->boundary.getSegmentCount(); ++j)
+			{
+				const float* s = ag->boundary.getSegment(j);
+				if (dtTriArea2D(ag->npos, s, s+3) < 0.0f)
+					continue;
+				m_obstacleQuery->addSegment(s, s+3);
+			}
+
+			dtObstacleAvoidanceDebugData* vod = 0;
+			if (debugIdx == i) 
+				vod = debug->vod;
+			
+			// Sample new safe velocity.
+			bool adaptive = true;
+			int ns = 0;
+
+			const dtObstacleAvoidanceParams* params = &m_obstacleQueryParams[ag->params.obstacleAvoidanceType];
+				
+			if (adaptive)
+			{
+				ns = m_obstacleQuery->sampleVelocityAdaptive(ag->npos, ag->params.radius, ag->desiredSpeed,
+															 ag->vel, ag->dvel, ag->nvel, params, vod);
+			}
+			else
+			{
+				ns = m_obstacleQuery->sampleVelocityGrid(ag->npos, ag->params.radius, ag->desiredSpeed,
+														 ag->vel, ag->dvel, ag->nvel, params, vod);
+			}
+			m_velocitySampleCount += ns;
+		}
+		else
+		{
+			// If not using velocity planning, new velocity is directly the desired velocity.
+			dtVcopy(ag->nvel, ag->dvel);
+		}
+	}
+
+	// Integrate.
+	for (int i = 0; i < nagents; ++i)
+	{
+		dtCrowdAgent* ag = agents[i];
+		if (ag->state != DT_CROWDAGENT_STATE_WALKING)
+			continue;
+		integrate(ag, dt);
+	}
+	
+	// Handle collisions.
+	static const float COLLISION_RESOLVE_FACTOR = 0.7f;
+	
+	for (int iter = 0; iter < 4; ++iter)
+	{
+		for (int i = 0; i < nagents; ++i)
+		{
+			dtCrowdAgent* ag = agents[i];
+			const int idx0 = getAgentIndex(ag);
+			
+			if (ag->state != DT_CROWDAGENT_STATE_WALKING)
+				continue;
+
+			dtVset(ag->disp, 0,0,0);
+			
+			float w = 0;
+
+			for (int j = 0; j < ag->nneis; ++j)
+			{
+				const dtCrowdAgent* nei = &m_agents[ag->neis[j].idx];
+				const int idx1 = getAgentIndex(nei);
+
+				float diff[3];
+				dtVsub(diff, ag->npos, nei->npos);
+				diff[1] = 0;
+				
+				float dist = dtVlenSqr(diff);
+				if (dist > dtSqr(ag->params.radius + nei->params.radius))
+					continue;
+				dist = dtMathSqrtf(dist);
+				float pen = (ag->params.radius + nei->params.radius) - dist;
+				if (dist < 0.0001f)
+				{
+					// Agents on top of each other, try to choose diverging separation directions.
+					if (idx0 > idx1)
+						dtVset(diff, -ag->dvel[2],0,ag->dvel[0]);
+					else
+						dtVset(diff, ag->dvel[2],0,-ag->dvel[0]);
+					pen = 0.01f;
+				}
+				else
+				{
+					pen = (1.0f/dist) * (pen*0.5f) * COLLISION_RESOLVE_FACTOR;
+				}
+				
+				dtVmad(ag->disp, ag->disp, diff, pen);			
+				
+				w += 1.0f;
+			}
+			
+			if (w > 0.0001f)
+			{
+				const float iw = 1.0f / w;
+				dtVscale(ag->disp, ag->disp, iw);
+			}
+		}
+		
+		for (int i = 0; i < nagents; ++i)
+		{
+			dtCrowdAgent* ag = agents[i];
+			if (ag->state != DT_CROWDAGENT_STATE_WALKING)
+				continue;
+			
+			dtVadd(ag->npos, ag->npos, ag->disp);
+		}
+	}
+	
+	for (int i = 0; i < nagents; ++i)
+	{
+		dtCrowdAgent* ag = agents[i];
+		if (ag->state != DT_CROWDAGENT_STATE_WALKING)
+			continue;
+		
+		// Move along navmesh.
+		ag->corridor.movePosition(ag->npos, m_navquery, &m_filters[ag->params.queryFilterType]);
+		// Get valid constrained position back.
+		dtVcopy(ag->npos, ag->corridor.getPos());
+
+		// If not using path, truncate the corridor to just one poly.
+		if (ag->targetState == DT_CROWDAGENT_TARGET_NONE || ag->targetState == DT_CROWDAGENT_TARGET_VELOCITY)
+		{
+			ag->corridor.reset(ag->corridor.getFirstPoly(), ag->npos);
+			ag->partial = false;
+		}
+
+	}
+	
+	// Update agents using off-mesh connection.
+	for (int i = 0; i < m_maxAgents; ++i)
+	{
+		dtCrowdAgentAnimation* anim = &m_agentAnims[i];
+		if (!anim->active)
+			continue;
+		dtCrowdAgent* ag = agents[i];
+
+		anim->t += dt;
+		if (anim->t > anim->tmax)
+		{
+			// Reset animation
+			anim->active = false;
+			// Prepare agent for walking.
+			ag->state = DT_CROWDAGENT_STATE_WALKING;
+			continue;
+		}
+		
+		// Update position
+		const float ta = anim->tmax*0.15f;
+		const float tb = anim->tmax;
+		if (anim->t < ta)
+		{
+			const float u = tween(anim->t, 0.0, ta);
+			dtVlerp(ag->npos, anim->initPos, anim->startPos, u);
+		}
+		else
+		{
+			const float u = tween(anim->t, ta, tb);
+			dtVlerp(ag->npos, anim->startPos, anim->endPos, u);
+		}
+			
+		// Update velocity.
+		dtVset(ag->vel, 0,0,0);
+		dtVset(ag->dvel, 0,0,0);
+	}
+	
+}

+ 137 - 0
Source/ThirdParty/DetourCrowd/source/DetourLocalBoundary.cpp

@@ -0,0 +1,137 @@
+//
+// Copyright (c) 2009-2010 Mikko Mononen [email protected]
+//
+// 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 <float.h>
+#include <string.h>
+#include "DetourLocalBoundary.h"
+#include "DetourNavMeshQuery.h"
+#include "DetourCommon.h"
+#include "DetourAssert.h"
+
+
+dtLocalBoundary::dtLocalBoundary() :
+	m_nsegs(0),
+	m_npolys(0)
+{
+	dtVset(m_center, FLT_MAX,FLT_MAX,FLT_MAX);
+}
+
+dtLocalBoundary::~dtLocalBoundary()
+{
+}
+
+void dtLocalBoundary::reset()
+{
+	dtVset(m_center, FLT_MAX,FLT_MAX,FLT_MAX);
+	m_npolys = 0;
+	m_nsegs = 0;
+}
+
+void dtLocalBoundary::addSegment(const float dist, const float* s)
+{
+	// Insert neighbour based on the distance.
+	Segment* seg = 0;
+	if (!m_nsegs)
+	{
+		// First, trivial accept.
+		seg = &m_segs[0];
+	}
+	else if (dist >= m_segs[m_nsegs-1].d)
+	{
+		// Further than the last segment, skip.
+		if (m_nsegs >= MAX_LOCAL_SEGS)
+			return;
+		// Last, trivial accept.
+		seg = &m_segs[m_nsegs];
+	}
+	else
+	{
+		// Insert inbetween.
+		int i;
+		for (i = 0; i < m_nsegs; ++i)
+			if (dist <= m_segs[i].d)
+				break;
+		const int tgt = i+1;
+		const int n = dtMin(m_nsegs-i, MAX_LOCAL_SEGS-tgt);
+		dtAssert(tgt+n <= MAX_LOCAL_SEGS);
+		if (n > 0)
+			memmove(&m_segs[tgt], &m_segs[i], sizeof(Segment)*n);
+		seg = &m_segs[i];
+	}
+	
+	seg->d = dist;
+	memcpy(seg->s, s, sizeof(float)*6);
+	
+	if (m_nsegs < MAX_LOCAL_SEGS)
+		m_nsegs++;
+}
+
+void dtLocalBoundary::update(dtPolyRef ref, const float* pos, const float collisionQueryRange,
+							 dtNavMeshQuery* navquery, const dtQueryFilter* filter)
+{
+	static const int MAX_SEGS_PER_POLY = DT_VERTS_PER_POLYGON*3;
+	
+	if (!ref)
+	{
+		dtVset(m_center, FLT_MAX,FLT_MAX,FLT_MAX);
+		m_nsegs = 0;
+		m_npolys = 0;
+		return;
+	}
+	
+	dtVcopy(m_center, pos);
+	
+	// First query non-overlapping polygons.
+	navquery->findLocalNeighbourhood(ref, pos, collisionQueryRange,
+									 filter, m_polys, 0, &m_npolys, MAX_LOCAL_POLYS);
+	
+	// Secondly, store all polygon edges.
+	m_nsegs = 0;
+	float segs[MAX_SEGS_PER_POLY*6];
+	int nsegs = 0;
+	for (int j = 0; j < m_npolys; ++j)
+	{
+		navquery->getPolyWallSegments(m_polys[j], filter, segs, 0, &nsegs, MAX_SEGS_PER_POLY);
+		for (int k = 0; k < nsegs; ++k)
+		{
+			const float* s = &segs[k*6];
+			// Skip too distant segments.
+			float tseg;
+			const float distSqr = dtDistancePtSegSqr2D(pos, s, s+3, tseg);
+			if (distSqr > dtSqr(collisionQueryRange))
+				continue;
+			addSegment(distSqr, s);
+		}
+	}
+}
+
+bool dtLocalBoundary::isValid(dtNavMeshQuery* navquery, const dtQueryFilter* filter)
+{
+	if (!m_npolys)
+		return false;
+	
+	// Check that all polygons still pass query filter.
+	for (int i = 0; i < m_npolys; ++i)
+	{
+		if (!navquery->isValidPolyRef(m_polys[i], filter))
+			return false;
+	}
+	
+	return true;
+}
+

+ 544 - 0
Source/ThirdParty/DetourCrowd/source/DetourObstacleAvoidance.cpp

@@ -0,0 +1,544 @@
+//
+// Copyright (c) 2009-2010 Mikko Mononen [email protected]
+//
+// 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 "DetourObstacleAvoidance.h"
+#include "DetourCommon.h"
+#include "DetourMath.h"
+#include "DetourAlloc.h"
+#include "DetourAssert.h"
+#include <string.h>
+#include <float.h>
+#include <new>
+
+static const float DT_PI = 3.14159265f;
+
+static int sweepCircleCircle(const float* c0, const float r0, const float* v,
+							 const float* c1, const float r1,
+							 float& tmin, float& tmax)
+{
+	static const float EPS = 0.0001f;
+	float s[3];
+	dtVsub(s,c1,c0);
+	float r = r0+r1;
+	float c = dtVdot2D(s,s) - r*r;
+	float a = dtVdot2D(v,v);
+	if (a < EPS) return 0;	// not moving
+	
+	// Overlap, calc time to exit.
+	float b = dtVdot2D(v,s);
+	float d = b*b - a*c;
+	if (d < 0.0f) return 0; // no intersection.
+	a = 1.0f / a;
+	const float rd = dtSqrt(d);
+	tmin = (b - rd) * a;
+	tmax = (b + rd) * a;
+	return 1;
+}
+
+static int isectRaySeg(const float* ap, const float* u,
+					   const float* bp, const float* bq,
+					   float& t)
+{
+	float v[3], w[3];
+	dtVsub(v,bq,bp);
+	dtVsub(w,ap,bp);
+	float d = dtVperp2D(u,v);
+	if (dtMathFabs(d) < 1e-6f) return 0;
+	d = 1.0f/d;
+	t = dtVperp2D(v,w) * d;
+	if (t < 0 || t > 1) return 0;
+	float s = dtVperp2D(u,w) * d;
+	if (s < 0 || s > 1) return 0;
+	return 1;
+}
+
+
+
+dtObstacleAvoidanceDebugData* dtAllocObstacleAvoidanceDebugData()
+{
+	void* mem = dtAlloc(sizeof(dtObstacleAvoidanceDebugData), DT_ALLOC_PERM);
+	if (!mem) return 0;
+	return new(mem) dtObstacleAvoidanceDebugData;
+}
+
+void dtFreeObstacleAvoidanceDebugData(dtObstacleAvoidanceDebugData* ptr)
+{
+	if (!ptr) return;
+	ptr->~dtObstacleAvoidanceDebugData();
+	dtFree(ptr);
+}
+
+
+dtObstacleAvoidanceDebugData::dtObstacleAvoidanceDebugData() :
+	m_nsamples(0),
+	m_maxSamples(0),
+	m_vel(0),
+	m_ssize(0),
+	m_pen(0),
+	m_vpen(0),
+	m_vcpen(0),
+	m_spen(0),
+	m_tpen(0)
+{
+}
+
+dtObstacleAvoidanceDebugData::~dtObstacleAvoidanceDebugData()
+{
+	dtFree(m_vel);
+	dtFree(m_ssize);
+	dtFree(m_pen);
+	dtFree(m_vpen);
+	dtFree(m_vcpen);
+	dtFree(m_spen);
+	dtFree(m_tpen);
+}
+		
+bool dtObstacleAvoidanceDebugData::init(const int maxSamples)
+{
+	dtAssert(maxSamples);
+	m_maxSamples = maxSamples;
+
+	m_vel = (float*)dtAlloc(sizeof(float)*3*m_maxSamples, DT_ALLOC_PERM);
+	if (!m_vel)
+		return false;
+	m_pen = (float*)dtAlloc(sizeof(float)*m_maxSamples, DT_ALLOC_PERM);
+	if (!m_pen)
+		return false;
+	m_ssize = (float*)dtAlloc(sizeof(float)*m_maxSamples, DT_ALLOC_PERM);
+	if (!m_ssize)
+		return false;
+	m_vpen = (float*)dtAlloc(sizeof(float)*m_maxSamples, DT_ALLOC_PERM);
+	if (!m_vpen)
+		return false;
+	m_vcpen = (float*)dtAlloc(sizeof(float)*m_maxSamples, DT_ALLOC_PERM);
+	if (!m_vcpen)
+		return false;
+	m_spen = (float*)dtAlloc(sizeof(float)*m_maxSamples, DT_ALLOC_PERM);
+	if (!m_spen)
+		return false;
+	m_tpen = (float*)dtAlloc(sizeof(float)*m_maxSamples, DT_ALLOC_PERM);
+	if (!m_tpen)
+		return false;
+	
+	return true;
+}
+
+void dtObstacleAvoidanceDebugData::reset()
+{
+	m_nsamples = 0;
+}
+
+void dtObstacleAvoidanceDebugData::addSample(const float* vel, const float ssize, const float pen,
+											 const float vpen, const float vcpen, const float spen, const float tpen)
+{
+	if (m_nsamples >= m_maxSamples)
+		return;
+	dtAssert(m_vel);
+	dtAssert(m_ssize);
+	dtAssert(m_pen);
+	dtAssert(m_vpen);
+	dtAssert(m_vcpen);
+	dtAssert(m_spen);
+	dtAssert(m_tpen);
+	dtVcopy(&m_vel[m_nsamples*3], vel);
+	m_ssize[m_nsamples] = ssize;
+	m_pen[m_nsamples] = pen;
+	m_vpen[m_nsamples] = vpen;
+	m_vcpen[m_nsamples] = vcpen;
+	m_spen[m_nsamples] = spen;
+	m_tpen[m_nsamples] = tpen;
+	m_nsamples++;
+}
+
+static void normalizeArray(float* arr, const int n)
+{
+	// Normalize penaly range.
+	float minPen = FLT_MAX;
+	float maxPen = -FLT_MAX;
+	for (int i = 0; i < n; ++i)
+	{
+		minPen = dtMin(minPen, arr[i]);
+		maxPen = dtMax(maxPen, arr[i]);
+	}
+	const float penRange = maxPen-minPen;
+	const float s = penRange > 0.001f ? (1.0f / penRange) : 1;
+	for (int i = 0; i < n; ++i)
+		arr[i] = dtClamp((arr[i]-minPen)*s, 0.0f, 1.0f);
+}
+
+void dtObstacleAvoidanceDebugData::normalizeSamples()
+{
+	normalizeArray(m_pen, m_nsamples);
+	normalizeArray(m_vpen, m_nsamples);
+	normalizeArray(m_vcpen, m_nsamples);
+	normalizeArray(m_spen, m_nsamples);
+	normalizeArray(m_tpen, m_nsamples);
+}
+
+
+dtObstacleAvoidanceQuery* dtAllocObstacleAvoidanceQuery()
+{
+	void* mem = dtAlloc(sizeof(dtObstacleAvoidanceQuery), DT_ALLOC_PERM);
+	if (!mem) return 0;
+	return new(mem) dtObstacleAvoidanceQuery;
+}
+
+void dtFreeObstacleAvoidanceQuery(dtObstacleAvoidanceQuery* ptr)
+{
+	if (!ptr) return;
+	ptr->~dtObstacleAvoidanceQuery();
+	dtFree(ptr);
+}
+
+
+dtObstacleAvoidanceQuery::dtObstacleAvoidanceQuery() :
+	m_maxCircles(0),
+	m_circles(0),
+	m_ncircles(0),
+	m_maxSegments(0),
+	m_segments(0),
+	m_nsegments(0)
+{
+}
+
+dtObstacleAvoidanceQuery::~dtObstacleAvoidanceQuery()
+{
+	dtFree(m_circles);
+	dtFree(m_segments);
+}
+
+bool dtObstacleAvoidanceQuery::init(const int maxCircles, const int maxSegments)
+{
+	m_maxCircles = maxCircles;
+	m_ncircles = 0;
+	m_circles = (dtObstacleCircle*)dtAlloc(sizeof(dtObstacleCircle)*m_maxCircles, DT_ALLOC_PERM);
+	if (!m_circles)
+		return false;
+	memset(m_circles, 0, sizeof(dtObstacleCircle)*m_maxCircles);
+
+	m_maxSegments = maxSegments;
+	m_nsegments = 0;
+	m_segments = (dtObstacleSegment*)dtAlloc(sizeof(dtObstacleSegment)*m_maxSegments, DT_ALLOC_PERM);
+	if (!m_segments)
+		return false;
+	memset(m_segments, 0, sizeof(dtObstacleSegment)*m_maxSegments);
+	
+	return true;
+}
+
+void dtObstacleAvoidanceQuery::reset()
+{
+	m_ncircles = 0;
+	m_nsegments = 0;
+}
+
+void dtObstacleAvoidanceQuery::addCircle(const float* pos, const float rad,
+										 const float* vel, const float* dvel)
+{
+	if (m_ncircles >= m_maxCircles)
+		return;
+		
+	dtObstacleCircle* cir = &m_circles[m_ncircles++];
+	dtVcopy(cir->p, pos);
+	cir->rad = rad;
+	dtVcopy(cir->vel, vel);
+	dtVcopy(cir->dvel, dvel);
+}
+
+void dtObstacleAvoidanceQuery::addSegment(const float* p, const float* q)
+{
+	if (m_nsegments > m_maxSegments)
+		return;
+	
+	dtObstacleSegment* seg = &m_segments[m_nsegments++];
+	dtVcopy(seg->p, p);
+	dtVcopy(seg->q, q);
+}
+
+void dtObstacleAvoidanceQuery::prepare(const float* pos, const float* dvel)
+{
+	// Prepare obstacles
+	for (int i = 0; i < m_ncircles; ++i)
+	{
+		dtObstacleCircle* cir = &m_circles[i];
+		
+		// Side
+		const float* pa = pos;
+		const float* pb = cir->p;
+		
+		const float orig[3] = {0,0};
+		float dv[3];
+		dtVsub(cir->dp,pb,pa);
+		dtVnormalize(cir->dp);
+		dtVsub(dv, cir->dvel, dvel);
+		
+		const float a = dtTriArea2D(orig, cir->dp,dv);
+		if (a < 0.01f)
+		{
+			cir->np[0] = -cir->dp[2];
+			cir->np[2] = cir->dp[0];
+		}
+		else
+		{
+			cir->np[0] = cir->dp[2];
+			cir->np[2] = -cir->dp[0];
+		}
+	}	
+
+	for (int i = 0; i < m_nsegments; ++i)
+	{
+		dtObstacleSegment* seg = &m_segments[i];
+		
+		// Precalc if the agent is really close to the segment.
+		const float r = 0.01f;
+		float t;
+		seg->touch = dtDistancePtSegSqr2D(pos, seg->p, seg->q, t) < dtSqr(r);
+	}	
+}
+
+float dtObstacleAvoidanceQuery::processSample(const float* vcand, const float cs,
+											  const float* pos, const float rad,
+											  const float* vel, const float* dvel,
+											  dtObstacleAvoidanceDebugData* debug)
+{
+	// Find min time of impact and exit amongst all obstacles.
+	float tmin = m_params.horizTime;
+	float side = 0;
+	int nside = 0;
+	
+	for (int i = 0; i < m_ncircles; ++i)
+	{
+		const dtObstacleCircle* cir = &m_circles[i];
+			
+		// RVO
+		float vab[3];
+		dtVscale(vab, vcand, 2);
+		dtVsub(vab, vab, vel);
+		dtVsub(vab, vab, cir->vel);
+		
+		// Side
+		side += dtClamp(dtMin(dtVdot2D(cir->dp,vab)*0.5f+0.5f, dtVdot2D(cir->np,vab)*2), 0.0f, 1.0f);
+		nside++;
+		
+		float htmin = 0, htmax = 0;
+		if (!sweepCircleCircle(pos,rad, vab, cir->p,cir->rad, htmin, htmax))
+			continue;
+		
+		// Handle overlapping obstacles.
+		if (htmin < 0.0f && htmax > 0.0f)
+		{
+			// Avoid more when overlapped.
+			htmin = -htmin * 0.5f;
+		}
+		
+		if (htmin >= 0.0f)
+		{
+			// The closest obstacle is somewhere ahead of us, keep track of nearest obstacle.
+			if (htmin < tmin)
+				tmin = htmin;
+		}
+	}
+
+	for (int i = 0; i < m_nsegments; ++i)
+	{
+		const dtObstacleSegment* seg = &m_segments[i];
+		float htmin = 0;
+		
+		if (seg->touch)
+		{
+			// Special case when the agent is very close to the segment.
+			float sdir[3], snorm[3];
+			dtVsub(sdir, seg->q, seg->p);
+			snorm[0] = -sdir[2];
+			snorm[2] = sdir[0];
+			// If the velocity is pointing towards the segment, no collision.
+			if (dtVdot2D(snorm, vcand) < 0.0f)
+				continue;
+			// Else immediate collision.
+			htmin = 0.0f;
+		}
+		else
+		{
+			if (!isectRaySeg(pos, vcand, seg->p, seg->q, htmin))
+				continue;
+		}
+		
+		// Avoid less when facing walls.
+		htmin *= 2.0f;
+		
+		// The closest obstacle is somewhere ahead of us, keep track of nearest obstacle.
+		if (htmin < tmin)
+			tmin = htmin;
+	}
+	
+	// Normalize side bias, to prevent it dominating too much.
+	if (nside)
+		side /= nside;
+	
+	const float vpen = m_params.weightDesVel * (dtVdist2D(vcand, dvel) * m_invVmax);
+	const float vcpen = m_params.weightCurVel * (dtVdist2D(vcand, vel) * m_invVmax);
+	const float spen = m_params.weightSide * side;
+	const float tpen = m_params.weightToi * (1.0f/(0.1f+tmin*m_invHorizTime));
+	
+	const float penalty = vpen + vcpen + spen + tpen;
+	
+	// Store different penalties for debug viewing
+	if (debug)
+		debug->addSample(vcand, cs, penalty, vpen, vcpen, spen, tpen);
+	
+	return penalty;
+}
+
+int dtObstacleAvoidanceQuery::sampleVelocityGrid(const float* pos, const float rad, const float vmax,
+												 const float* vel, const float* dvel, float* nvel,
+												 const dtObstacleAvoidanceParams* params,
+												 dtObstacleAvoidanceDebugData* debug)
+{
+	prepare(pos, dvel);
+	
+	memcpy(&m_params, params, sizeof(dtObstacleAvoidanceParams));
+	m_invHorizTime = 1.0f / m_params.horizTime;
+	m_vmax = vmax;
+	m_invVmax = 1.0f / vmax;
+	
+	dtVset(nvel, 0,0,0);
+	
+	if (debug)
+		debug->reset();
+
+	const float cvx = dvel[0] * m_params.velBias;
+	const float cvz = dvel[2] * m_params.velBias;
+	const float cs = vmax * 2 * (1 - m_params.velBias) / (float)(m_params.gridSize-1);
+	const float half = (m_params.gridSize-1)*cs*0.5f;
+		
+	float minPenalty = FLT_MAX;
+	int ns = 0;
+		
+	for (int y = 0; y < m_params.gridSize; ++y)
+	{
+		for (int x = 0; x < m_params.gridSize; ++x)
+		{
+			float vcand[3];
+			vcand[0] = cvx + x*cs - half;
+			vcand[1] = 0;
+			vcand[2] = cvz + y*cs - half;
+			
+			if (dtSqr(vcand[0])+dtSqr(vcand[2]) > dtSqr(vmax+cs/2)) continue;
+			
+			const float penalty = processSample(vcand, cs, pos,rad,vel,dvel, debug);
+			ns++;
+			if (penalty < minPenalty)
+			{
+				minPenalty = penalty;
+				dtVcopy(nvel, vcand);
+			}
+		}
+	}
+	
+	return ns;
+}
+
+
+int dtObstacleAvoidanceQuery::sampleVelocityAdaptive(const float* pos, const float rad, const float vmax,
+													 const float* vel, const float* dvel, float* nvel,
+													 const dtObstacleAvoidanceParams* params,
+													 dtObstacleAvoidanceDebugData* debug)
+{
+	prepare(pos, dvel);
+	
+	memcpy(&m_params, params, sizeof(dtObstacleAvoidanceParams));
+	m_invHorizTime = 1.0f / m_params.horizTime;
+	m_vmax = vmax;
+	m_invVmax = 1.0f / vmax;
+	
+	dtVset(nvel, 0,0,0);
+	
+	if (debug)
+		debug->reset();
+
+	// Build sampling pattern aligned to desired velocity.
+	float pat[(DT_MAX_PATTERN_DIVS*DT_MAX_PATTERN_RINGS+1)*2];
+	int npat = 0;
+
+	const int ndivs = (int)m_params.adaptiveDivs;
+	const int nrings= (int)m_params.adaptiveRings;
+	const int depth = (int)m_params.adaptiveDepth;
+	
+	const int nd = dtClamp(ndivs, 1, DT_MAX_PATTERN_DIVS);
+	const int nr = dtClamp(nrings, 1, DT_MAX_PATTERN_RINGS);
+	const float da = (1.0f/nd) * DT_PI*2;
+	const float dang = dtMathAtan2f(dvel[2], dvel[0]);
+	
+	// Always add sample at zero
+	pat[npat*2+0] = 0;
+	pat[npat*2+1] = 0;
+	npat++;
+	
+	for (int j = 0; j < nr; ++j)
+	{
+		const float r = (float)(nr-j)/(float)nr;
+		float a = dang + (j&1)*0.5f*da;
+		for (int i = 0; i < nd; ++i)
+		{
+			pat[npat*2+0] = cosf(a)*r;
+			pat[npat*2+1] = sinf(a)*r;
+			npat++;
+			a += da;
+		}
+	}
+
+	// Start sampling.
+	float cr = vmax * (1.0f - m_params.velBias);
+	float res[3];
+	dtVset(res, dvel[0] * m_params.velBias, 0, dvel[2] * m_params.velBias);
+	int ns = 0;
+
+	for (int k = 0; k < depth; ++k)
+	{
+		float minPenalty = FLT_MAX;
+		float bvel[3];
+		dtVset(bvel, 0,0,0);
+		
+		for (int i = 0; i < npat; ++i)
+		{
+			float vcand[3];
+			vcand[0] = res[0] + pat[i*2+0]*cr;
+			vcand[1] = 0;
+			vcand[2] = res[2] + pat[i*2+1]*cr;
+			
+			if (dtSqr(vcand[0])+dtSqr(vcand[2]) > dtSqr(vmax+0.001f)) continue;
+			
+			const float penalty = processSample(vcand,cr/10, pos,rad,vel,dvel, debug);
+			ns++;
+			if (penalty < minPenalty)
+			{
+				minPenalty = penalty;
+				dtVcopy(bvel, vcand);
+			}
+		}
+
+		dtVcopy(res, bvel);
+
+		cr *= 0.5f;
+	}	
+	
+	dtVcopy(nvel, res);
+	
+	return ns;
+}
+

+ 597 - 0
Source/ThirdParty/DetourCrowd/source/DetourPathCorridor.cpp

@@ -0,0 +1,597 @@
+//
+// Copyright (c) 2009-2010 Mikko Mononen [email protected]
+//
+// 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 <string.h>
+#include "DetourPathCorridor.h"
+#include "DetourNavMeshQuery.h"
+#include "DetourCommon.h"
+#include "DetourAssert.h"
+#include "DetourAlloc.h"
+
+
+int dtMergeCorridorStartMoved(dtPolyRef* path, const int npath, const int maxPath,
+							  const dtPolyRef* visited, const int nvisited)
+{
+	int furthestPath = -1;
+	int furthestVisited = -1;
+	
+	// Find furthest common polygon.
+	for (int i = npath-1; i >= 0; --i)
+	{
+		bool found = false;
+		for (int j = nvisited-1; j >= 0; --j)
+		{
+			if (path[i] == visited[j])
+			{
+				furthestPath = i;
+				furthestVisited = j;
+				found = true;
+			}
+		}
+		if (found)
+			break;
+	}
+	
+	// If no intersection found just return current path. 
+	if (furthestPath == -1 || furthestVisited == -1)
+		return npath;
+	
+	// Concatenate paths.	
+	
+	// Adjust beginning of the buffer to include the visited.
+	const int req = nvisited - furthestVisited;
+	const int orig = dtMin(furthestPath+1, npath);
+	int size = dtMax(0, npath-orig);
+	if (req+size > maxPath)
+		size = maxPath-req;
+	if (size)
+		memmove(path+req, path+orig, size*sizeof(dtPolyRef));
+	
+	// Store visited
+	for (int i = 0; i < req; ++i)
+		path[i] = visited[(nvisited-1)-i];				
+	
+	return req+size;
+}
+
+int dtMergeCorridorEndMoved(dtPolyRef* path, const int npath, const int maxPath,
+							const dtPolyRef* visited, const int nvisited)
+{
+	int furthestPath = -1;
+	int furthestVisited = -1;
+	
+	// Find furthest common polygon.
+	for (int i = 0; i < npath; ++i)
+	{
+		bool found = false;
+		for (int j = nvisited-1; j >= 0; --j)
+		{
+			if (path[i] == visited[j])
+			{
+				furthestPath = i;
+				furthestVisited = j;
+				found = true;
+			}
+		}
+		if (found)
+			break;
+	}
+	
+	// If no intersection found just return current path. 
+	if (furthestPath == -1 || furthestVisited == -1)
+		return npath;
+	
+	// Concatenate paths.
+	const int ppos = furthestPath+1;
+	const int vpos = furthestVisited+1;
+	const int count = dtMin(nvisited-vpos, maxPath-ppos);
+	dtAssert(ppos+count <= maxPath);
+	if (count)
+		memcpy(path+ppos, visited+vpos, sizeof(dtPolyRef)*count);
+	
+	return ppos+count;
+}
+
+int dtMergeCorridorStartShortcut(dtPolyRef* path, const int npath, const int maxPath,
+								 const dtPolyRef* visited, const int nvisited)
+{
+	int furthestPath = -1;
+	int furthestVisited = -1;
+	
+	// Find furthest common polygon.
+	for (int i = npath-1; i >= 0; --i)
+	{
+		bool found = false;
+		for (int j = nvisited-1; j >= 0; --j)
+		{
+			if (path[i] == visited[j])
+			{
+				furthestPath = i;
+				furthestVisited = j;
+				found = true;
+			}
+		}
+		if (found)
+			break;
+	}
+	
+	// If no intersection found just return current path. 
+	if (furthestPath == -1 || furthestVisited == -1)
+		return npath;
+	
+	// Concatenate paths.	
+	
+	// Adjust beginning of the buffer to include the visited.
+	const int req = furthestVisited;
+	if (req <= 0)
+		return npath;
+	
+	const int orig = furthestPath;
+	int size = dtMax(0, npath-orig);
+	if (req+size > maxPath)
+		size = maxPath-req;
+	if (size)
+		memmove(path+req, path+orig, size*sizeof(dtPolyRef));
+	
+	// Store visited
+	for (int i = 0; i < req; ++i)
+		path[i] = visited[i];
+	
+	return req+size;
+}
+
+/**
+@class dtPathCorridor
+@par
+
+The corridor is loaded with a path, usually obtained from a #dtNavMeshQuery::findPath() query. The corridor
+is then used to plan local movement, with the corridor automatically updating as needed to deal with inaccurate 
+agent locomotion.
+
+Example of a common use case:
+
+-# Construct the corridor object and call #init() to allocate its path buffer.
+-# Obtain a path from a #dtNavMeshQuery object.
+-# Use #reset() to set the agent's current position. (At the beginning of the path.)
+-# Use #setCorridor() to load the path and target.
+-# Use #findCorners() to plan movement. (This handles dynamic path straightening.)
+-# Use #movePosition() to feed agent movement back into the corridor. (The corridor will automatically adjust as needed.)
+-# If the target is moving, use #moveTargetPosition() to update the end of the corridor. 
+   (The corridor will automatically adjust as needed.)
+-# Repeat the previous 3 steps to continue to move the agent.
+
+The corridor position and target are always constrained to the navigation mesh.
+
+One of the difficulties in maintaining a path is that floating point errors, locomotion inaccuracies, and/or local 
+steering can result in the agent crossing the boundary of the path corridor, temporarily invalidating the path. 
+This class uses local mesh queries to detect and update the corridor as needed to handle these types of issues. 
+
+The fact that local mesh queries are used to move the position and target locations results in two beahviors that 
+need to be considered:
+
+Every time a move function is used there is a chance that the path will become non-optimial. Basically, the further 
+the target is moved from its original location, and the further the position is moved outside the original corridor, 
+the more likely the path will become non-optimal. This issue can be addressed by periodically running the 
+#optimizePathTopology() and #optimizePathVisibility() methods.
+
+All local mesh queries have distance limitations. (Review the #dtNavMeshQuery methods for details.) So the most accurate 
+use case is to move the position and target in small increments. If a large increment is used, then the corridor 
+may not be able to accurately find the new location.  Because of this limiation, if a position is moved in a large
+increment, then compare the desired and resulting polygon references. If the two do not match, then path replanning 
+may be needed.  E.g. If you move the target, check #getLastPoly() to see if it is the expected polygon.
+
+*/
+
+dtPathCorridor::dtPathCorridor() :
+	m_path(0),
+	m_npath(0),
+	m_maxPath(0)
+{
+}
+
+dtPathCorridor::~dtPathCorridor()
+{
+	dtFree(m_path);
+}
+
+/// @par
+///
+/// @warning Cannot be called more than once.
+bool dtPathCorridor::init(const int maxPath)
+{
+	dtAssert(!m_path);
+	m_path = (dtPolyRef*)dtAlloc(sizeof(dtPolyRef)*maxPath, DT_ALLOC_PERM);
+	if (!m_path)
+		return false;
+	m_npath = 0;
+	m_maxPath = maxPath;
+	return true;
+}
+
+/// @par
+///
+/// Essentially, the corridor is set of one polygon in size with the target
+/// equal to the position.
+void dtPathCorridor::reset(dtPolyRef ref, const float* pos)
+{
+	dtAssert(m_path);
+	dtVcopy(m_pos, pos);
+	dtVcopy(m_target, pos);
+	m_path[0] = ref;
+	m_npath = 1;
+}
+
+/**
+@par
+
+This is the function used to plan local movement within the corridor. One or more corners can be 
+detected in order to plan movement. It performs essentially the same function as #dtNavMeshQuery::findStraightPath.
+
+Due to internal optimizations, the maximum number of corners returned will be (@p maxCorners - 1) 
+For example: If the buffers are sized to hold 10 corners, the function will never return more than 9 corners. 
+So if 10 corners are needed, the buffers should be sized for 11 corners.
+
+If the target is within range, it will be the last corner and have a polygon reference id of zero.
+*/
+int dtPathCorridor::findCorners(float* cornerVerts, unsigned char* cornerFlags,
+							  dtPolyRef* cornerPolys, const int maxCorners,
+							  dtNavMeshQuery* navquery, const dtQueryFilter* /*filter*/)
+{
+	dtAssert(m_path);
+	dtAssert(m_npath);
+	
+	static const float MIN_TARGET_DIST = 0.01f;
+	
+	int ncorners = 0;
+	navquery->findStraightPath(m_pos, m_target, m_path, m_npath,
+							   cornerVerts, cornerFlags, cornerPolys, &ncorners, maxCorners);
+	
+	// Prune points in the beginning of the path which are too close.
+	while (ncorners)
+	{
+		if ((cornerFlags[0] & DT_STRAIGHTPATH_OFFMESH_CONNECTION) ||
+			dtVdist2DSqr(&cornerVerts[0], m_pos) > dtSqr(MIN_TARGET_DIST))
+			break;
+		ncorners--;
+		if (ncorners)
+		{
+			memmove(cornerFlags, cornerFlags+1, sizeof(unsigned char)*ncorners);
+			memmove(cornerPolys, cornerPolys+1, sizeof(dtPolyRef)*ncorners);
+			memmove(cornerVerts, cornerVerts+3, sizeof(float)*3*ncorners);
+		}
+	}
+	
+	// Prune points after an off-mesh connection.
+	for (int i = 0; i < ncorners; ++i)
+	{
+		if (cornerFlags[i] & DT_STRAIGHTPATH_OFFMESH_CONNECTION)
+		{
+			ncorners = i+1;
+			break;
+		}
+	}
+	
+	return ncorners;
+}
+
+/** 
+@par
+
+Inaccurate locomotion or dynamic obstacle avoidance can force the argent position significantly outside the 
+original corridor. Over time this can result in the formation of a non-optimal corridor. Non-optimal paths can 
+also form near the corners of tiles.
+
+This function uses an efficient local visibility search to try to optimize the corridor 
+between the current position and @p next.
+
+The corridor will change only if @p next is visible from the current position and moving directly toward the point 
+is better than following the existing path.
+
+The more inaccurate the agent movement, the more beneficial this function becomes. Simply adjust the frequency 
+of the call to match the needs to the agent.
+
+This function is not suitable for long distance searches.
+*/
+void dtPathCorridor::optimizePathVisibility(const float* next, const float pathOptimizationRange,
+										  dtNavMeshQuery* navquery, const dtQueryFilter* filter)
+{
+	dtAssert(m_path);
+	
+	// Clamp the ray to max distance.
+	float goal[3];
+	dtVcopy(goal, next);
+	float dist = dtVdist2D(m_pos, goal);
+	
+	// If too close to the goal, do not try to optimize.
+	if (dist < 0.01f)
+		return;
+	
+	// Overshoot a little. This helps to optimize open fields in tiled meshes.
+	dist = dtMin(dist+0.01f, pathOptimizationRange);
+	
+	// Adjust ray length.
+	float delta[3];
+	dtVsub(delta, goal, m_pos);
+	dtVmad(goal, m_pos, delta, pathOptimizationRange/dist);
+	
+	static const int MAX_RES = 32;
+	dtPolyRef res[MAX_RES];
+	float t, norm[3];
+	int nres = 0;
+	navquery->raycast(m_path[0], m_pos, goal, filter, &t, norm, res, &nres, MAX_RES);
+	if (nres > 1 && t > 0.99f)
+	{
+		m_npath = dtMergeCorridorStartShortcut(m_path, m_npath, m_maxPath, res, nres);
+	}
+}
+
+/**
+@par
+
+Inaccurate locomotion or dynamic obstacle avoidance can force the agent position significantly outside the 
+original corridor. Over time this can result in the formation of a non-optimal corridor. This function will use a 
+local area path search to try to re-optimize the corridor.
+
+The more inaccurate the agent movement, the more beneficial this function becomes. Simply adjust the frequency of 
+the call to match the needs to the agent.
+*/
+bool dtPathCorridor::optimizePathTopology(dtNavMeshQuery* navquery, const dtQueryFilter* filter)
+{
+	dtAssert(navquery);
+	dtAssert(filter);
+	dtAssert(m_path);
+	
+	if (m_npath < 3)
+		return false;
+	
+	static const int MAX_ITER = 32;
+	static const int MAX_RES = 32;
+	
+	dtPolyRef res[MAX_RES];
+	int nres = 0;
+	navquery->initSlicedFindPath(m_path[0], m_path[m_npath-1], m_pos, m_target, filter);
+	navquery->updateSlicedFindPath(MAX_ITER, 0);
+	dtStatus status = navquery->finalizeSlicedFindPathPartial(m_path, m_npath, res, &nres, MAX_RES);
+	
+	if (dtStatusSucceed(status) && nres > 0)
+	{
+		m_npath = dtMergeCorridorStartShortcut(m_path, m_npath, m_maxPath, res, nres);
+		return true;
+	}
+	
+	return false;
+}
+
+bool dtPathCorridor::moveOverOffmeshConnection(dtPolyRef offMeshConRef, dtPolyRef* refs,
+											   float* startPos, float* endPos,
+											   dtNavMeshQuery* navquery)
+{
+	dtAssert(navquery);
+	dtAssert(m_path);
+	dtAssert(m_npath);
+
+	// Advance the path up to and over the off-mesh connection.
+	dtPolyRef prevRef = 0, polyRef = m_path[0];
+	int npos = 0;
+	while (npos < m_npath && polyRef != offMeshConRef)
+	{
+		prevRef = polyRef;
+		polyRef = m_path[npos];
+		npos++;
+	}
+	if (npos == m_npath)
+	{
+		// Could not find offMeshConRef
+		return false;
+	}
+	
+	// Prune path
+	for (int i = npos; i < m_npath; ++i)
+		m_path[i-npos] = m_path[i];
+	m_npath -= npos;
+
+	refs[0] = prevRef;
+	refs[1] = polyRef;
+	
+	const dtNavMesh* nav = navquery->getAttachedNavMesh();
+	dtAssert(nav);
+
+	dtStatus status = nav->getOffMeshConnectionPolyEndPoints(refs[0], refs[1], startPos, endPos);
+	if (dtStatusSucceed(status))
+	{
+		dtVcopy(m_pos, endPos);
+		return true;
+	}
+
+	return false;
+}
+
+/**
+@par
+
+Behavior:
+
+- The movement is constrained to the surface of the navigation mesh. 
+- The corridor is automatically adjusted (shorted or lengthened) in order to remain valid. 
+- The new position will be located in the adjusted corridor's first polygon.
+
+The expected use case is that the desired position will be 'near' the current corridor. What is considered 'near' 
+depends on local polygon density, query search extents, etc.
+
+The resulting position will differ from the desired position if the desired position is not on the navigation mesh, 
+or it can't be reached using a local search.
+*/
+bool dtPathCorridor::movePosition(const float* npos, dtNavMeshQuery* navquery, const dtQueryFilter* filter)
+{
+	dtAssert(m_path);
+	dtAssert(m_npath);
+	
+	// Move along navmesh and update new position.
+	float result[3];
+	static const int MAX_VISITED = 16;
+	dtPolyRef visited[MAX_VISITED];
+	int nvisited = 0;
+	dtStatus status = navquery->moveAlongSurface(m_path[0], m_pos, npos, filter,
+												 result, visited, &nvisited, MAX_VISITED);
+	if (dtStatusSucceed(status)) {
+		m_npath = dtMergeCorridorStartMoved(m_path, m_npath, m_maxPath, visited, nvisited);
+		
+		// Adjust the position to stay on top of the navmesh.
+		float h = m_pos[1];
+		navquery->getPolyHeight(m_path[0], result, &h);
+		result[1] = h;
+		dtVcopy(m_pos, result);
+		return true;
+	}
+	return false;
+}
+
+/**
+@par
+
+Behavior:
+
+- The movement is constrained to the surface of the navigation mesh. 
+- The corridor is automatically adjusted (shorted or lengthened) in order to remain valid. 
+- The new target will be located in the adjusted corridor's last polygon.
+
+The expected use case is that the desired target will be 'near' the current corridor. What is considered 'near' depends on local polygon density, query search extents, etc.
+
+The resulting target will differ from the desired target if the desired target is not on the navigation mesh, or it can't be reached using a local search.
+*/
+bool dtPathCorridor::moveTargetPosition(const float* npos, dtNavMeshQuery* navquery, const dtQueryFilter* filter)
+{
+	dtAssert(m_path);
+	dtAssert(m_npath);
+	
+	// Move along navmesh and update new position.
+	float result[3];
+	static const int MAX_VISITED = 16;
+	dtPolyRef visited[MAX_VISITED];
+	int nvisited = 0;
+	dtStatus status = navquery->moveAlongSurface(m_path[m_npath-1], m_target, npos, filter,
+												 result, visited, &nvisited, MAX_VISITED);
+	if (dtStatusSucceed(status))
+	{
+		m_npath = dtMergeCorridorEndMoved(m_path, m_npath, m_maxPath, visited, nvisited);
+		// TODO: should we do that?
+		// Adjust the position to stay on top of the navmesh.
+		/*	float h = m_target[1];
+		 navquery->getPolyHeight(m_path[m_npath-1], result, &h);
+		 result[1] = h;*/
+		
+		dtVcopy(m_target, result);
+		
+		return true;
+	}
+	return false;
+}
+
+/// @par
+///
+/// The current corridor position is expected to be within the first polygon in the path. The target 
+/// is expected to be in the last polygon. 
+/// 
+/// @warning The size of the path must not exceed the size of corridor's path buffer set during #init().
+void dtPathCorridor::setCorridor(const float* target, const dtPolyRef* path, const int npath)
+{
+	dtAssert(m_path);
+	dtAssert(npath > 0);
+	dtAssert(npath < m_maxPath);
+	
+	dtVcopy(m_target, target);
+	memcpy(m_path, path, sizeof(dtPolyRef)*npath);
+	m_npath = npath;
+}
+
+bool dtPathCorridor::fixPathStart(dtPolyRef safeRef, const float* safePos)
+{
+	dtAssert(m_path);
+
+	dtVcopy(m_pos, safePos);
+	if (m_npath < 3 && m_npath > 0)
+	{
+		m_path[2] = m_path[m_npath-1];
+		m_path[0] = safeRef;
+		m_path[1] = 0;
+		m_npath = 3;
+	}
+	else
+	{
+		m_path[0] = safeRef;
+		m_path[1] = 0;
+	}
+	
+	return true;
+}
+
+bool dtPathCorridor::trimInvalidPath(dtPolyRef safeRef, const float* safePos,
+									 dtNavMeshQuery* navquery, const dtQueryFilter* filter)
+{
+	dtAssert(navquery);
+	dtAssert(filter);
+	dtAssert(m_path);
+	
+	// Keep valid path as far as possible.
+	int n = 0;
+	while (n < m_npath && navquery->isValidPolyRef(m_path[n], filter)) {
+		n++;
+	}
+	
+	if (n == m_npath)
+	{
+		// All valid, no need to fix.
+		return true;
+	}
+	else if (n == 0)
+	{
+		// The first polyref is bad, use current safe values.
+		dtVcopy(m_pos, safePos);
+		m_path[0] = safeRef;
+		m_npath = 1;
+	}
+	else
+	{
+		// The path is partially usable.
+		m_npath = n;
+	}
+	
+	// Clamp target pos to last poly
+	float tgt[3];
+	dtVcopy(tgt, m_target);
+	navquery->closestPointOnPolyBoundary(m_path[m_npath-1], tgt, m_target);
+	
+	return true;
+}
+
+/// @par
+///
+/// The path can be invalidated if there are structural changes to the underlying navigation mesh, or the state of 
+/// a polygon within the path changes resulting in it being filtered out. (E.g. An exclusion or inclusion flag changes.)
+bool dtPathCorridor::isValid(const int maxLookAhead, dtNavMeshQuery* navquery, const dtQueryFilter* filter)
+{
+	// Check that all polygons still pass query filter.
+	const int n = dtMin(m_npath, maxLookAhead);
+	for (int i = 0; i < n; ++i)
+	{
+		if (!navquery->isValidPolyRef(m_path[i], filter))
+			return false;
+	}
+
+	return true;
+}

+ 200 - 0
Source/ThirdParty/DetourCrowd/source/DetourPathQueue.cpp

@@ -0,0 +1,200 @@
+//
+// Copyright (c) 2009-2010 Mikko Mononen [email protected]
+//
+// 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 <string.h>
+#include "DetourPathQueue.h"
+#include "DetourNavMesh.h"
+#include "DetourNavMeshQuery.h"
+#include "DetourAlloc.h"
+#include "DetourCommon.h"
+
+
+dtPathQueue::dtPathQueue() :
+	m_nextHandle(1),
+	m_maxPathSize(0),
+	m_queueHead(0),
+	m_navquery(0)
+{
+	for (int i = 0; i < MAX_QUEUE; ++i)
+		m_queue[i].path = 0;
+}
+
+dtPathQueue::~dtPathQueue()
+{
+	purge();
+}
+
+void dtPathQueue::purge()
+{
+	dtFreeNavMeshQuery(m_navquery);
+	m_navquery = 0;
+	for (int i = 0; i < MAX_QUEUE; ++i)
+	{
+		dtFree(m_queue[i].path);
+		m_queue[i].path = 0;
+	}
+}
+
+bool dtPathQueue::init(const int maxPathSize, const int maxSearchNodeCount, dtNavMesh* nav)
+{
+	purge();
+
+	m_navquery = dtAllocNavMeshQuery();
+	if (!m_navquery)
+		return false;
+	if (dtStatusFailed(m_navquery->init(nav, maxSearchNodeCount)))
+		return false;
+	
+	m_maxPathSize = maxPathSize;
+	for (int i = 0; i < MAX_QUEUE; ++i)
+	{
+		m_queue[i].ref = DT_PATHQ_INVALID;
+		m_queue[i].path = (dtPolyRef*)dtAlloc(sizeof(dtPolyRef)*m_maxPathSize, DT_ALLOC_PERM);
+		if (!m_queue[i].path)
+			return false;
+	}
+	
+	m_queueHead = 0;
+	
+	return true;
+}
+
+void dtPathQueue::update(const int maxIters)
+{
+	static const int MAX_KEEP_ALIVE = 2; // in update ticks.
+
+	// Update path request until there is nothing to update
+	// or upto maxIters pathfinder iterations has been consumed.
+	int iterCount = maxIters;
+	
+	for (int i = 0; i < MAX_QUEUE; ++i)
+	{
+		PathQuery& q = m_queue[m_queueHead % MAX_QUEUE];
+		
+		// Skip inactive requests.
+		if (q.ref == DT_PATHQ_INVALID)
+		{
+			m_queueHead++;
+			continue;
+		}
+		
+		// Handle completed request.
+		if (dtStatusSucceed(q.status) || dtStatusFailed(q.status))
+		{
+			// If the path result has not been read in few frames, free the slot.
+			q.keepAlive++;
+			if (q.keepAlive > MAX_KEEP_ALIVE)
+			{
+				q.ref = DT_PATHQ_INVALID;
+				q.status = 0;
+			}
+			
+			m_queueHead++;
+			continue;
+		}
+		
+		// Handle query start.
+		if (q.status == 0)
+		{
+			q.status = m_navquery->initSlicedFindPath(q.startRef, q.endRef, q.startPos, q.endPos, q.filter);
+		}		
+		// Handle query in progress.
+		if (dtStatusInProgress(q.status))
+		{
+			int iters = 0;
+			q.status = m_navquery->updateSlicedFindPath(iterCount, &iters);
+			iterCount -= iters;
+		}
+		if (dtStatusSucceed(q.status))
+		{
+			q.status = m_navquery->finalizeSlicedFindPath(q.path, &q.npath, m_maxPathSize);
+		}
+
+		if (iterCount <= 0)
+			break;
+
+		m_queueHead++;
+	}
+}
+
+dtPathQueueRef dtPathQueue::request(dtPolyRef startRef, dtPolyRef endRef,
+									const float* startPos, const float* endPos,
+									const dtQueryFilter* filter)
+{
+	// Find empty slot
+	int slot = -1;
+	for (int i = 0; i < MAX_QUEUE; ++i)
+	{
+		if (m_queue[i].ref == DT_PATHQ_INVALID)
+		{
+			slot = i;
+			break;
+		}
+	}
+	// Could not find slot.
+	if (slot == -1)
+		return DT_PATHQ_INVALID;
+	
+	dtPathQueueRef ref = m_nextHandle++;
+	if (m_nextHandle == DT_PATHQ_INVALID) m_nextHandle++;
+	
+	PathQuery& q = m_queue[slot];
+	q.ref = ref;
+	dtVcopy(q.startPos, startPos);
+	q.startRef = startRef;
+	dtVcopy(q.endPos, endPos);
+	q.endRef = endRef;
+	
+	q.status = 0;
+	q.npath = 0;
+	q.filter = filter;
+	q.keepAlive = 0;
+	
+	return ref;
+}
+
+dtStatus dtPathQueue::getRequestStatus(dtPathQueueRef ref) const
+{
+	for (int i = 0; i < MAX_QUEUE; ++i)
+	{
+		if (m_queue[i].ref == ref)
+			return m_queue[i].status;
+	}
+	return DT_FAILURE;
+}
+
+dtStatus dtPathQueue::getPathResult(dtPathQueueRef ref, dtPolyRef* path, int* pathSize, const int maxPath)
+{
+	for (int i = 0; i < MAX_QUEUE; ++i)
+	{
+		if (m_queue[i].ref == ref)
+		{
+			PathQuery& q = m_queue[i];
+			dtStatus details = q.status & DT_STATUS_DETAIL_MASK;
+			// Free request for reuse.
+			q.ref = DT_PATHQ_INVALID;
+			q.status = 0;
+			// Copy path
+			int n = dtMin(q.npath, maxPath);
+			memcpy(path, q.path, sizeof(dtPolyRef)*n);
+			*pathSize = n;
+			return details | DT_SUCCESS;
+		}
+	}
+	return DT_FAILURE;
+}

+ 194 - 0
Source/ThirdParty/DetourCrowd/source/DetourProximityGrid.cpp

@@ -0,0 +1,194 @@
+//
+// Copyright (c) 2009-2010 Mikko Mononen [email protected]
+//
+// 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 <string.h>
+#include <new>
+#include "DetourProximityGrid.h"
+#include "DetourCommon.h"
+#include "DetourMath.h"
+#include "DetourAlloc.h"
+#include "DetourAssert.h"
+
+
+dtProximityGrid* dtAllocProximityGrid()
+{
+	void* mem = dtAlloc(sizeof(dtProximityGrid), DT_ALLOC_PERM);
+	if (!mem) return 0;
+	return new(mem) dtProximityGrid;
+}
+
+void dtFreeProximityGrid(dtProximityGrid* ptr)
+{
+	if (!ptr) return;
+	ptr->~dtProximityGrid();
+	dtFree(ptr);
+}
+
+
+inline int hashPos2(int x, int y, int n)
+{
+	return ((x*73856093) ^ (y*19349663)) & (n-1);
+}
+
+
+dtProximityGrid::dtProximityGrid() :
+	m_maxItems(0),
+	m_cellSize(0),
+	m_pool(0),
+	m_poolHead(0),
+	m_poolSize(0),
+	m_buckets(0),
+	m_bucketsSize(0)
+{
+}
+
+dtProximityGrid::~dtProximityGrid()
+{
+	dtFree(m_buckets);
+	dtFree(m_pool);
+}
+
+bool dtProximityGrid::init(const int poolSize, const float cellSize)
+{
+	dtAssert(poolSize > 0);
+	dtAssert(cellSize > 0.0f);
+	
+	m_cellSize = cellSize;
+	m_invCellSize = 1.0f / m_cellSize;
+	
+	// Allocate hashs buckets
+	m_bucketsSize = dtNextPow2(poolSize);
+	m_buckets = (unsigned short*)dtAlloc(sizeof(unsigned short)*m_bucketsSize, DT_ALLOC_PERM);
+	if (!m_buckets)
+		return false;
+	
+	// Allocate pool of items.
+	m_poolSize = poolSize;
+	m_poolHead = 0;
+	m_pool = (Item*)dtAlloc(sizeof(Item)*m_poolSize, DT_ALLOC_PERM);
+	if (!m_pool)
+		return false;
+	
+	clear();
+	
+	return true;
+}
+
+void dtProximityGrid::clear()
+{
+	memset(m_buckets, 0xff, sizeof(unsigned short)*m_bucketsSize);
+	m_poolHead = 0;
+	m_bounds[0] = 0xffff;
+	m_bounds[1] = 0xffff;
+	m_bounds[2] = -0xffff;
+	m_bounds[3] = -0xffff;
+}
+
+void dtProximityGrid::addItem(const unsigned short id,
+							  const float minx, const float miny,
+							  const float maxx, const float maxy)
+{
+	const int iminx = (int)dtMathFloorf(minx * m_invCellSize);
+	const int iminy = (int)dtMathFloorf(miny * m_invCellSize);
+	const int imaxx = (int)dtMathFloorf(maxx * m_invCellSize);
+	const int imaxy = (int)dtMathFloorf(maxy * m_invCellSize);
+	
+	m_bounds[0] = dtMin(m_bounds[0], iminx);
+	m_bounds[1] = dtMin(m_bounds[1], iminy);
+	m_bounds[2] = dtMax(m_bounds[2], imaxx);
+	m_bounds[3] = dtMax(m_bounds[3], imaxy);
+	
+	for (int y = iminy; y <= imaxy; ++y)
+	{
+		for (int x = iminx; x <= imaxx; ++x)
+		{
+			if (m_poolHead < m_poolSize)
+			{
+				const int h = hashPos2(x, y, m_bucketsSize);
+				const unsigned short idx = (unsigned short)m_poolHead;
+				m_poolHead++;
+				Item& item = m_pool[idx];
+				item.x = (short)x;
+				item.y = (short)y;
+				item.id = id;
+				item.next = m_buckets[h];
+				m_buckets[h] = idx;
+			}
+		}
+	}
+}
+
+int dtProximityGrid::queryItems(const float minx, const float miny,
+								const float maxx, const float maxy,
+								unsigned short* ids, const int maxIds) const
+{
+	const int iminx = (int)dtMathFloorf(minx * m_invCellSize);
+	const int iminy = (int)dtMathFloorf(miny * m_invCellSize);
+	const int imaxx = (int)dtMathFloorf(maxx * m_invCellSize);
+	const int imaxy = (int)dtMathFloorf(maxy * m_invCellSize);
+	
+	int n = 0;
+	
+	for (int y = iminy; y <= imaxy; ++y)
+	{
+		for (int x = iminx; x <= imaxx; ++x)
+		{
+			const int h = hashPos2(x, y, m_bucketsSize);
+			unsigned short idx = m_buckets[h];
+			while (idx != 0xffff)
+			{
+				Item& item = m_pool[idx];
+				if ((int)item.x == x && (int)item.y == y)
+				{
+					// Check if the id exists already.
+					const unsigned short* end = ids + n;
+					unsigned short* i = ids;
+					while (i != end && *i != item.id)
+						++i;
+					// Item not found, add it.
+					if (i == end)
+					{
+						if (n >= maxIds)
+							return n;
+						ids[n++] = item.id;
+					}
+				}
+				idx = item.next;
+			}
+		}
+	}
+	
+	return n;
+}
+
+int dtProximityGrid::getItemCountAt(const int x, const int y) const
+{
+	int n = 0;
+	
+	const int h = hashPos2(x, y, m_bucketsSize);
+	unsigned short idx = m_buckets[h];
+	while (idx != 0xffff)
+	{
+		Item& item = m_pool[idx];
+		if ((int)item.x == x && (int)item.y == y)
+			n++;
+		idx = item.next;
+	}
+	
+	return n;
+}

+ 37 - 0
Source/ThirdParty/DetourTileCache/CMakeLists.txt

@@ -0,0 +1,37 @@
+#
+# Copyright (c) 2008-2015 the Urho3D project.
+#
+# Permission is hereby granted, free of charge, to any person obtaining a copy
+# of this software and associated documentation files (the "Software"), to deal
+# in the Software without restriction, including without limitation the rights
+# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+# copies of the Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be included in
+# all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+# THE SOFTWARE.
+#
+
+# Define target name
+set (TARGET_NAME DetourTileCache)
+
+# Define source files
+define_source_files (GLOB_CPP_PATTERNS source/*.cpp GLOB_H_PATTERNS include/*.h)
+
+# Define dependency libs
+set (INCLUDE_DIRS include ../Detour/include)
+
+
+# Setup target
+setup_library ()
+
+# Install headers for building the Urho3D library
+install_header_files (DIRECTORY include/ DESTINATION ${DEST_INCLUDE_DIR}/ThirdParty/DetourTileCache FILES_MATCHING PATTERN *.h BUILD_TREE_ONLY)  # Note: the trailing slash is significant

+ 18 - 0
Source/ThirdParty/DetourTileCache/License.txt

@@ -0,0 +1,18 @@
+Copyright (c) 2009 Mikko Mononen [email protected]
+
+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.
+

+ 62 - 0
Source/ThirdParty/DetourTileCache/README.md

@@ -0,0 +1,62 @@
+
+Recast & Detour
+===============
+
+[![Bitdeli Badge](https://d2weczhvl823v0.cloudfront.net/memononen/recastnavigation/trend.png)](https://bitdeli.com/free "Bitdeli Badge")
+
+![screenshot of a navmesh baked with the sample program](/RecastDemo/screenshot.png?raw=true)
+
+## Recast
+
+Recast is state of the art navigation mesh construction toolset for games.
+
+* It is automatic, which means that you can throw any level geometry at it and you will get robust mesh out
+* It is fast which means swift turnaround times for level designers
+* It is open source so it comes with full source and you can customize it to your heart's content. 
+
+The Recast process starts with constructing a voxel mold from a level geometry 
+and then casting a navigation mesh over it. The process consists of three steps, 
+building the voxel mold, partitioning the mold into simple regions, peeling off 
+the regions as simple polygons.
+
+1. The voxel mold is build from the input triangle mesh by rasterizing the triangles into a multi-layer heightfield. Some simple filters are  then applied to the mold to prune out locations where the character would not be able to move.
+2. The walkable areas described by the mold are divided into simple overlayed 2D regions. The resulting regions have only one non-overlapping contour, which simplifies the final step of the process tremendously.
+3. The navigation polygons are peeled off from the regions by first tracing the boundaries and then simplifying them. The resulting polygons are finally converted to convex polygons which makes them perfect for pathfinding and spatial reasoning about the level. 
+
+
+## Detour
+
+Recast is accompanied with Detour, path-finding and spatial reasoning toolkit. You can use any navigation mesh with Detour, but of course the data generated with Recast fits perfectly.
+
+Detour offers simple static navigation mesh which is suitable for many simple cases, as well as tiled navigation mesh which allows you to plug in and out pieces of the mesh. The tiled mesh allows you to create systems where you stream new navigation data in and out as the player progresses the level, or you may regenerate tiles as the world changes. 
+
+
+## Recast Demo
+
+You can find a comprehensive demo project in RecastDemo folder. It is a kitchen sink demo containing all the functionality of the library. If you are new to Recast & Detour, check out [Sample_SoloMesh.cpp](/RecastDemo/Source/Sample_SoloMesh.cpp) to get started with building navmeshes and [NavMeshTesterTool.cpp](/RecastDemo/Source/NavMeshTesterTool.cpp) to see how Detour can be used to find paths.
+
+### Building RecastDemo
+
+RecastDemo uses [premake4](http://industriousone.com/premake) to build platform specific projects, now is good time to install it if you don't have it already. To build *RecasDemo*, in your favorite terminal navigate into the `RecastDemo` folder, then:
+
+- *OS X*: `premake4 xcode4`
+- *Windows*: `premake4 vs2010`
+- *Linux*: `premake4 gmake`
+
+See premake4 documentation for full list of supported build file types. The projects will be created in `RecastDemo/Build` folder. And after you have compiled the project, the *RecastDemo* executable will be located in `RecastDemo/Bin` folder.
+
+
+## Integrating with your own project
+
+It is recommended to add the source directories `DebugUtils`, `Detour`, `DetourCrowd`, `DetourTileCache`, and `Recast` into your own project depending on which parts of the project you need. For example your level building tool could include DebugUtils, Recast, and Detour, and your game runtime could just include Detour.
+
+
+## Discuss
+
+- Discuss Recast & Detour: http://groups.google.com/group/recastnavigation
+- Development blog: http://digestingduck.blogspot.com/
+
+
+## License
+
+Recast & Detour is licensed under ZLib license, see License.txt for more information.

+ 120 - 0
Source/ThirdParty/DetourTileCache/Readme.txt

@@ -0,0 +1,120 @@
+
+Recast & Detour Version 1.4
+
+
+Recast
+
+Recast is state of the art navigation mesh construction toolset for games.
+
+    * It is automatic, which means that you can throw any level geometry
+      at it and you will get robust mesh out
+    * It is fast which means swift turnaround times for level designers
+    * It is open source so it comes with full source and you can
+      customize it to your hearts content. 
+
+The Recast process starts with constructing a voxel mold from a level geometry 
+and then casting a navigation mesh over it. The process consists of three steps, 
+building the voxel mold, partitioning the mold into simple regions, peeling off 
+the regions as simple polygons.
+
+   1. The voxel mold is build from the input triangle mesh by rasterizing 
+      the triangles into a multi-layer heightfield. Some simple filters are 
+      then applied to the mold to prune out locations where the character 
+      would not be able to move.
+   2. The walkable areas described by the mold are divided into simple 
+      overlayed 2D regions. The resulting regions have only one non-overlapping 
+      contour, which simplifies the final step of the process tremendously.
+   3. The navigation polygons are peeled off from the regions by first tracing 
+      the boundaries and then simplifying them. The resulting polygons are 
+      finally converted to convex polygons which makes them perfect for 
+      pathfinding and spatial reasoning about the level. 
+
+The toolset code is located in the Recast folder and demo application using the Recast
+toolset is located in the RecastDemo folder.
+
+The project files with this distribution can be compiled with Microsoft Visual C++ 2008
+(you can download it for free) and XCode 3.1.
+
+
+Detour
+
+Recast is accompanied with Detour, path-finding and spatial reasoning toolkit. You can use any navigation mesh with Detour, but of course the data generated with Recast fits perfectly.
+
+Detour offers simple static navigation mesh which is suitable for many simple cases, as well as tiled navigation mesh which allows you to plug in and out pieces of the mesh. The tiled mesh allows to create systems where you stream new navigation data in and out as the player progresses the level, or you may regenerate tiles as the world changes. 
+
+
+Latest code available at http://code.google.com/p/recastnavigation/
+
+
+--
+
+Release Notes
+
+----------------
+* Recast 1.4
+  Released August 24th, 2009
+
+- Added detail height mesh generation (RecastDetailMesh.cpp) for single,
+  tiled statmeshes as well as tilemesh.
+- Added feature to contour tracing which detects extra vertices along
+  tile edges which should be removed later.
+- Changed the tiled stat mesh preprocess, so that it first generated
+  polymeshes per tile and finally combines them.
+- Fixed bug in the GUI code where invisible buttons could be pressed.
+
+----------------
+* Recast 1.31
+  Released July 24th, 2009
+
+- Better cost and heuristic functions.
+- Fixed tile navmesh raycast on tile borders.
+
+----------------
+* Recast 1.3
+  Released July 14th, 2009
+
+- Added dtTileNavMesh which allows to dynamically add and remove navmesh pieces at runtime.
+- Renamed stat navmesh types to dtStat* (i.e. dtPoly is now dtStatPoly).
+- Moved common code used by tile and stat navmesh to DetourNode.h/cpp and DetourCommon.h/cpp.
+- Refactores the demo code.
+
+----------------
+* Recast 1.2
+  Released June 17th, 2009
+
+- Added tiled mesh generation. The tiled generation allows to generate navigation for
+  much larger worlds, it removes some of the artifacts that comes from distance fields
+  in open areas, and allows later streaming and dynamic runtime generation
+- Improved and added some debug draw modes
+- API change: The helper function rcBuildNavMesh does not exists anymore,
+  had to change few internal things to cope with the tiled processing,
+  similar API functionality will be added later once the tiled process matures
+- The demo is getting way too complicated, need to split demos
+- Fixed several filtering functions so that the mesh is tighter to the geometry,
+  sometimes there could be up error up to tow voxel units close to walls,
+  now it should be just one.
+
+----------------
+* Recast 1.1
+  Released April 11th, 2009
+
+This is the first release of Detour.
+
+----------------
+* Recast 1.0
+  Released March 29th, 2009
+
+This is the first release of Recast.
+
+The process is not always as robust as I would wish. The watershed phase sometimes swallows tiny islands
+which are close to edges. These droppings are handled in rcBuildContours, but the code is not
+particularly robust either.
+
+Another non-robust case is when portal contours (contours shared between two regions) are always
+assumed to be straight. That can lead to overlapping contours specially when the level has
+large open areas.
+
+
+
+Mikko Mononen
[email protected]

+ 20 - 0
Source/ThirdParty/DetourTileCache/TODO.txt

@@ -0,0 +1,20 @@
+TODO/Roadmap
+
+Summer/Autumn 2009
+
+- Off mesh links (jump links)
+- Area annotations
+- Embed extra data per polygon
+- Height conforming navmesh
+
+
+Autumn/Winter 2009/2010
+
+- Detour path following
+- More dynamic example with tile navmesh
+- Faster small tile process
+
+
+More info at http://digestingduck.blogspot.com/2009/07/recast-and-detour-roadmap.html
+
+-

+ 212 - 0
Source/ThirdParty/DetourTileCache/include/DetourTileCache.h

@@ -0,0 +1,212 @@
+#ifndef DETOURTILECACHE_H
+#define DETOURTILECACHE_H
+
+#include "DetourStatus.h"
+
+
+
+typedef unsigned int dtObstacleRef;
+
+typedef unsigned int dtCompressedTileRef;
+
+/// Flags for addTile
+enum dtCompressedTileFlags
+{
+	DT_COMPRESSEDTILE_FREE_DATA = 0x01,					///< Navmesh owns the tile memory and should free it.
+};
+
+struct dtCompressedTile
+{
+	unsigned int salt;						///< Counter describing modifications to the tile.
+	struct dtTileCacheLayerHeader* header;
+	unsigned char* compressed;
+	int compressedSize;
+	unsigned char* data;
+	int dataSize;
+	unsigned int flags;
+	dtCompressedTile* next;
+};
+
+enum ObstacleState
+{
+	DT_OBSTACLE_EMPTY,
+	DT_OBSTACLE_PROCESSING,
+	DT_OBSTACLE_PROCESSED,
+	DT_OBSTACLE_REMOVING,
+};
+
+static const int DT_MAX_TOUCHED_TILES = 8;
+struct dtTileCacheObstacle
+{
+	float pos[3], radius, height;
+	dtCompressedTileRef touched[DT_MAX_TOUCHED_TILES];
+	dtCompressedTileRef pending[DT_MAX_TOUCHED_TILES];
+	unsigned short salt;
+	unsigned char state;
+	unsigned char ntouched;
+	unsigned char npending;
+	dtTileCacheObstacle* next;
+};
+
+struct dtTileCacheParams
+{
+	float orig[3];
+	float cs, ch;
+	int width, height;
+	float walkableHeight;
+	float walkableRadius;
+	float walkableClimb;
+	float maxSimplificationError;
+	int maxTiles;
+	int maxObstacles;
+};
+
+struct dtTileCacheMeshProcess
+{
+	virtual ~dtTileCacheMeshProcess() { }
+
+	virtual void process(struct dtNavMeshCreateParams* params,
+						 unsigned char* polyAreas, unsigned short* polyFlags) = 0;
+};
+
+
+class dtTileCache
+{
+public:
+	dtTileCache();
+	~dtTileCache();
+	
+	struct dtTileCacheAlloc* getAlloc() { return m_talloc; }
+	struct dtTileCacheCompressor* getCompressor() { return m_tcomp; }
+	const dtTileCacheParams* getParams() const { return &m_params; }
+	
+	inline int getTileCount() const { return m_params.maxTiles; }
+	inline const dtCompressedTile* getTile(const int i) const { return &m_tiles[i]; }
+	
+	inline int getObstacleCount() const { return m_params.maxObstacles; }
+	inline const dtTileCacheObstacle* getObstacle(const int i) const { return &m_obstacles[i]; }
+	
+	const dtTileCacheObstacle* getObstacleByRef(dtObstacleRef ref);
+	
+	dtObstacleRef getObstacleRef(const dtTileCacheObstacle* obmin) const;
+	
+	dtStatus init(const dtTileCacheParams* params,
+				  struct dtTileCacheAlloc* talloc,
+				  struct dtTileCacheCompressor* tcomp,
+				  struct dtTileCacheMeshProcess* tmproc);
+	
+	int getTilesAt(const int tx, const int ty, dtCompressedTileRef* tiles, const int maxTiles) const ;
+	
+	dtCompressedTile* getTileAt(const int tx, const int ty, const int tlayer);
+	dtCompressedTileRef getTileRef(const dtCompressedTile* tile) const;
+	const dtCompressedTile* getTileByRef(dtCompressedTileRef ref) const;
+	
+	dtStatus addTile(unsigned char* data, const int dataSize, unsigned char flags, dtCompressedTileRef* result);
+	
+	dtStatus removeTile(dtCompressedTileRef ref, unsigned char** data, int* dataSize);
+	
+	dtStatus addObstacle(const float* pos, const float radius, const float height, dtObstacleRef* result);
+	dtStatus removeObstacle(const dtObstacleRef ref);
+	
+	dtStatus queryTiles(const float* bmin, const float* bmax,
+						dtCompressedTileRef* results, int* resultCount, const int maxResults) const;
+	
+	dtStatus update(const float /*dt*/, class dtNavMesh* navmesh);
+	
+	dtStatus buildNavMeshTilesAt(const int tx, const int ty, class dtNavMesh* navmesh);
+	
+	dtStatus buildNavMeshTile(const dtCompressedTileRef ref, class dtNavMesh* navmesh);
+	
+	void calcTightTileBounds(const struct dtTileCacheLayerHeader* header, float* bmin, float* bmax) const;
+	
+	void getObstacleBounds(const struct dtTileCacheObstacle* ob, float* bmin, float* bmax) const;
+	
+
+	/// Encodes a tile id.
+	inline dtCompressedTileRef encodeTileId(unsigned int salt, unsigned int it) const
+	{
+		return ((dtCompressedTileRef)salt << m_tileBits) | (dtCompressedTileRef)it;
+	}
+	
+	/// Decodes a tile salt.
+	inline unsigned int decodeTileIdSalt(dtCompressedTileRef ref) const
+	{
+		const dtCompressedTileRef saltMask = ((dtCompressedTileRef)1<<m_saltBits)-1;
+		return (unsigned int)((ref >> m_tileBits) & saltMask);
+	}
+	
+	/// Decodes a tile id.
+	inline unsigned int decodeTileIdTile(dtCompressedTileRef ref) const
+	{
+		const dtCompressedTileRef tileMask = ((dtCompressedTileRef)1<<m_tileBits)-1;
+		return (unsigned int)(ref & tileMask);
+	}
+
+	/// Encodes an obstacle id.
+	inline dtObstacleRef encodeObstacleId(unsigned int salt, unsigned int it) const
+	{
+		return ((dtObstacleRef)salt << 16) | (dtObstacleRef)it;
+	}
+	
+	/// Decodes an obstacle salt.
+	inline unsigned int decodeObstacleIdSalt(dtObstacleRef ref) const
+	{
+		const dtObstacleRef saltMask = ((dtObstacleRef)1<<16)-1;
+		return (unsigned int)((ref >> 16) & saltMask);
+	}
+	
+	/// Decodes an obstacle id.
+	inline unsigned int decodeObstacleIdObstacle(dtObstacleRef ref) const
+	{
+		const dtObstacleRef tileMask = ((dtObstacleRef)1<<16)-1;
+		return (unsigned int)(ref & tileMask);
+	}
+	
+	
+private:
+	
+	enum ObstacleRequestAction
+	{
+		REQUEST_ADD,
+		REQUEST_REMOVE,
+	};
+	
+	struct ObstacleRequest
+	{
+		int action;
+		dtObstacleRef ref;
+	};
+	
+	int m_tileLutSize;						///< Tile hash lookup size (must be pot).
+	int m_tileLutMask;						///< Tile hash lookup mask.
+	
+	dtCompressedTile** m_posLookup;			///< Tile hash lookup.
+	dtCompressedTile* m_nextFreeTile;		///< Freelist of tiles.
+	dtCompressedTile* m_tiles;				///< List of tiles.
+	
+	unsigned int m_saltBits;				///< Number of salt bits in the tile ID.
+	unsigned int m_tileBits;				///< Number of tile bits in the tile ID.
+	
+	dtTileCacheParams m_params;
+	
+	dtTileCacheAlloc* m_talloc;
+	dtTileCacheCompressor* m_tcomp;
+	dtTileCacheMeshProcess* m_tmproc;
+	
+	dtTileCacheObstacle* m_obstacles;
+	dtTileCacheObstacle* m_nextFreeObstacle;
+	
+	static const int MAX_REQUESTS = 64;
+	ObstacleRequest m_reqs[MAX_REQUESTS];
+	int m_nreqs;
+	
+	static const int MAX_UPDATE = 64;
+	dtCompressedTileRef m_update[MAX_UPDATE];
+	int m_nupdate;
+	
+};
+
+dtTileCache* dtAllocTileCache();
+void dtFreeTileCache(dtTileCache* tc);
+
+#endif

+ 152 - 0
Source/ThirdParty/DetourTileCache/include/DetourTileCacheBuilder.h

@@ -0,0 +1,152 @@
+//
+// Copyright (c) 2009-2010 Mikko Mononen [email protected]
+//
+// 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 DETOURTILECACHEBUILDER_H
+#define DETOURTILECACHEBUILDER_H
+
+#include "DetourAlloc.h"
+#include "DetourStatus.h"
+
+static const int DT_TILECACHE_MAGIC = 'D'<<24 | 'T'<<16 | 'L'<<8 | 'R'; ///< 'DTLR';
+static const int DT_TILECACHE_VERSION = 1;
+
+static const unsigned char DT_TILECACHE_NULL_AREA = 0;
+static const unsigned char DT_TILECACHE_WALKABLE_AREA = 63;
+static const unsigned short DT_TILECACHE_NULL_IDX = 0xffff;
+
+struct dtTileCacheLayerHeader
+{
+	int magic;								///< Data magic
+	int version;							///< Data version
+	int tx,ty,tlayer;
+	float bmin[3], bmax[3];
+	unsigned short hmin, hmax;				///< Height min/max range
+	unsigned char width, height;			///< Dimension of the layer.
+	unsigned char minx, maxx, miny, maxy;	///< Usable sub-region.
+};
+
+struct dtTileCacheLayer
+{
+	dtTileCacheLayerHeader* header;
+	unsigned char regCount;					///< Region count.
+	unsigned char* heights;
+	unsigned char* areas;
+	unsigned char* cons;
+	unsigned char* regs;
+};
+
+struct dtTileCacheContour
+{
+	int nverts;
+	unsigned char* verts;
+	unsigned char reg;
+	unsigned char area;
+};
+
+struct dtTileCacheContourSet
+{
+	int nconts;
+	dtTileCacheContour* conts;
+};
+
+struct dtTileCachePolyMesh
+{
+	int nvp;
+	int nverts;				///< Number of vertices.
+	int npolys;				///< Number of polygons.
+	unsigned short* verts;	///< Vertices of the mesh, 3 elements per vertex.
+	unsigned short* polys;	///< Polygons of the mesh, nvp*2 elements per polygon.
+	unsigned short* flags;	///< Per polygon flags.
+	unsigned char* areas;	///< Area ID of polygons.
+};
+
+
+struct dtTileCacheAlloc
+{
+	virtual ~dtTileCacheAlloc() { }
+
+	virtual void reset()
+	{
+	}
+	
+	virtual void* alloc(const int size)
+	{
+		return dtAlloc(size, DT_ALLOC_TEMP);
+	}
+	
+	virtual void free(void* ptr)
+	{
+		dtFree(ptr);
+	}
+};
+
+struct dtTileCacheCompressor
+{
+	virtual ~dtTileCacheCompressor() { }
+
+	virtual int maxCompressedSize(const int bufferSize) = 0;
+	virtual dtStatus compress(const unsigned char* buffer, const int bufferSize,
+							  unsigned char* compressed, const int maxCompressedSize, int* compressedSize) = 0;
+	virtual dtStatus decompress(const unsigned char* compressed, const int compressedSize,
+								unsigned char* buffer, const int maxBufferSize, int* bufferSize) = 0;
+};
+
+
+dtStatus dtBuildTileCacheLayer(dtTileCacheCompressor* comp,
+							   dtTileCacheLayerHeader* header,
+							   const unsigned char* heights,
+							   const unsigned char* areas,
+							   const unsigned char* cons,
+							   unsigned char** outData, int* outDataSize);
+
+void dtFreeTileCacheLayer(dtTileCacheAlloc* alloc, dtTileCacheLayer* layer);
+
+dtStatus dtDecompressTileCacheLayer(dtTileCacheAlloc* alloc, dtTileCacheCompressor* comp,
+									unsigned char* compressed, const int compressedSize,
+									dtTileCacheLayer** layerOut);
+
+dtTileCacheContourSet* dtAllocTileCacheContourSet(dtTileCacheAlloc* alloc);
+void dtFreeTileCacheContourSet(dtTileCacheAlloc* alloc, dtTileCacheContourSet* cset);
+
+dtTileCachePolyMesh* dtAllocTileCachePolyMesh(dtTileCacheAlloc* alloc);
+void dtFreeTileCachePolyMesh(dtTileCacheAlloc* alloc, dtTileCachePolyMesh* lmesh);
+
+dtStatus dtMarkCylinderArea(dtTileCacheLayer& layer, const float* orig, const float cs, const float ch,
+							const float* pos, const float radius, const float height, const unsigned char areaId);
+
+dtStatus dtBuildTileCacheRegions(dtTileCacheAlloc* alloc,
+								 dtTileCacheLayer& layer,
+								 const int walkableClimb);
+
+dtStatus dtBuildTileCacheContours(dtTileCacheAlloc* alloc,
+								  dtTileCacheLayer& layer,
+								  const int walkableClimb, 	const float maxError,
+								  dtTileCacheContourSet& lcset);
+
+dtStatus dtBuildTileCachePolyMesh(dtTileCacheAlloc* alloc,
+								  dtTileCacheContourSet& lcset,
+								  dtTileCachePolyMesh& mesh);
+
+/// Swaps the endianess of the compressed tile data's header (#dtTileCacheLayerHeader).
+/// Tile layer data does not need endian swapping as it consits only of bytes.
+///  @param[in,out]	data		The tile data array.
+///  @param[in]		dataSize	The size of the data array.
+bool dtTileCacheHeaderSwapEndian(unsigned char* data, const int dataSize);
+
+
+#endif // DETOURTILECACHEBUILDER_H

+ 704 - 0
Source/ThirdParty/DetourTileCache/source/DetourTileCache.cpp

@@ -0,0 +1,704 @@
+#include "DetourTileCache.h"
+#include "DetourTileCacheBuilder.h"
+#include "DetourNavMeshBuilder.h"
+#include "DetourNavMesh.h"
+#include "DetourCommon.h"
+#include "DetourMath.h"
+#include "DetourAlloc.h"
+#include "DetourAssert.h"
+#include <string.h>
+#include <new>
+
+dtTileCache* dtAllocTileCache()
+{
+	void* mem = dtAlloc(sizeof(dtTileCache), DT_ALLOC_PERM);
+	if (!mem) return 0;
+	return new(mem) dtTileCache;
+}
+
+void dtFreeTileCache(dtTileCache* tc)
+{
+	if (!tc) return;
+	tc->~dtTileCache();
+	dtFree(tc);
+}
+
+static bool contains(const dtCompressedTileRef* a, const int n, const dtCompressedTileRef v)
+{
+	for (int i = 0; i < n; ++i)
+		if (a[i] == v)
+			return true;
+	return false;
+}
+
+inline int computeTileHash(int x, int y, const int mask)
+{
+	const unsigned int h1 = 0x8da6b343; // Large multiplicative constants;
+	const unsigned int h2 = 0xd8163841; // here arbitrarily chosen primes
+	unsigned int n = h1 * x + h2 * y;
+	return (int)(n & mask);
+}
+
+
+struct BuildContext
+{
+	inline BuildContext(struct dtTileCacheAlloc* a) : layer(0), lcset(0), lmesh(0), alloc(a) {}
+	inline ~BuildContext() { purge(); }
+	void purge()
+	{
+		dtFreeTileCacheLayer(alloc, layer);
+		layer = 0;
+		dtFreeTileCacheContourSet(alloc, lcset);
+		lcset = 0;
+		dtFreeTileCachePolyMesh(alloc, lmesh);
+		lmesh = 0;
+	}
+	struct dtTileCacheLayer* layer;
+	struct dtTileCacheContourSet* lcset;
+	struct dtTileCachePolyMesh* lmesh;
+	struct dtTileCacheAlloc* alloc;
+};
+
+
+dtTileCache::dtTileCache() :
+	m_tileLutSize(0),
+	m_tileLutMask(0),
+	m_posLookup(0),
+	m_nextFreeTile(0),	
+	m_tiles(0),	
+	m_saltBits(0),
+	m_tileBits(0),
+	m_talloc(0),
+	m_tcomp(0),
+	m_tmproc(0),
+	m_obstacles(0),
+	m_nextFreeObstacle(0),
+	m_nreqs(0),
+	m_nupdate(0)
+{
+	memset(&m_params, 0, sizeof(m_params));
+}
+	
+dtTileCache::~dtTileCache()
+{
+	for (int i = 0; i < m_params.maxTiles; ++i)
+	{
+		if (m_tiles[i].flags & DT_COMPRESSEDTILE_FREE_DATA)
+		{
+			dtFree(m_tiles[i].data);
+			m_tiles[i].data = 0;
+		}
+	}
+	dtFree(m_obstacles);
+	m_obstacles = 0;
+	dtFree(m_posLookup);
+	m_posLookup = 0;
+	dtFree(m_tiles);
+	m_tiles = 0;
+	m_nreqs = 0;
+	m_nupdate = 0;
+}
+
+const dtCompressedTile* dtTileCache::getTileByRef(dtCompressedTileRef ref) const
+{
+	if (!ref)
+		return 0;
+	unsigned int tileIndex = decodeTileIdTile(ref);
+	unsigned int tileSalt = decodeTileIdSalt(ref);
+	if ((int)tileIndex >= m_params.maxTiles)
+		return 0;
+	const dtCompressedTile* tile = &m_tiles[tileIndex];
+	if (tile->salt != tileSalt)
+		return 0;
+	return tile;
+}
+
+
+dtStatus dtTileCache::init(const dtTileCacheParams* params,
+						   dtTileCacheAlloc* talloc,
+						   dtTileCacheCompressor* tcomp,
+						   dtTileCacheMeshProcess* tmproc)
+{
+	m_talloc = talloc;
+	m_tcomp = tcomp;
+	m_tmproc = tmproc;
+	m_nreqs = 0;
+	memcpy(&m_params, params, sizeof(m_params));
+	
+	// Alloc space for obstacles.
+	m_obstacles = (dtTileCacheObstacle*)dtAlloc(sizeof(dtTileCacheObstacle)*m_params.maxObstacles, DT_ALLOC_PERM);
+	if (!m_obstacles)
+		return DT_FAILURE | DT_OUT_OF_MEMORY;
+	memset(m_obstacles, 0, sizeof(dtTileCacheObstacle)*m_params.maxObstacles);
+	m_nextFreeObstacle = 0;
+	for (int i = m_params.maxObstacles-1; i >= 0; --i)
+	{
+		m_obstacles[i].salt = 1;
+		m_obstacles[i].next = m_nextFreeObstacle;
+		m_nextFreeObstacle = &m_obstacles[i];
+	}
+	
+	// Init tiles
+	m_tileLutSize = dtNextPow2(m_params.maxTiles/4);
+	if (!m_tileLutSize) m_tileLutSize = 1;
+	m_tileLutMask = m_tileLutSize-1;
+	
+	m_tiles = (dtCompressedTile*)dtAlloc(sizeof(dtCompressedTile)*m_params.maxTiles, DT_ALLOC_PERM);
+	if (!m_tiles)
+		return DT_FAILURE | DT_OUT_OF_MEMORY;
+	m_posLookup = (dtCompressedTile**)dtAlloc(sizeof(dtCompressedTile*)*m_tileLutSize, DT_ALLOC_PERM);
+	if (!m_posLookup)
+		return DT_FAILURE | DT_OUT_OF_MEMORY;
+	memset(m_tiles, 0, sizeof(dtCompressedTile)*m_params.maxTiles);
+	memset(m_posLookup, 0, sizeof(dtCompressedTile*)*m_tileLutSize);
+	m_nextFreeTile = 0;
+	for (int i = m_params.maxTiles-1; i >= 0; --i)
+	{
+		m_tiles[i].salt = 1;
+		m_tiles[i].next = m_nextFreeTile;
+		m_nextFreeTile = &m_tiles[i];
+	}
+	
+	// Init ID generator values.
+	m_tileBits = dtIlog2(dtNextPow2((unsigned int)m_params.maxTiles));
+	// Only allow 31 salt bits, since the salt mask is calculated using 32bit uint and it will overflow.
+	m_saltBits = dtMin((unsigned int)31, 32 - m_tileBits);
+	if (m_saltBits < 10)
+		return DT_FAILURE | DT_INVALID_PARAM;
+	
+	return DT_SUCCESS;
+}
+
+int dtTileCache::getTilesAt(const int tx, const int ty, dtCompressedTileRef* tiles, const int maxTiles) const 
+{
+	int n = 0;
+	
+	// Find tile based on hash.
+	int h = computeTileHash(tx,ty,m_tileLutMask);
+	dtCompressedTile* tile = m_posLookup[h];
+	while (tile)
+	{
+		if (tile->header &&
+			tile->header->tx == tx &&
+			tile->header->ty == ty)
+		{
+			if (n < maxTiles)
+				tiles[n++] = getTileRef(tile);
+		}
+		tile = tile->next;
+	}
+	
+	return n;
+}
+
+dtCompressedTile* dtTileCache::getTileAt(const int tx, const int ty, const int tlayer)
+{
+	// Find tile based on hash.
+	int h = computeTileHash(tx,ty,m_tileLutMask);
+	dtCompressedTile* tile = m_posLookup[h];
+	while (tile)
+	{
+		if (tile->header &&
+			tile->header->tx == tx &&
+			tile->header->ty == ty &&
+			tile->header->tlayer == tlayer)
+		{
+			return tile;
+		}
+		tile = tile->next;
+	}
+	return 0;
+}
+
+dtCompressedTileRef dtTileCache::getTileRef(const dtCompressedTile* tile) const
+{
+	if (!tile) return 0;
+	const unsigned int it = (unsigned int)(tile - m_tiles);
+	return (dtCompressedTileRef)encodeTileId(tile->salt, it);
+}
+
+dtObstacleRef dtTileCache::getObstacleRef(const dtTileCacheObstacle* ob) const
+{
+	if (!ob) return 0;
+	const unsigned int idx = (unsigned int)(ob - m_obstacles);
+	return encodeObstacleId(ob->salt, idx);
+}
+
+const dtTileCacheObstacle* dtTileCache::getObstacleByRef(dtObstacleRef ref)
+{
+	if (!ref)
+		return 0;
+	unsigned int idx = decodeObstacleIdObstacle(ref);
+	if ((int)idx >= m_params.maxObstacles)
+		return 0;
+	const dtTileCacheObstacle* ob = &m_obstacles[idx];
+	unsigned int salt = decodeObstacleIdSalt(ref);
+	if (ob->salt != salt)
+		return 0;
+	return ob;
+}
+
+dtStatus dtTileCache::addTile(unsigned char* data, const int dataSize, unsigned char flags, dtCompressedTileRef* result)
+{
+	// Make sure the data is in right format.
+	dtTileCacheLayerHeader* header = (dtTileCacheLayerHeader*)data;
+	if (header->magic != DT_TILECACHE_MAGIC)
+		return DT_FAILURE | DT_WRONG_MAGIC;
+	if (header->version != DT_TILECACHE_VERSION)
+		return DT_FAILURE | DT_WRONG_VERSION;
+	
+	// Make sure the location is free.
+	if (getTileAt(header->tx, header->ty, header->tlayer))
+		return DT_FAILURE;
+	
+	// Allocate a tile.
+	dtCompressedTile* tile = 0;
+	if (m_nextFreeTile)
+	{
+		tile = m_nextFreeTile;
+		m_nextFreeTile = tile->next;
+		tile->next = 0;
+	}
+	
+	// Make sure we could allocate a tile.
+	if (!tile)
+		return DT_FAILURE | DT_OUT_OF_MEMORY;
+	
+	// Insert tile into the position lut.
+	int h = computeTileHash(header->tx, header->ty, m_tileLutMask);
+	tile->next = m_posLookup[h];
+	m_posLookup[h] = tile;
+	
+	// Init tile.
+	const int headerSize = dtAlign4(sizeof(dtTileCacheLayerHeader));
+	tile->header = (dtTileCacheLayerHeader*)data;
+	tile->data = data;
+	tile->dataSize = dataSize;
+	tile->compressed = tile->data + headerSize;
+	tile->compressedSize = tile->dataSize - headerSize;
+	tile->flags = flags;
+	
+	if (result)
+		*result = getTileRef(tile);
+	
+	return DT_SUCCESS;
+}
+
+dtStatus dtTileCache::removeTile(dtCompressedTileRef ref, unsigned char** data, int* dataSize)
+{
+	if (!ref)
+		return DT_FAILURE | DT_INVALID_PARAM;
+	unsigned int tileIndex = decodeTileIdTile(ref);
+	unsigned int tileSalt = decodeTileIdSalt(ref);
+	if ((int)tileIndex >= m_params.maxTiles)
+		return DT_FAILURE | DT_INVALID_PARAM;
+	dtCompressedTile* tile = &m_tiles[tileIndex];
+	if (tile->salt != tileSalt)
+		return DT_FAILURE | DT_INVALID_PARAM;
+	
+	// Remove tile from hash lookup.
+	const int h = computeTileHash(tile->header->tx,tile->header->ty,m_tileLutMask);
+	dtCompressedTile* prev = 0;
+	dtCompressedTile* cur = m_posLookup[h];
+	while (cur)
+	{
+		if (cur == tile)
+		{
+			if (prev)
+				prev->next = cur->next;
+			else
+				m_posLookup[h] = cur->next;
+			break;
+		}
+		prev = cur;
+		cur = cur->next;
+	}
+	
+	// Reset tile.
+	if (tile->flags & DT_COMPRESSEDTILE_FREE_DATA)
+	{
+		// Owns data
+		dtFree(tile->data);
+		tile->data = 0;
+		tile->dataSize = 0;
+		if (data) *data = 0;
+		if (dataSize) *dataSize = 0;
+	}
+	else
+	{
+		if (data) *data = tile->data;
+		if (dataSize) *dataSize = tile->dataSize;
+	}
+	
+	tile->header = 0;
+	tile->data = 0;
+	tile->dataSize = 0;
+	tile->compressed = 0;
+	tile->compressedSize = 0;
+	tile->flags = 0;
+	
+	// Update salt, salt should never be zero.
+	tile->salt = (tile->salt+1) & ((1<<m_saltBits)-1);
+	if (tile->salt == 0)
+		tile->salt++;
+	
+	// Add to free list.
+	tile->next = m_nextFreeTile;
+	m_nextFreeTile = tile;
+	
+	return DT_SUCCESS;
+}
+
+
+dtObstacleRef dtTileCache::addObstacle(const float* pos, const float radius, const float height, dtObstacleRef* result)
+{
+	if (m_nreqs >= MAX_REQUESTS)
+		return DT_FAILURE | DT_BUFFER_TOO_SMALL;
+	
+	dtTileCacheObstacle* ob = 0;
+	if (m_nextFreeObstacle)
+	{
+		ob = m_nextFreeObstacle;
+		m_nextFreeObstacle = ob->next;
+		ob->next = 0;
+	}
+	if (!ob)
+		return DT_FAILURE | DT_OUT_OF_MEMORY;
+	
+	unsigned short salt = ob->salt;
+	memset(ob, 0, sizeof(dtTileCacheObstacle));
+	ob->salt = salt;
+	ob->state = DT_OBSTACLE_PROCESSING;
+	dtVcopy(ob->pos, pos);
+	ob->radius = radius;
+	ob->height = height;
+	
+	ObstacleRequest* req = &m_reqs[m_nreqs++];
+	memset(req, 0, sizeof(ObstacleRequest));
+	req->action = REQUEST_ADD;
+	req->ref = getObstacleRef(ob);
+	
+	if (result)
+		*result = req->ref;
+	
+	return DT_SUCCESS;
+}
+
+dtObstacleRef dtTileCache::removeObstacle(const dtObstacleRef ref)
+{
+	if (!ref)
+		return DT_SUCCESS;
+	if (m_nreqs >= MAX_REQUESTS)
+		return DT_FAILURE | DT_BUFFER_TOO_SMALL;
+	
+	ObstacleRequest* req = &m_reqs[m_nreqs++];
+	memset(req, 0, sizeof(ObstacleRequest));
+	req->action = REQUEST_REMOVE;
+	req->ref = ref;
+	
+	return DT_SUCCESS;
+}
+
+dtStatus dtTileCache::queryTiles(const float* bmin, const float* bmax,
+								 dtCompressedTileRef* results, int* resultCount, const int maxResults) const 
+{
+	const int MAX_TILES = 32;
+	dtCompressedTileRef tiles[MAX_TILES];
+	
+	int n = 0;
+	
+	const float tw = m_params.width * m_params.cs;
+	const float th = m_params.height * m_params.cs;
+	const int tx0 = (int)dtMathFloorf((bmin[0]-m_params.orig[0]) / tw);
+	const int tx1 = (int)dtMathFloorf((bmax[0]-m_params.orig[0]) / tw);
+	const int ty0 = (int)dtMathFloorf((bmin[2]-m_params.orig[2]) / th);
+	const int ty1 = (int)dtMathFloorf((bmax[2]-m_params.orig[2]) / th);
+	
+	for (int ty = ty0; ty <= ty1; ++ty)
+	{
+		for (int tx = tx0; tx <= tx1; ++tx)
+		{
+			const int ntiles = getTilesAt(tx,ty,tiles,MAX_TILES);
+			
+			for (int i = 0; i < ntiles; ++i)
+			{
+				const dtCompressedTile* tile = &m_tiles[decodeTileIdTile(tiles[i])];
+				float tbmin[3], tbmax[3];
+				calcTightTileBounds(tile->header, tbmin, tbmax);
+				
+				if (dtOverlapBounds(bmin,bmax, tbmin,tbmax))
+				{
+					if (n < maxResults)
+						results[n++] = tiles[i];
+				}
+			}
+		}
+	}
+	
+	*resultCount = n;
+	
+	return DT_SUCCESS;
+}
+
+dtStatus dtTileCache::update(const float /*dt*/, dtNavMesh* navmesh)
+{
+	if (m_nupdate == 0)
+	{
+		// Process requests.
+		for (int i = 0; i < m_nreqs; ++i)
+		{
+			ObstacleRequest* req = &m_reqs[i];
+			
+			unsigned int idx = decodeObstacleIdObstacle(req->ref);
+			if ((int)idx >= m_params.maxObstacles)
+				continue;
+			dtTileCacheObstacle* ob = &m_obstacles[idx];
+			unsigned int salt = decodeObstacleIdSalt(req->ref);
+			if (ob->salt != salt)
+				continue;
+			
+			if (req->action == REQUEST_ADD)
+			{
+				// Find touched tiles.
+				float bmin[3], bmax[3];
+				getObstacleBounds(ob, bmin, bmax);
+
+				int ntouched = 0;
+				queryTiles(bmin, bmax, ob->touched, &ntouched, DT_MAX_TOUCHED_TILES);
+				ob->ntouched = (unsigned char)ntouched;
+				// Add tiles to update list.
+				ob->npending = 0;
+				for (int j = 0; j < ob->ntouched; ++j)
+				{
+					if (m_nupdate < MAX_UPDATE)
+					{
+						if (!contains(m_update, m_nupdate, ob->touched[j]))
+							m_update[m_nupdate++] = ob->touched[j];
+						ob->pending[ob->npending++] = ob->touched[j];
+					}
+				}
+			}
+			else if (req->action == REQUEST_REMOVE)
+			{
+				// Prepare to remove obstacle.
+				ob->state = DT_OBSTACLE_REMOVING;
+				// Add tiles to update list.
+				ob->npending = 0;
+				for (int j = 0; j < ob->ntouched; ++j)
+				{
+					if (m_nupdate < MAX_UPDATE)
+					{
+						if (!contains(m_update, m_nupdate, ob->touched[j]))
+							m_update[m_nupdate++] = ob->touched[j];
+						ob->pending[ob->npending++] = ob->touched[j];
+					}
+				}
+			}
+		}
+		
+		m_nreqs = 0;
+	}
+	
+	// Process updates
+	if (m_nupdate)
+	{
+		// Build mesh
+		const dtCompressedTileRef ref = m_update[0];
+		dtStatus status = buildNavMeshTile(ref, navmesh);
+		m_nupdate--;
+		if (m_nupdate > 0)
+			memmove(m_update, m_update+1, m_nupdate*sizeof(dtCompressedTileRef));
+
+		// Update obstacle states.
+		for (int i = 0; i < m_params.maxObstacles; ++i)
+		{
+			dtTileCacheObstacle* ob = &m_obstacles[i];
+			if (ob->state == DT_OBSTACLE_PROCESSING || ob->state == DT_OBSTACLE_REMOVING)
+			{
+				// Remove handled tile from pending list.
+				for (int j = 0; j < (int)ob->npending; j++)
+				{
+					if (ob->pending[j] == ref)
+					{
+						ob->pending[j] = ob->pending[(int)ob->npending-1];
+						ob->npending--;
+						break;
+					}
+				}
+				
+				// If all pending tiles processed, change state.
+				if (ob->npending == 0)
+				{
+					if (ob->state == DT_OBSTACLE_PROCESSING)
+					{
+						ob->state = DT_OBSTACLE_PROCESSED;
+					}
+					else if (ob->state == DT_OBSTACLE_REMOVING)
+					{
+						ob->state = DT_OBSTACLE_EMPTY;
+						// Update salt, salt should never be zero.
+						ob->salt = (ob->salt+1) & ((1<<16)-1);
+						if (ob->salt == 0)
+							ob->salt++;
+						// Return obstacle to free list.
+						ob->next = m_nextFreeObstacle;
+						m_nextFreeObstacle = ob;
+					}
+				}
+			}
+		}
+			
+		if (dtStatusFailed(status))
+			return status;
+	}
+	
+	return DT_SUCCESS;
+}
+
+
+dtStatus dtTileCache::buildNavMeshTilesAt(const int tx, const int ty, dtNavMesh* navmesh)
+{
+	const int MAX_TILES = 32;
+	dtCompressedTileRef tiles[MAX_TILES];
+	const int ntiles = getTilesAt(tx,ty,tiles,MAX_TILES);
+	
+	for (int i = 0; i < ntiles; ++i)
+	{
+		dtStatus status = buildNavMeshTile(tiles[i], navmesh);
+		if (dtStatusFailed(status))
+			return status;
+	}
+	
+	return DT_SUCCESS;
+}
+
+dtStatus dtTileCache::buildNavMeshTile(const dtCompressedTileRef ref, dtNavMesh* navmesh)
+{	
+	dtAssert(m_talloc);
+	dtAssert(m_tcomp);
+	
+	unsigned int idx = decodeTileIdTile(ref);
+	if (idx > (unsigned int)m_params.maxTiles)
+		return DT_FAILURE | DT_INVALID_PARAM;
+	const dtCompressedTile* tile = &m_tiles[idx];
+	unsigned int salt = decodeTileIdSalt(ref);
+	if (tile->salt != salt)
+		return DT_FAILURE | DT_INVALID_PARAM;
+	
+	m_talloc->reset();
+	
+	BuildContext bc(m_talloc);
+	const int walkableClimbVx = (int)(m_params.walkableClimb / m_params.ch);
+	dtStatus status;
+	
+	// Decompress tile layer data. 
+	status = dtDecompressTileCacheLayer(m_talloc, m_tcomp, tile->data, tile->dataSize, &bc.layer);
+	if (dtStatusFailed(status))
+		return status;
+	
+	// Rasterize obstacles.
+	for (int i = 0; i < m_params.maxObstacles; ++i)
+	{
+		const dtTileCacheObstacle* ob = &m_obstacles[i];
+		if (ob->state == DT_OBSTACLE_EMPTY || ob->state == DT_OBSTACLE_REMOVING)
+			continue;
+		if (contains(ob->touched, ob->ntouched, ref))
+		{
+			dtMarkCylinderArea(*bc.layer, tile->header->bmin, m_params.cs, m_params.ch,
+							   ob->pos, ob->radius, ob->height, 0);
+		}
+	}
+	
+	// Build navmesh
+	status = dtBuildTileCacheRegions(m_talloc, *bc.layer, walkableClimbVx);
+	if (dtStatusFailed(status))
+		return status;
+	
+	bc.lcset = dtAllocTileCacheContourSet(m_talloc);
+	if (!bc.lcset)
+		return status;
+	status = dtBuildTileCacheContours(m_talloc, *bc.layer, walkableClimbVx,
+									  m_params.maxSimplificationError, *bc.lcset);
+	if (dtStatusFailed(status))
+		return status;
+	
+	bc.lmesh = dtAllocTileCachePolyMesh(m_talloc);
+	if (!bc.lmesh)
+		return status;
+	status = dtBuildTileCachePolyMesh(m_talloc, *bc.lcset, *bc.lmesh);
+	if (dtStatusFailed(status))
+		return status;
+	
+	// Early out if the mesh tile is empty.
+	if (!bc.lmesh->npolys)
+		return DT_SUCCESS;
+	
+	dtNavMeshCreateParams params;
+	memset(&params, 0, sizeof(params));
+	params.verts = bc.lmesh->verts;
+	params.vertCount = bc.lmesh->nverts;
+	params.polys = bc.lmesh->polys;
+	params.polyAreas = bc.lmesh->areas;
+	params.polyFlags = bc.lmesh->flags;
+	params.polyCount = bc.lmesh->npolys;
+	params.nvp = DT_VERTS_PER_POLYGON;
+	params.walkableHeight = m_params.walkableHeight;
+	params.walkableRadius = m_params.walkableRadius;
+	params.walkableClimb = m_params.walkableClimb;
+	params.tileX = tile->header->tx;
+	params.tileY = tile->header->ty;
+	params.tileLayer = tile->header->tlayer;
+	params.cs = m_params.cs;
+	params.ch = m_params.ch;
+	params.buildBvTree = false;
+	dtVcopy(params.bmin, tile->header->bmin);
+	dtVcopy(params.bmax, tile->header->bmax);
+	
+	if (m_tmproc)
+	{
+		m_tmproc->process(&params, bc.lmesh->areas, bc.lmesh->flags);
+	}
+	
+	unsigned char* navData = 0;
+	int navDataSize = 0;
+	if (!dtCreateNavMeshData(&params, &navData, &navDataSize))
+		return DT_FAILURE;
+
+	// Remove existing tile.
+	navmesh->removeTile(navmesh->getTileRefAt(tile->header->tx,tile->header->ty,tile->header->tlayer),0,0);
+
+	// Add new tile, or leave the location empty.
+	if (navData)
+	{
+		// Let the navmesh own the data.
+		status = navmesh->addTile(navData,navDataSize,DT_TILE_FREE_DATA,0,0);
+		if (dtStatusFailed(status))
+		{
+			dtFree(navData);
+			return status;
+		}
+	}
+	
+	return DT_SUCCESS;
+}
+
+void dtTileCache::calcTightTileBounds(const dtTileCacheLayerHeader* header, float* bmin, float* bmax) const
+{
+	const float cs = m_params.cs;
+	bmin[0] = header->bmin[0] + header->minx*cs;
+	bmin[1] = header->bmin[1];
+	bmin[2] = header->bmin[2] + header->miny*cs;
+	bmax[0] = header->bmin[0] + (header->maxx+1)*cs;
+	bmax[1] = header->bmax[1];
+	bmax[2] = header->bmin[2] + (header->maxy+1)*cs;
+}
+
+void dtTileCache::getObstacleBounds(const struct dtTileCacheObstacle* ob, float* bmin, float* bmax) const
+{
+	bmin[0] = ob->pos[0] - ob->radius;
+	bmin[1] = ob->pos[1];
+	bmin[2] = ob->pos[2] - ob->radius;
+	bmax[0] = ob->pos[0] + ob->radius;
+	bmax[1] = ob->pos[1] + ob->height;
+	bmax[2] = ob->pos[2] + ob->radius;	
+}

+ 2151 - 0
Source/ThirdParty/DetourTileCache/source/DetourTileCacheBuilder.cpp

@@ -0,0 +1,2151 @@
+//
+// Copyright (c) 2009-2010 Mikko Mononen [email protected]
+//
+// 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 "DetourCommon.h"
+#include "DetourMath.h"
+#include "DetourStatus.h"
+#include "DetourAssert.h"
+#include "DetourTileCacheBuilder.h"
+#include <string.h>
+
+
+template<class T> class dtFixedArray
+{
+	dtTileCacheAlloc* m_alloc;
+	T* m_ptr;
+	const int m_size;
+	inline T* operator=(T* p);
+	inline void operator=(dtFixedArray<T>& p);
+	inline dtFixedArray();
+public:
+	inline dtFixedArray(dtTileCacheAlloc* a, const int s) : m_alloc(a), m_ptr((T*)a->alloc(sizeof(T)*s)), m_size(s) {}
+	inline ~dtFixedArray() { if (m_alloc) m_alloc->free(m_ptr); }
+	inline operator T*() { return m_ptr; }
+	inline int size() const { return m_size; }
+};
+
+inline int getDirOffsetX(int dir)
+{
+	const int offset[4] = { -1, 0, 1, 0, };
+	return offset[dir&0x03];
+}
+
+inline int getDirOffsetY(int dir)
+{
+	const int offset[4] = { 0, 1, 0, -1 };
+	return offset[dir&0x03];
+}
+
+static const int MAX_VERTS_PER_POLY = 6;	// TODO: use the DT_VERTS_PER_POLYGON
+static const int MAX_REM_EDGES = 48;		// TODO: make this an expression.
+
+
+
+dtTileCacheContourSet* dtAllocTileCacheContourSet(dtTileCacheAlloc* alloc)
+{
+	dtAssert(alloc);
+
+	dtTileCacheContourSet* cset = (dtTileCacheContourSet*)alloc->alloc(sizeof(dtTileCacheContourSet));
+	memset(cset, 0, sizeof(dtTileCacheContourSet));
+	return cset;
+}
+
+void dtFreeTileCacheContourSet(dtTileCacheAlloc* alloc, dtTileCacheContourSet* cset)
+{
+	dtAssert(alloc);
+
+	if (!cset) return;
+	for (int i = 0; i < cset->nconts; ++i)
+		alloc->free(cset->conts[i].verts);
+	alloc->free(cset->conts);
+	alloc->free(cset);
+}
+
+dtTileCachePolyMesh* dtAllocTileCachePolyMesh(dtTileCacheAlloc* alloc)
+{
+	dtAssert(alloc);
+
+	dtTileCachePolyMesh* lmesh = (dtTileCachePolyMesh*)alloc->alloc(sizeof(dtTileCachePolyMesh));
+	memset(lmesh, 0, sizeof(dtTileCachePolyMesh));
+	return lmesh;
+}
+
+void dtFreeTileCachePolyMesh(dtTileCacheAlloc* alloc, dtTileCachePolyMesh* lmesh)
+{
+	dtAssert(alloc);
+	
+	if (!lmesh) return;
+	alloc->free(lmesh->verts);
+	alloc->free(lmesh->polys);
+	alloc->free(lmesh->flags);
+	alloc->free(lmesh->areas);
+	alloc->free(lmesh);
+}
+
+
+
+struct dtLayerSweepSpan
+{
+	unsigned short ns;	// number samples
+	unsigned char id;	// region id
+	unsigned char nei;	// neighbour id
+};
+
+static const int DT_LAYER_MAX_NEIS = 16;
+
+struct dtLayerMonotoneRegion
+{
+	int area;
+	unsigned char neis[DT_LAYER_MAX_NEIS];
+	unsigned char nneis;
+	unsigned char regId;
+	unsigned char areaId;
+};
+
+struct dtTempContour
+{
+	inline dtTempContour(unsigned char* vbuf, const int nvbuf,
+						 unsigned short* pbuf, const int npbuf) :
+		verts(vbuf), nverts(0), cverts(nvbuf),
+		poly(pbuf), npoly(0), cpoly(npbuf) 
+	{
+	}
+	unsigned char* verts;
+	int nverts;
+	int cverts;
+	unsigned short* poly;
+	int npoly;
+	int cpoly;
+};
+
+
+
+
+inline bool overlapRangeExl(const unsigned short amin, const unsigned short amax,
+							const unsigned short bmin, const unsigned short bmax)
+{
+	return (amin >= bmax || amax <= bmin) ? false : true;
+}
+
+static void addUniqueLast(unsigned char* a, unsigned char& an, unsigned char v)
+{
+	const int n = (int)an;
+	if (n > 0 && a[n-1] == v) return;
+	a[an] = v;
+	an++;
+}
+
+inline bool isConnected(const dtTileCacheLayer& layer,
+						const int ia, const int ib, const int walkableClimb)
+{
+	if (layer.areas[ia] != layer.areas[ib]) return false;
+	if (dtAbs((int)layer.heights[ia] - (int)layer.heights[ib]) > walkableClimb) return false;
+	return true;
+}
+
+static bool canMerge(unsigned char oldRegId, unsigned char newRegId, const dtLayerMonotoneRegion* regs, const int nregs)
+{
+	int count = 0;
+	for (int i = 0; i < nregs; ++i)
+	{
+		const dtLayerMonotoneRegion& reg = regs[i];
+		if (reg.regId != oldRegId) continue;
+		const int nnei = (int)reg.nneis;
+		for (int j = 0; j < nnei; ++j)
+		{
+			if (regs[reg.neis[j]].regId == newRegId)
+				count++;
+		}
+	}
+	return count == 1;
+}
+
+
+dtStatus dtBuildTileCacheRegions(dtTileCacheAlloc* alloc,
+								 dtTileCacheLayer& layer,
+								 const int walkableClimb)
+{
+	dtAssert(alloc);
+	
+	const int w = (int)layer.header->width;
+	const int h = (int)layer.header->height;
+	
+	memset(layer.regs,0xff,sizeof(unsigned char)*w*h);
+	
+	const int nsweeps = w;
+	dtFixedArray<dtLayerSweepSpan> sweeps(alloc, nsweeps);
+	if (!sweeps)
+		return DT_FAILURE | DT_OUT_OF_MEMORY;
+	memset(sweeps,0,sizeof(dtLayerSweepSpan)*nsweeps);
+	
+	// Partition walkable area into monotone regions.
+	unsigned char prevCount[256];
+	unsigned char regId = 0;
+	
+	for (int y = 0; y < h; ++y)
+	{
+		if (regId > 0)
+			memset(prevCount,0,sizeof(unsigned char)*regId);
+		unsigned char sweepId = 0;
+		
+		for (int x = 0; x < w; ++x)
+		{
+			const int idx = x + y*w;
+			if (layer.areas[idx] == DT_TILECACHE_NULL_AREA) continue;
+			
+			unsigned char sid = 0xff;
+			
+			// -x
+			const int xidx = (x-1)+y*w;
+			if (x > 0 && isConnected(layer, idx, xidx, walkableClimb))
+			{
+				if (layer.regs[xidx] != 0xff)
+					sid = layer.regs[xidx];
+			}
+			
+			if (sid == 0xff)
+			{
+				sid = sweepId++;
+				sweeps[sid].nei = 0xff;
+				sweeps[sid].ns = 0;
+			}
+			
+			// -y
+			const int yidx = x+(y-1)*w;
+			if (y > 0 && isConnected(layer, idx, yidx, walkableClimb))
+			{
+				const unsigned char nr = layer.regs[yidx];
+				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;
+					}
+				}
+			}
+			
+			layer.regs[idx] = 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 && (unsigned short)prevCount[sweeps[i].nei] == sweeps[i].ns)
+			{
+				sweeps[i].id = sweeps[i].nei;
+			}
+			else
+			{
+				if (regId == 255)
+				{
+					// Region ID's overflow.
+					return DT_FAILURE | DT_BUFFER_TOO_SMALL;
+				}
+				sweeps[i].id = regId++;
+			}
+		}
+		
+		// Remap local sweep ids to region ids.
+		for (int x = 0; x < w; ++x)
+		{
+			const int idx = x+y*w;
+			if (layer.regs[idx] != 0xff)
+				layer.regs[idx] = sweeps[layer.regs[idx]].id;
+		}
+	}
+	
+	// Allocate and init layer regions.
+	const int nregs = (int)regId;
+	dtFixedArray<dtLayerMonotoneRegion> regs(alloc, nregs);
+	if (!regs)
+		return DT_FAILURE | DT_OUT_OF_MEMORY;
+
+	memset(regs, 0, sizeof(dtLayerMonotoneRegion)*nregs);
+	for (int i = 0; i < nregs; ++i)
+		regs[i].regId = 0xff;
+	
+	// Find region neighbours.
+	for (int y = 0; y < h; ++y)
+	{
+		for (int x = 0; x < w; ++x)
+		{
+			const int idx = x+y*w;
+			const unsigned char ri = layer.regs[idx];
+			if (ri == 0xff)
+				continue;
+			
+			// Update area.
+			regs[ri].area++;
+			regs[ri].areaId = layer.areas[idx];
+			
+			// Update neighbours
+			const int ymi = x+(y-1)*w;
+			if (y > 0 && isConnected(layer, idx, ymi, walkableClimb))
+			{
+				const unsigned char rai = layer.regs[ymi];
+				if (rai != 0xff && rai != ri)
+				{
+					addUniqueLast(regs[ri].neis, regs[ri].nneis, rai);
+					addUniqueLast(regs[rai].neis, regs[rai].nneis, ri);
+				}
+			}
+		}
+	}
+	
+	for (int i = 0; i < nregs; ++i)
+		regs[i].regId = (unsigned char)i;
+	
+	for (int i = 0; i < nregs; ++i)
+	{
+		dtLayerMonotoneRegion& reg = regs[i];
+		
+		int merge = -1;
+		int mergea = 0;
+		for (int j = 0; j < (int)reg.nneis; ++j)
+		{
+			const unsigned char nei = reg.neis[j];
+			dtLayerMonotoneRegion& regn = regs[nei];
+			if (reg.regId == regn.regId)
+				continue;
+			if (reg.areaId != regn.areaId)
+				continue;
+			if (regn.area > mergea)
+			{
+				if (canMerge(reg.regId, regn.regId, regs, nregs))
+				{
+					mergea = regn.area;
+					merge = (int)nei;
+				}
+			}
+		}
+		if (merge != -1)
+		{
+			const unsigned char oldId = reg.regId;
+			const unsigned char newId = regs[merge].regId;
+			for (int j = 0; j < nregs; ++j)
+				if (regs[j].regId == oldId)
+					regs[j].regId = newId;
+		}
+	}
+	
+	// Compact ids.
+	unsigned char remap[256];
+	memset(remap, 0, 256);
+	// Find number of unique regions.
+	regId = 0;
+	for (int i = 0; i < nregs; ++i)
+		remap[regs[i].regId] = 1;
+	for (int i = 0; i < 256; ++i)
+		if (remap[i])
+			remap[i] = regId++;
+	// Remap ids.
+	for (int i = 0; i < nregs; ++i)
+		regs[i].regId = remap[regs[i].regId];
+	
+	layer.regCount = regId;
+	
+	for (int i = 0; i < w*h; ++i)
+	{
+		if (layer.regs[i] != 0xff)
+			layer.regs[i] = regs[layer.regs[i]].regId;
+	}
+	
+	return DT_SUCCESS;
+}
+
+
+
+static bool appendVertex(dtTempContour& cont, const int x, const int y, const int z, const int r)
+{
+	// Try to merge with existing segments.
+	if (cont.nverts > 1)
+	{
+		unsigned char* pa = &cont.verts[(cont.nverts-2)*4];
+		unsigned char* pb = &cont.verts[(cont.nverts-1)*4];
+		if ((int)pb[3] == r)
+		{
+			if (pa[0] == pb[0] && (int)pb[0] == x)
+			{
+				// The verts are aligned aling x-axis, update z.
+				pb[1] = (unsigned char)y;
+				pb[2] = (unsigned char)z;
+				return true;
+			}
+			else if (pa[2] == pb[2] && (int)pb[2] == z)
+			{
+				// The verts are aligned aling z-axis, update x.
+				pb[0] = (unsigned char)x;
+				pb[1] = (unsigned char)y;
+				return true;
+			}
+		}
+	}
+	
+	// Add new point.
+	if (cont.nverts+1 > cont.cverts)
+		return false;
+	
+	unsigned char* v = &cont.verts[cont.nverts*4];
+	v[0] = (unsigned char)x;
+	v[1] = (unsigned char)y;
+	v[2] = (unsigned char)z;
+	v[3] = (unsigned char)r;
+	cont.nverts++;
+	
+	return true;
+}
+
+
+static unsigned char getNeighbourReg(dtTileCacheLayer& layer,
+									 const int ax, const int ay, const int dir)
+{
+	const int w = (int)layer.header->width;
+	const int ia = ax + ay*w;
+	
+	const unsigned char con = layer.cons[ia] & 0xf;
+	const unsigned char portal = layer.cons[ia] >> 4;
+	const unsigned char mask = (unsigned char)(1<<dir);
+	
+	if ((con & mask) == 0)
+	{
+		// No connection, return portal or hard edge.
+		if (portal & mask)
+			return 0xf8 + (unsigned char)dir;
+		return 0xff;
+	}
+	
+	const int bx = ax + getDirOffsetX(dir);
+	const int by = ay + getDirOffsetY(dir);
+	const int ib = bx + by*w;
+	
+	return layer.regs[ib];
+}
+
+static bool walkContour(dtTileCacheLayer& layer, int x, int y, dtTempContour& cont)
+{
+	const int w = (int)layer.header->width;
+	const int h = (int)layer.header->height;
+	
+	cont.nverts = 0;
+	
+	int startX = x;
+	int startY = y;
+	int startDir = -1;
+	
+	for (int i = 0; i < 4; ++i)
+	{
+		const int dir = (i+3)&3;
+		unsigned char rn = getNeighbourReg(layer, x, y, dir);
+		if (rn != layer.regs[x+y*w])
+		{
+			startDir = dir;
+			break;
+		}
+	}
+	if (startDir == -1)
+		return true;
+	
+	int dir = startDir;
+	const int maxIter = w*h;
+	
+	int iter = 0;
+	while (iter < maxIter)
+	{
+		unsigned char rn = getNeighbourReg(layer, x, y, dir);
+		
+		int nx = x;
+		int ny = y;
+		int ndir = dir;
+		
+		if (rn != layer.regs[x+y*w])
+		{
+			// Solid edge.
+			int px = x;
+			int pz = y;
+			switch(dir)
+			{
+				case 0: pz++; break;
+				case 1: px++; pz++; break;
+				case 2: px++; break;
+			}
+			
+			// Try to merge with previous vertex.
+			if (!appendVertex(cont, px, (int)layer.heights[x+y*w], pz,rn))
+				return false;
+			
+			ndir = (dir+1) & 0x3;  // Rotate CW
+		}
+		else
+		{
+			// Move to next.
+			nx = x + getDirOffsetX(dir);
+			ny = y + getDirOffsetY(dir);
+			ndir = (dir+3) & 0x3;	// Rotate CCW
+		}
+		
+		if (iter > 0 && x == startX && y == startY && dir == startDir)
+			break;
+		
+		x = nx;
+		y = ny;
+		dir = ndir;
+		
+		iter++;
+	}
+	
+	// Remove last vertex if it is duplicate of the first one.
+	unsigned char* pa = &cont.verts[(cont.nverts-1)*4];
+	unsigned char* pb = &cont.verts[0];
+	if (pa[0] == pb[0] && pa[2] == pb[2])
+		cont.nverts--;
+	
+	return true;
+}	
+
+
+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 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(dtTempContour& cont, const float maxError)
+{
+	cont.npoly = 0;
+	
+	for (int i = 0; i < cont.nverts; ++i)
+	{
+		int j = (i+1) % cont.nverts;
+		// Check for start of a wall segment.
+		unsigned char ra = cont.verts[j*4+3];
+		unsigned char rb = cont.verts[i*4+3];
+		if (ra != rb)
+			cont.poly[cont.npoly++] = (unsigned short)i;
+	}
+	if (cont.npoly < 2)
+	{
+		// If there is no transitions at all,
+		// create some initial points for the simplification process. 
+		// Find lower-left and upper-right vertices of the contour.
+		int llx = cont.verts[0];
+		int llz = cont.verts[2];
+		int lli = 0;
+		int urx = cont.verts[0];
+		int urz = cont.verts[2];
+		int uri = 0;
+		for (int i = 1; i < cont.nverts; ++i)
+		{
+			int x = cont.verts[i*4+0];
+			int z = cont.verts[i*4+2];
+			if (x < llx || (x == llx && z < llz))
+			{
+				llx = x;
+				llz = z;
+				lli = i;
+			}
+			if (x > urx || (x == urx && z > urz))
+			{
+				urx = x;
+				urz = z;
+				uri = i;
+			}
+		}
+		cont.npoly = 0;
+		cont.poly[cont.npoly++] = (unsigned short)lli;
+		cont.poly[cont.npoly++] = (unsigned short)uri;
+	}
+	
+	// Add points until all raw points are within
+	// error tolerance to the simplified shape.
+	for (int i = 0; i < cont.npoly; )
+	{
+		int ii = (i+1) % cont.npoly;
+		
+		const int ai = (int)cont.poly[i];
+		const int ax = (int)cont.verts[ai*4+0];
+		const int az = (int)cont.verts[ai*4+2];
+		
+		const int bi = (int)cont.poly[ii];
+		const int bx = (int)cont.verts[bi*4+0];
+		const int bz = (int)cont.verts[bi*4+2];
+		
+		// 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) % cont.nverts;
+			endi = bi;
+		}
+		else
+		{
+			cinc = cont.nverts-1;
+			ci = (bi+cinc) % cont.nverts;
+			endi = ai;
+		}
+		
+		// Tessellate only outer edges or edges between areas.
+		while (ci != endi)
+		{
+			float d = distancePtSeg(cont.verts[ci*4+0], cont.verts[ci*4+2], ax, az, bx, bz);
+			if (d > maxd)
+			{
+				maxd = d;
+				maxi = ci;
+			}
+			ci = (ci+cinc) % cont.nverts;
+		}
+		
+		
+		// If the max deviation is larger than accepted error,
+		// add new point, else continue to next segment.
+		if (maxi != -1 && maxd > (maxError*maxError))
+		{
+			cont.npoly++;
+			for (int j = cont.npoly-1; j > i; --j)
+				cont.poly[j] = cont.poly[j-1];
+			cont.poly[i+1] = (unsigned short)maxi;
+		}
+		else
+		{
+			++i;
+		}
+	}
+	
+	// Remap vertices
+	int start = 0;
+	for (int i = 1; i < cont.npoly; ++i)
+		if (cont.poly[i] < cont.poly[start])
+			start = i;
+	
+	cont.nverts = 0;
+	for (int i = 0; i < cont.npoly; ++i)
+	{
+		const int j = (start+i) % cont.npoly;
+		unsigned char* src = &cont.verts[cont.poly[j]*4];
+		unsigned char* dst = &cont.verts[cont.nverts*4];
+		dst[0] = src[0];
+		dst[1] = src[1];
+		dst[2] = src[2];
+		dst[3] = src[3];
+		cont.nverts++;
+	}
+}
+
+static unsigned char getCornerHeight(dtTileCacheLayer& layer,
+									 const int x, const int y, const int z,
+									 const int walkableClimb,
+									 bool& shouldRemove)
+{
+	const int w = (int)layer.header->width;
+	const int h = (int)layer.header->height;
+	
+	int n = 0;
+	
+	unsigned char portal = 0xf;
+	unsigned char height = 0;
+	unsigned char preg = 0xff;
+	bool allSameReg = true;
+	
+	for (int dz = -1; dz <= 0; ++dz)
+	{
+		for (int dx = -1; dx <= 0; ++dx)
+		{
+			const int px = x+dx;
+			const int pz = z+dz;
+			if (px >= 0 && pz >= 0 && px < w && pz < h)
+			{
+				const int idx  = px + pz*w;
+				const int lh = (int)layer.heights[idx];
+				if (dtAbs(lh-y) <= walkableClimb && layer.areas[idx] != DT_TILECACHE_NULL_AREA)
+				{
+					height = dtMax(height, (unsigned char)lh);
+					portal &= (layer.cons[idx] >> 4);
+					if (preg != 0xff && preg != layer.regs[idx])
+						allSameReg = false;
+					preg = layer.regs[idx]; 
+					n++;
+				}
+			}
+		}
+	}
+	
+	int portalCount = 0;
+	for (int dir = 0; dir < 4; ++dir)
+		if (portal & (1<<dir))
+			portalCount++;
+	
+	shouldRemove = false;
+	if (n > 1 && portalCount == 1 && allSameReg)
+	{
+		shouldRemove = true;
+	}
+	
+	return height;
+}
+
+
+// TODO: move this somewhere else, once the layer meshing is done.
+dtStatus dtBuildTileCacheContours(dtTileCacheAlloc* alloc,
+								  dtTileCacheLayer& layer,
+								  const int walkableClimb, 	const float maxError,
+								  dtTileCacheContourSet& lcset)
+{
+	dtAssert(alloc);
+
+	const int w = (int)layer.header->width;
+	const int h = (int)layer.header->height;
+	
+	lcset.nconts = layer.regCount;
+	lcset.conts = (dtTileCacheContour*)alloc->alloc(sizeof(dtTileCacheContour)*lcset.nconts);
+	if (!lcset.conts)
+		return DT_FAILURE | DT_OUT_OF_MEMORY;
+	memset(lcset.conts, 0, sizeof(dtTileCacheContour)*lcset.nconts);
+	
+	// Allocate temp buffer for contour tracing.
+	const int maxTempVerts = (w+h)*2 * 2; // Twice around the layer.
+	
+	dtFixedArray<unsigned char> tempVerts(alloc, maxTempVerts*4);
+	if (!tempVerts)
+		return DT_FAILURE | DT_OUT_OF_MEMORY;
+	
+	dtFixedArray<unsigned short> tempPoly(alloc, maxTempVerts);
+	if (!tempPoly)
+		return DT_FAILURE | DT_OUT_OF_MEMORY;
+
+	dtTempContour temp(tempVerts, maxTempVerts, tempPoly, maxTempVerts);
+	
+	// Find contours.
+	for (int y = 0; y < h; ++y)
+	{
+		for (int x = 0; x < w; ++x)
+		{
+			const int idx = x+y*w;
+			const unsigned char ri = layer.regs[idx];
+			if (ri == 0xff)
+				continue;
+			
+			dtTileCacheContour& cont = lcset.conts[ri];
+			
+			if (cont.nverts > 0)
+				continue;
+			
+			cont.reg = ri;
+			cont.area = layer.areas[idx];
+			
+			if (!walkContour(layer, x, y, temp))
+			{
+				// Too complex contour.
+				// Note: If you hit here ofte, try increasing 'maxTempVerts'.
+				return DT_FAILURE | DT_BUFFER_TOO_SMALL;
+			}
+			
+			simplifyContour(temp, maxError);
+			
+			// Store contour.
+			cont.nverts = temp.nverts;
+			if (cont.nverts > 0)
+			{
+				cont.verts = (unsigned char*)alloc->alloc(sizeof(unsigned char)*4*temp.nverts);
+				if (!cont.verts)
+					return DT_FAILURE | DT_OUT_OF_MEMORY;
+				
+				for (int i = 0, j = temp.nverts-1; i < temp.nverts; j=i++)
+				{
+					unsigned char* dst = &cont.verts[j*4];
+					unsigned char* v = &temp.verts[j*4];
+					unsigned char* vn = &temp.verts[i*4];
+					unsigned char nei = vn[3]; // The neighbour reg is stored at segment vertex of a segment. 
+					bool shouldRemove = false;
+					unsigned char lh = getCornerHeight(layer, (int)v[0], (int)v[1], (int)v[2],
+													   walkableClimb, shouldRemove);
+					
+					dst[0] = v[0];
+					dst[1] = lh;
+					dst[2] = v[2];
+					
+					// Store portal direction and remove status to the fourth component.
+					dst[3] = 0x0f;
+					if (nei != 0xff && nei >= 0xf8)
+						dst[3] = nei - 0xf8;
+					if (shouldRemove)
+						dst[3] |= 0x80;
+				}
+			}
+		}
+	}
+	
+	return DT_SUCCESS;
+}	
+
+
+
+static const int VERTEX_BUCKET_COUNT2 = (1<<8);
+
+inline int computeVertexHash2(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_COUNT2-1));
+}
+
+static unsigned short addVertex(unsigned short x, unsigned short y, unsigned short z,
+								unsigned short* verts, unsigned short* firstVert, unsigned short* nextVert, int& nv)
+{
+	int bucket = computeVertexHash2(x, 0, z);
+	unsigned short i = firstVert[bucket];
+	
+	while (i != DT_TILECACHE_NULL_IDX)
+	{
+		const unsigned short* v = &verts[i*3];
+		if (v[0] == x && v[2] == z && (dtAbs(v[1] - y) <= 2))
+			return i;
+		i = nextVert[i]; // next
+	}
+	
+	// Could not find, create new.
+	i = (unsigned short)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;
+}
+
+
+struct rcEdge
+{
+	unsigned short vert[2];
+	unsigned short polyEdge[2];
+	unsigned short poly[2];
+};
+
+static bool buildMeshAdjacency(dtTileCacheAlloc* alloc,
+							   unsigned short* polys, const int npolys,
+							   const unsigned short* verts, const int nverts,
+							   const dtTileCacheContourSet& lcset)
+{
+	// Based on code by Eric Lengyel from:
+	// http://www.terathon.com/code/edges.php
+	
+	const int maxEdgeCount = npolys*MAX_VERTS_PER_POLY;
+	dtFixedArray<unsigned short> firstEdge(alloc, nverts + maxEdgeCount);
+	if (!firstEdge)
+		return false;
+	unsigned short* nextEdge = firstEdge + nverts;
+	int edgeCount = 0;
+	
+	dtFixedArray<rcEdge> edges(alloc, maxEdgeCount);
+	if (!edges)
+		return false;
+	
+	for (int i = 0; i < nverts; i++)
+		firstEdge[i] = DT_TILECACHE_NULL_IDX;
+	
+	for (int i = 0; i < npolys; ++i)
+	{
+		unsigned short* t = &polys[i*MAX_VERTS_PER_POLY*2];
+		for (int j = 0; j < MAX_VERTS_PER_POLY; ++j)
+		{
+			if (t[j] == DT_TILECACHE_NULL_IDX) break;
+			unsigned short v0 = t[j];
+			unsigned short v1 = (j+1 >= MAX_VERTS_PER_POLY || t[j+1] == DT_TILECACHE_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] = 0xff;
+				// Insert edge
+				nextEdge[edgeCount] = firstEdge[v0];
+				firstEdge[v0] = (unsigned short)edgeCount;
+				edgeCount++;
+			}
+		}
+	}
+	
+	for (int i = 0; i < npolys; ++i)
+	{
+		unsigned short* t = &polys[i*MAX_VERTS_PER_POLY*2];
+		for (int j = 0; j < MAX_VERTS_PER_POLY; ++j)
+		{
+			if (t[j] == DT_TILECACHE_NULL_IDX) break;
+			unsigned short v0 = t[j];
+			unsigned short v1 = (j+1 >= MAX_VERTS_PER_POLY || t[j+1] == DT_TILECACHE_NULL_IDX) ? t[0] : t[j+1];
+			if (v0 > v1)
+			{
+				bool found = false;
+				for (unsigned short e = firstEdge[v1]; e != DT_TILECACHE_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;
+						found = true;
+						break;
+					}
+				}
+				if (!found)
+				{
+					// Matching edge not found, it is an open edge, add it.
+					rcEdge& edge = edges[edgeCount];
+					edge.vert[0] = v1;
+					edge.vert[1] = v0;
+					edge.poly[0] = (unsigned short)i;
+					edge.polyEdge[0] = (unsigned short)j;
+					edge.poly[1] = (unsigned short)i;
+					edge.polyEdge[1] = 0xff;
+					// Insert edge
+					nextEdge[edgeCount] = firstEdge[v1];
+					firstEdge[v1] = (unsigned short)edgeCount;
+					edgeCount++;
+				}
+			}
+		}
+	}
+	
+	// Mark portal edges.
+	for (int i = 0; i < lcset.nconts; ++i)
+	{
+		dtTileCacheContour& cont = lcset.conts[i];
+		if (cont.nverts < 3)
+			continue;
+		
+		for (int j = 0, k = cont.nverts-1; j < cont.nverts; k=j++)
+		{
+			const unsigned char* va = &cont.verts[k*4];
+			const unsigned char* vb = &cont.verts[j*4];
+			const unsigned char dir = va[3] & 0xf;
+			if (dir == 0xf)
+				continue;
+			
+			if (dir == 0 || dir == 2)
+			{
+				// Find matching vertical edge
+				const unsigned short x = (unsigned short)va[0];
+				unsigned short zmin = (unsigned short)va[2];
+				unsigned short zmax = (unsigned short)vb[2];
+				if (zmin > zmax)
+					dtSwap(zmin, zmax);
+				
+				for (int m = 0; m < edgeCount; ++m)
+				{
+					rcEdge& e = edges[m];
+					// Skip connected edges.
+					if (e.poly[0] != e.poly[1])
+						continue;
+					const unsigned short* eva = &verts[e.vert[0]*3];
+					const unsigned short* evb = &verts[e.vert[1]*3];
+					if (eva[0] == x && evb[0] == x)
+					{
+						unsigned short ezmin = eva[2];
+						unsigned short ezmax = evb[2];
+						if (ezmin > ezmax)
+							dtSwap(ezmin, ezmax);
+						if (overlapRangeExl(zmin,zmax, ezmin, ezmax))
+						{
+							// Reuse the other polyedge to store dir.
+							e.polyEdge[1] = dir;
+						}
+					}
+				}
+			}
+			else
+			{
+				// Find matching vertical edge
+				const unsigned short z = (unsigned short)va[2];
+				unsigned short xmin = (unsigned short)va[0];
+				unsigned short xmax = (unsigned short)vb[0];
+				if (xmin > xmax)
+					dtSwap(xmin, xmax);
+				for (int m = 0; m < edgeCount; ++m)
+				{
+					rcEdge& e = edges[m];
+					// Skip connected edges.
+					if (e.poly[0] != e.poly[1])
+						continue;
+					const unsigned short* eva = &verts[e.vert[0]*3];
+					const unsigned short* evb = &verts[e.vert[1]*3];
+					if (eva[2] == z && evb[2] == z)
+					{
+						unsigned short exmin = eva[0];
+						unsigned short exmax = evb[0];
+						if (exmin > exmax)
+							dtSwap(exmin, exmax);
+						if (overlapRangeExl(xmin,xmax, exmin, exmax))
+						{
+							// Reuse the other polyedge to store dir.
+							e.polyEdge[1] = dir;
+						}
+					}
+				}
+			}
+		}
+	}
+	
+	
+	// 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]*MAX_VERTS_PER_POLY*2];
+			unsigned short* p1 = &polys[e.poly[1]*MAX_VERTS_PER_POLY*2];
+			p0[MAX_VERTS_PER_POLY + e.polyEdge[0]] = e.poly[1];
+			p1[MAX_VERTS_PER_POLY + e.polyEdge[1]] = e.poly[0];
+		}
+		else if (e.polyEdge[1] != 0xff)
+		{
+			unsigned short* p0 = &polys[e.poly[0]*MAX_VERTS_PER_POLY*2];
+			p0[MAX_VERTS_PER_POLY + e.polyEdge[0]] = 0x8000 | (unsigned short)e.polyEdge[1];
+		}
+		
+	}
+	
+	return true;
+}
+
+
+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 unsigned char* a, const unsigned char* b, const unsigned char* 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]);
+}
+
+//	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 unsigned char* a, const unsigned char* b, const unsigned char* c)
+{
+	return area2(a, b, c) < 0;
+}
+
+inline bool leftOn(const unsigned char* a, const unsigned char* b, const unsigned char* c)
+{
+	return area2(a, b, c) <= 0;
+}
+
+inline bool collinear(const unsigned char* a, const unsigned char* b, const unsigned char* 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 unsigned char* a, const unsigned char* b,
+						  const unsigned char* c, const unsigned char* 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 unsigned char* a, const unsigned char* b, const unsigned char* 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 unsigned char* a, const unsigned char* b,
+					  const unsigned char* c, const unsigned char* 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 unsigned char* a, const unsigned char* 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 unsigned char* verts, const unsigned short* indices)
+{
+	const unsigned char* d0 = &verts[(indices[i] & 0x7fff) * 4];
+	const unsigned char* d1 = &verts[(indices[j] & 0x7fff) * 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 unsigned char* p0 = &verts[(indices[k] & 0x7fff) * 4];
+			const unsigned char* p1 = &verts[(indices[k1] & 0x7fff) * 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 unsigned char* verts, const unsigned short* indices)
+{
+	const unsigned char* pi = &verts[(indices[i] & 0x7fff) * 4];
+	const unsigned char* pj = &verts[(indices[j] & 0x7fff) * 4];
+	const unsigned char* pi1 = &verts[(indices[next(i, n)] & 0x7fff) * 4];
+	const unsigned char* pin1 = &verts[(indices[prev(i, n)] & 0x7fff) * 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 unsigned char* verts, const unsigned short* indices)
+{
+	return inCone(i, j, n, verts, indices) && diagonalie(i, j, n, verts, indices);
+}
+
+static int triangulate(int n, const unsigned char* verts, unsigned short* indices, unsigned short* tris)
+{
+	int ntris = 0;
+	unsigned short* 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] |= 0x8000;
+	}
+	
+	while (n > 3)
+	{
+		int minLen = -1;
+		int mini = -1;
+		for (int i = 0; i < n; i++)
+		{
+			int i1 = next(i, n);
+			if (indices[i1] & 0x8000)
+			{
+				const unsigned char* p0 = &verts[(indices[i] & 0x7fff) * 4];
+				const unsigned char* p2 = &verts[(indices[next(i1, n)] & 0x7fff) * 4];
+				
+				const int dx = (int)p2[0] - (int)p0[0];
+				const int dz = (int)p2[2] - (int)p0[2];
+				const int len = dx*dx + dz*dz;
+				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] & 0x7fff;
+		*dst++ = indices[i1] & 0x7fff;
+		*dst++ = indices[i2] & 0x7fff;
+		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] |= 0x8000;
+		else
+			indices[i] &= 0x7fff;
+		
+		if (diagonal(i, next(i1, n), n, verts, indices))
+			indices[i1] |= 0x8000;
+		else
+			indices[i1] &= 0x7fff;
+	}
+	
+	// Append the remaining triangle.
+	*dst++ = indices[0] & 0x7fff;
+	*dst++ = indices[1] & 0x7fff;
+	*dst++ = indices[2] & 0x7fff;
+	ntris++;
+	
+	return ntris;
+}
+
+
+static int countPolyVerts(const unsigned short* p)
+{
+	for (int i = 0; i < MAX_VERTS_PER_POLY; ++i)
+		if (p[i] == DT_TILECACHE_NULL_IDX)
+			return i;
+	return MAX_VERTS_PER_POLY;
+}
+
+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 na = countPolyVerts(pa);
+	const int nb = countPolyVerts(pb);
+	
+	// If the merged polygon would be too big, do not merge.
+	if (na+nb-2 > MAX_VERTS_PER_POLY)
+		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)
+			dtSwap(va0, va1);
+		for (int j = 0; j < nb; ++j)
+		{
+			unsigned short vb0 = pb[j];
+			unsigned short vb1 = pb[(j+1) % nb];
+			if (vb0 > vb1)
+				dtSwap(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[MAX_VERTS_PER_POLY*2];
+	
+	const int na = countPolyVerts(pa);
+	const int nb = countPolyVerts(pb);
+	
+	// Merge polygons.
+	memset(tmp, 0xff, sizeof(unsigned short)*MAX_VERTS_PER_POLY*2);
+	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)*MAX_VERTS_PER_POLY);
+}
+
+
+static void pushFront(unsigned short v, unsigned short* arr, int& an)
+{
+	an++;
+	for (int i = an-1; i > 0; --i)
+		arr[i] = arr[i-1];
+	arr[0] = v;
+}
+
+static void pushBack(unsigned short v, unsigned short* arr, int& an)
+{
+	arr[an] = v;
+	an++;
+}
+
+static bool canRemoveVertex(dtTileCachePolyMesh& mesh, const unsigned short rem)
+{
+	// 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*MAX_VERTS_PER_POLY*2];
+		const int nv = countPolyVerts(p);
+		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;
+	
+	// Check that there is enough memory for the test.
+	const int maxEdges = numTouchedVerts*2;
+	if (maxEdges > MAX_REM_EDGES)
+		return false;
+	
+	// Find edges which share the removed vertex.
+	unsigned short edges[MAX_REM_EDGES];
+	int nedges = 0;
+	
+	for (int i = 0; i < mesh.npolys; ++i)
+	{
+		unsigned short* p = &mesh.polys[i*MAX_VERTS_PER_POLY*2];
+		const int nv = countPolyVerts(p);
+		
+		// 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)
+					dtSwap(a,b);
+				
+				// Check if the edge exists
+				bool exists = false;
+				for (int m = 0; m < nedges; ++m)
+				{
+					unsigned short* e = &edges[m*3];
+					if (e[1] == b)
+					{
+						// Exists, increment vertex share count.
+						e[2]++;
+						exists = true;
+					}
+				}
+				// Add new edge.
+				if (!exists)
+				{
+					unsigned short* e = &edges[nedges*3];
+					e[0] = (unsigned short)a;
+					e[1] = (unsigned short)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 dtStatus removeVertex(dtTileCachePolyMesh& mesh, const unsigned short rem, const int maxTris)
+{
+	// Count number of polygons to remove.
+	int numRemovedVerts = 0;
+	for (int i = 0; i < mesh.npolys; ++i)
+	{
+		unsigned short* p = &mesh.polys[i*MAX_VERTS_PER_POLY*2];
+		const int nv = countPolyVerts(p);
+		for (int j = 0; j < nv; ++j)
+		{
+			if (p[j] == rem)
+				numRemovedVerts++;
+		}
+	}
+	
+	int nedges = 0;
+	unsigned short edges[MAX_REM_EDGES*3];
+	int nhole = 0;
+	unsigned short hole[MAX_REM_EDGES];
+	int nharea = 0;
+	unsigned short harea[MAX_REM_EDGES];
+	
+	for (int i = 0; i < mesh.npolys; ++i)
+	{
+		unsigned short* p = &mesh.polys[i*MAX_VERTS_PER_POLY*2];
+		const int nv = countPolyVerts(p);
+		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)
+				{
+					if (nedges >= MAX_REM_EDGES)
+						return DT_FAILURE | DT_BUFFER_TOO_SMALL;
+					unsigned short* e = &edges[nedges*3];
+					e[0] = p[k];
+					e[1] = p[j];
+					e[2] = mesh.areas[i];
+					nedges++;
+				}
+			}
+			// Remove the polygon.
+			unsigned short* p2 = &mesh.polys[(mesh.npolys-1)*MAX_VERTS_PER_POLY*2];
+			memcpy(p,p2,sizeof(unsigned short)*MAX_VERTS_PER_POLY);
+			memset(p+MAX_VERTS_PER_POLY,0xff,sizeof(unsigned short)*MAX_VERTS_PER_POLY);
+			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*MAX_VERTS_PER_POLY*2];
+		const int nv = countPolyVerts(p);
+		for (int j = 0; j < nv; ++j)
+			if (p[j] > rem) p[j]--;
+	}
+	for (int i = 0; i < nedges; ++i)
+	{
+		if (edges[i*3+0] > rem) edges[i*3+0]--;
+		if (edges[i*3+1] > rem) edges[i*3+1]--;
+	}
+	
+	if (nedges == 0)
+		return DT_SUCCESS;
+	
+	// Start with one vertex, keep appending connected
+	// segments to the start and end of the hole.
+	pushBack(edges[0], hole, nhole);
+	pushBack(edges[2], harea, nharea);
+	
+	while (nedges)
+	{
+		bool match = false;
+		
+		for (int i = 0; i < nedges; ++i)
+		{
+			const unsigned short ea = edges[i*3+0];
+			const unsigned short eb = edges[i*3+1];
+			const unsigned short a = edges[i*3+2];
+			bool add = false;
+			if (hole[0] == eb)
+			{
+				// The segment matches the beginning of the hole boundary.
+				if (nhole >= MAX_REM_EDGES)
+					return DT_FAILURE | DT_BUFFER_TOO_SMALL;
+				pushFront(ea, hole, nhole);
+				pushFront(a, harea, nharea);
+				add = true;
+			}
+			else if (hole[nhole-1] == ea)
+			{
+				// The segment matches the end of the hole boundary.
+				if (nhole >= MAX_REM_EDGES)
+					return DT_FAILURE | DT_BUFFER_TOO_SMALL;
+				pushBack(eb, hole, nhole);
+				pushBack(a, harea, nharea);
+				add = true;
+			}
+			if (add)
+			{
+				// The edge segment was added, remove it.
+				edges[i*3+0] = edges[(nedges-1)*3+0];
+				edges[i*3+1] = edges[(nedges-1)*3+1];
+				edges[i*3+2] = edges[(nedges-1)*3+2];
+				--nedges;
+				match = true;
+				--i;
+			}
+		}
+		
+		if (!match)
+			break;
+	}
+	
+	
+	unsigned short tris[MAX_REM_EDGES*3];
+	unsigned char tverts[MAX_REM_EDGES*3];
+	unsigned short tpoly[MAX_REM_EDGES*3];
+	
+	// Generate temp vertex array for triangulation.
+	for (int i = 0; i < nhole; ++i)
+	{
+		const unsigned short pi = hole[i];
+		tverts[i*4+0] = (unsigned char)mesh.verts[pi*3+0];
+		tverts[i*4+1] = (unsigned char)mesh.verts[pi*3+1];
+		tverts[i*4+2] = (unsigned char)mesh.verts[pi*3+2];
+		tverts[i*4+3] = 0;
+		tpoly[i] = (unsigned short)i;
+	}
+	
+	// Triangulate the hole.
+	int ntris = triangulate(nhole, tverts, tpoly, tris);
+	if (ntris < 0)
+	{
+		// TODO: issue warning!
+		ntris = -ntris;
+	}
+	
+	if (ntris > MAX_REM_EDGES)
+		return DT_FAILURE | DT_BUFFER_TOO_SMALL;
+	
+	unsigned short polys[MAX_REM_EDGES*MAX_VERTS_PER_POLY];
+	unsigned char pareas[MAX_REM_EDGES];
+	
+	// Build initial polygons.
+	int npolys = 0;
+	memset(polys, 0xff, ntris*MAX_VERTS_PER_POLY*sizeof(unsigned short));
+	for (int j = 0; j < ntris; ++j)
+	{
+		unsigned short* t = &tris[j*3];
+		if (t[0] != t[1] && t[0] != t[2] && t[1] != t[2])
+		{
+			polys[npolys*MAX_VERTS_PER_POLY+0] = hole[t[0]];
+			polys[npolys*MAX_VERTS_PER_POLY+1] = hole[t[1]];
+			polys[npolys*MAX_VERTS_PER_POLY+2] = hole[t[2]];
+			pareas[npolys] = (unsigned char)harea[t[0]];
+			npolys++;
+		}
+	}
+	if (!npolys)
+		return DT_SUCCESS;
+	
+	// Merge polygons.
+	int maxVertsPerPoly = MAX_VERTS_PER_POLY;
+	if (maxVertsPerPoly > 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*MAX_VERTS_PER_POLY];
+				for (int k = j+1; k < npolys; ++k)
+				{
+					unsigned short* pk = &polys[k*MAX_VERTS_PER_POLY];
+					int ea, eb;
+					int v = getPolyMergeValue(pj, pk, mesh.verts, ea, eb);
+					if (v > bestMergeVal)
+					{
+						bestMergeVal = v;
+						bestPa = j;
+						bestPb = k;
+						bestEa = ea;
+						bestEb = eb;
+					}
+				}
+			}
+			
+			if (bestMergeVal > 0)
+			{
+				// Found best, merge.
+				unsigned short* pa = &polys[bestPa*MAX_VERTS_PER_POLY];
+				unsigned short* pb = &polys[bestPb*MAX_VERTS_PER_POLY];
+				mergePolys(pa, pb, bestEa, bestEb);
+				memcpy(pb, &polys[(npolys-1)*MAX_VERTS_PER_POLY], sizeof(unsigned short)*MAX_VERTS_PER_POLY);
+				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*MAX_VERTS_PER_POLY*2];
+		memset(p,0xff,sizeof(unsigned short)*MAX_VERTS_PER_POLY*2);
+		for (int j = 0; j < MAX_VERTS_PER_POLY; ++j)
+			p[j] = polys[i*MAX_VERTS_PER_POLY+j];
+		mesh.areas[mesh.npolys] = pareas[i];
+		mesh.npolys++;
+		if (mesh.npolys > maxTris)
+			return DT_FAILURE | DT_BUFFER_TOO_SMALL;
+	}
+	
+	return DT_SUCCESS;
+}
+
+
+dtStatus dtBuildTileCachePolyMesh(dtTileCacheAlloc* alloc,
+								  dtTileCacheContourSet& lcset,
+								  dtTileCachePolyMesh& mesh)
+{
+	dtAssert(alloc);
+	
+	int maxVertices = 0;
+	int maxTris = 0;
+	int maxVertsPerCont = 0;
+	for (int i = 0; i < lcset.nconts; ++i)
+	{
+		// Skip null contours.
+		if (lcset.conts[i].nverts < 3) continue;
+		maxVertices += lcset.conts[i].nverts;
+		maxTris += lcset.conts[i].nverts - 2;
+		maxVertsPerCont = dtMax(maxVertsPerCont, lcset.conts[i].nverts);
+	}
+
+	// TODO: warn about too many vertices?
+	
+	mesh.nvp = MAX_VERTS_PER_POLY;
+	
+	dtFixedArray<unsigned char> vflags(alloc, maxVertices);
+	if (!vflags)
+		return DT_FAILURE | DT_OUT_OF_MEMORY;
+	memset(vflags, 0, maxVertices);
+	
+	mesh.verts = (unsigned short*)alloc->alloc(sizeof(unsigned short)*maxVertices*3);
+	if (!mesh.verts)
+		return DT_FAILURE | DT_OUT_OF_MEMORY;
+	
+	mesh.polys = (unsigned short*)alloc->alloc(sizeof(unsigned short)*maxTris*MAX_VERTS_PER_POLY*2);
+	if (!mesh.polys)
+		return DT_FAILURE | DT_OUT_OF_MEMORY;
+
+	mesh.areas = (unsigned char*)alloc->alloc(sizeof(unsigned char)*maxTris);
+	if (!mesh.areas)
+		return DT_FAILURE | DT_OUT_OF_MEMORY;
+
+	mesh.flags = (unsigned short*)alloc->alloc(sizeof(unsigned short)*maxTris);
+	if (!mesh.flags)
+		return DT_FAILURE | DT_OUT_OF_MEMORY;
+
+	// Just allocate and clean the mesh flags array. The user is resposible for filling it.
+	memset(mesh.flags, 0, sizeof(unsigned short) * maxTris);
+		
+	mesh.nverts = 0;
+	mesh.npolys = 0;
+	
+	memset(mesh.verts, 0, sizeof(unsigned short)*maxVertices*3);
+	memset(mesh.polys, 0xff, sizeof(unsigned short)*maxTris*MAX_VERTS_PER_POLY*2);
+	memset(mesh.areas, 0, sizeof(unsigned char)*maxTris);
+	
+	unsigned short firstVert[VERTEX_BUCKET_COUNT2];
+	for (int i = 0; i < VERTEX_BUCKET_COUNT2; ++i)
+		firstVert[i] = DT_TILECACHE_NULL_IDX;
+	
+	dtFixedArray<unsigned short> nextVert(alloc, maxVertices);
+	if (!nextVert)
+		return DT_FAILURE | DT_OUT_OF_MEMORY;
+	memset(nextVert, 0, sizeof(unsigned short)*maxVertices);
+	
+	dtFixedArray<unsigned short> indices(alloc, maxVertsPerCont);
+	if (!indices)
+		return DT_FAILURE | DT_OUT_OF_MEMORY;
+	
+	dtFixedArray<unsigned short> tris(alloc, maxVertsPerCont*3);
+	if (!tris)
+		return DT_FAILURE | DT_OUT_OF_MEMORY;
+
+	dtFixedArray<unsigned short> polys(alloc, maxVertsPerCont*MAX_VERTS_PER_POLY);
+	if (!polys)
+		return DT_FAILURE | DT_OUT_OF_MEMORY;
+	
+	for (int i = 0; i < lcset.nconts; ++i)
+	{
+		dtTileCacheContour& cont = lcset.conts[i];
+		
+		// Skip null contours.
+		if (cont.nverts < 3)
+			continue;
+		
+		// Triangulate contour
+		for (int j = 0; j < cont.nverts; ++j)
+			indices[j] = (unsigned short)j;
+		
+		int ntris = triangulate(cont.nverts, cont.verts, &indices[0], &tris[0]);
+		if (ntris <= 0)
+		{
+			// TODO: issue warning!
+			ntris = -ntris;
+		}
+		
+		// Add and merge vertices.
+		for (int j = 0; j < cont.nverts; ++j)
+		{
+			const unsigned char* 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] & 0x80)
+			{
+				// This vertex should be removed.
+				vflags[indices[j]] = 1;
+			}
+		}
+		
+		// Build initial polygons.
+		int npolys = 0;
+		memset(polys, 0xff, sizeof(unsigned short) * maxVertsPerCont * MAX_VERTS_PER_POLY);
+		for (int j = 0; j < ntris; ++j)
+		{
+			const unsigned short* t = &tris[j*3];
+			if (t[0] != t[1] && t[0] != t[2] && t[1] != t[2])
+			{
+				polys[npolys*MAX_VERTS_PER_POLY+0] = indices[t[0]];
+				polys[npolys*MAX_VERTS_PER_POLY+1] = indices[t[1]];
+				polys[npolys*MAX_VERTS_PER_POLY+2] = indices[t[2]];
+				npolys++;
+			}
+		}
+		if (!npolys)
+			continue;
+		
+		// Merge polygons.
+		int maxVertsPerPoly =MAX_VERTS_PER_POLY ;
+		if (maxVertsPerPoly > 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*MAX_VERTS_PER_POLY];
+					for (int k = j+1; k < npolys; ++k)
+					{
+						unsigned short* pk = &polys[k*MAX_VERTS_PER_POLY];
+						int ea, eb;
+						int v = getPolyMergeValue(pj, pk, mesh.verts, ea, eb);
+						if (v > bestMergeVal)
+						{
+							bestMergeVal = v;
+							bestPa = j;
+							bestPb = k;
+							bestEa = ea;
+							bestEb = eb;
+						}
+					}
+				}
+				
+				if (bestMergeVal > 0)
+				{
+					// Found best, merge.
+					unsigned short* pa = &polys[bestPa*MAX_VERTS_PER_POLY];
+					unsigned short* pb = &polys[bestPb*MAX_VERTS_PER_POLY];
+					mergePolys(pa, pb, bestEa, bestEb);
+					memcpy(pb, &polys[(npolys-1)*MAX_VERTS_PER_POLY], sizeof(unsigned short)*MAX_VERTS_PER_POLY);
+					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*MAX_VERTS_PER_POLY*2];
+			unsigned short* q = &polys[j*MAX_VERTS_PER_POLY];
+			for (int k = 0; k < MAX_VERTS_PER_POLY; ++k)
+				p[k] = q[k];
+			mesh.areas[mesh.npolys] = cont.area;
+			mesh.npolys++;
+			if (mesh.npolys > maxTris)
+				return DT_FAILURE | DT_BUFFER_TOO_SMALL;
+		}
+	}
+	
+	
+	// Remove edge vertices.
+	for (int i = 0; i < mesh.nverts; ++i)
+	{
+		if (vflags[i])
+		{
+			if (!canRemoveVertex(mesh, (unsigned short)i))
+				continue;
+			dtStatus status = removeVertex(mesh, (unsigned short)i, maxTris);
+			if (dtStatusFailed(status))
+				return status;
+			// Remove vertex
+			// Note: mesh.nverts is already decremented inside removeVertex()!
+			for (int j = i; j < mesh.nverts; ++j)
+				vflags[j] = vflags[j+1];
+			--i;
+		}
+	}
+	
+	// Calculate adjacency.
+	if (!buildMeshAdjacency(alloc, mesh.polys, mesh.npolys, mesh.verts, mesh.nverts, lcset))
+		return DT_FAILURE | DT_OUT_OF_MEMORY;
+		
+	return DT_SUCCESS;
+}
+
+dtStatus dtMarkCylinderArea(dtTileCacheLayer& layer, const float* orig, const float cs, const float ch,
+							const float* pos, const float radius, const float height, const unsigned char areaId)
+{
+	float bmin[3], bmax[3];
+	bmin[0] = pos[0] - radius;
+	bmin[1] = pos[1];
+	bmin[2] = pos[2] - radius;
+	bmax[0] = pos[0] + radius;
+	bmax[1] = pos[1] + height;
+	bmax[2] = pos[2] + radius;
+	const float r2 = dtSqr(radius/cs + 0.5f);
+
+	const int w = (int)layer.header->width;
+	const int h = (int)layer.header->height;
+	const float ics = 1.0f/cs;
+	const float ich = 1.0f/ch;
+	
+	const float px = (pos[0]-orig[0])*ics;
+	const float pz = (pos[2]-orig[2])*ics;
+	
+	int minx = (int)dtMathFloorf((bmin[0]-orig[0])*ics);
+	int miny = (int)dtMathFloorf((bmin[1]-orig[1])*ich);
+	int minz = (int)dtMathFloorf((bmin[2]-orig[2])*ics);
+	int maxx = (int)dtMathFloorf((bmax[0]-orig[0])*ics);
+	int maxy = (int)dtMathFloorf((bmax[1]-orig[1])*ich);
+	int maxz = (int)dtMathFloorf((bmax[2]-orig[2])*ics);
+
+	if (maxx < 0) return DT_SUCCESS;
+	if (minx >= w) return DT_SUCCESS;
+	if (maxz < 0) return DT_SUCCESS;
+	if (minz >= h) return DT_SUCCESS;
+	
+	if (minx < 0) minx = 0;
+	if (maxx >= w) maxx = w-1;
+	if (minz < 0) minz = 0;
+	if (maxz >= h) maxz = h-1;
+	
+	for (int z = minz; z <= maxz; ++z)
+	{
+		for (int x = minx; x <= maxx; ++x)
+		{
+			const float dx = (float)(x+0.5f) - px;
+			const float dz = (float)(z+0.5f) - pz;
+			if (dx*dx + dz*dz > r2)
+				continue;
+			const int y = layer.heights[x+z*w];
+			if (y < miny || y > maxy)
+				continue;
+			layer.areas[x+z*w] = areaId;
+		}
+	}
+
+	return DT_SUCCESS;
+}
+
+
+dtStatus dtBuildTileCacheLayer(dtTileCacheCompressor* comp,
+							   dtTileCacheLayerHeader* header,
+							   const unsigned char* heights,
+							   const unsigned char* areas,
+							   const unsigned char* cons,
+							   unsigned char** outData, int* outDataSize)
+{
+	const int headerSize = dtAlign4(sizeof(dtTileCacheLayerHeader));
+	const int gridSize = (int)header->width * (int)header->height;
+	const int maxDataSize = headerSize + comp->maxCompressedSize(gridSize*3);
+	unsigned char* data = (unsigned char*)dtAlloc(maxDataSize, DT_ALLOC_PERM);
+	if (!data)
+		return DT_FAILURE | DT_OUT_OF_MEMORY;
+	memset(data, 0, maxDataSize);
+	
+	// Store header
+	memcpy(data, header, sizeof(dtTileCacheLayerHeader));
+	
+	// Concatenate grid data for compression.
+	const int bufferSize = gridSize*3;
+	unsigned char* buffer = (unsigned char*)dtAlloc(bufferSize, DT_ALLOC_TEMP);
+	if (!buffer)
+		return DT_FAILURE | DT_OUT_OF_MEMORY;
+	memcpy(buffer, heights, gridSize);
+	memcpy(buffer+gridSize, areas, gridSize);
+	memcpy(buffer+gridSize*2, cons, gridSize);
+	
+	// Compress
+	unsigned char* compressed = data + headerSize;
+	const int maxCompressedSize = maxDataSize - headerSize;
+	int compressedSize = 0;
+	dtStatus status = comp->compress(buffer, bufferSize, compressed, maxCompressedSize, &compressedSize);
+	if (dtStatusFailed(status))
+		return status;
+
+	*outData = data;
+	*outDataSize = headerSize + compressedSize;
+	
+	dtFree(buffer);
+	
+	return DT_SUCCESS;
+}
+
+void dtFreeTileCacheLayer(dtTileCacheAlloc* alloc, dtTileCacheLayer* layer)
+{
+	dtAssert(alloc);
+	// The layer is allocated as one conitguous blob of data.
+	alloc->free(layer);
+}
+
+dtStatus dtDecompressTileCacheLayer(dtTileCacheAlloc* alloc, dtTileCacheCompressor* comp,
+									unsigned char* compressed, const int compressedSize,
+									dtTileCacheLayer** layerOut)
+{
+	dtAssert(alloc);
+	dtAssert(comp);
+
+	if (!layerOut)
+		return DT_FAILURE | DT_INVALID_PARAM;
+	if (!compressed)
+		return DT_FAILURE | DT_INVALID_PARAM;
+
+	*layerOut = 0;
+
+	dtTileCacheLayerHeader* compressedHeader = (dtTileCacheLayerHeader*)compressed;
+	if (compressedHeader->magic != DT_TILECACHE_MAGIC)
+		return DT_FAILURE | DT_WRONG_MAGIC;
+	if (compressedHeader->version != DT_TILECACHE_VERSION)
+		return DT_FAILURE | DT_WRONG_VERSION;
+	
+	const int layerSize = dtAlign4(sizeof(dtTileCacheLayer));
+	const int headerSize = dtAlign4(sizeof(dtTileCacheLayerHeader));
+	const int gridSize = (int)compressedHeader->width * (int)compressedHeader->height;
+	const int bufferSize = layerSize + headerSize + gridSize*4;
+	
+	unsigned char* buffer = (unsigned char*)alloc->alloc(bufferSize);
+	if (!buffer)
+		return DT_FAILURE | DT_OUT_OF_MEMORY;
+	memset(buffer, 0, bufferSize);
+
+	dtTileCacheLayer* layer = (dtTileCacheLayer*)buffer;
+	dtTileCacheLayerHeader* header = (dtTileCacheLayerHeader*)(buffer + layerSize);
+	unsigned char* grids = buffer + layerSize + headerSize;
+	const int gridsSize = bufferSize - (layerSize + headerSize); 
+	
+	// Copy header
+	memcpy(header, compressedHeader, headerSize);
+	// Decompress grid.
+	int size = 0;
+	dtStatus status = comp->decompress(compressed+headerSize, compressedSize-headerSize,
+									   grids, gridsSize, &size);
+	if (dtStatusFailed(status))
+	{
+		dtFree(buffer);
+		return status;
+	}
+	
+	layer->header = header;
+	layer->heights = grids;
+	layer->areas = grids + gridSize;
+	layer->cons = grids + gridSize*2;
+	layer->regs = grids + gridSize*3;
+	
+	*layerOut = layer;
+	
+	return DT_SUCCESS;
+}
+
+
+
+bool dtTileCacheHeaderSwapEndian(unsigned char* data, const int dataSize)
+{
+	dtIgnoreUnused(dataSize);
+	dtTileCacheLayerHeader* header = (dtTileCacheLayerHeader*)data;
+	
+	int swappedMagic = DT_TILECACHE_MAGIC;
+	int swappedVersion = DT_TILECACHE_VERSION;
+	dtSwapEndian(&swappedMagic);
+	dtSwapEndian(&swappedVersion);
+	
+	if ((header->magic != DT_TILECACHE_MAGIC || header->version != DT_TILECACHE_VERSION) &&
+		(header->magic != swappedMagic || header->version != swappedVersion))
+	{
+		return false;
+	}
+	
+	dtSwapEndian(&header->magic);
+	dtSwapEndian(&header->version);
+	dtSwapEndian(&header->tx);
+	dtSwapEndian(&header->ty);
+	dtSwapEndian(&header->tlayer);
+	dtSwapEndian(&header->bmin[0]);
+	dtSwapEndian(&header->bmin[1]);
+	dtSwapEndian(&header->bmin[2]);
+	dtSwapEndian(&header->bmax[0]);
+	dtSwapEndian(&header->bmax[1]);
+	dtSwapEndian(&header->bmax[2]);
+	dtSwapEndian(&header->hmin);
+	dtSwapEndian(&header->hmax);
+	
+	// width, height, minx, maxx, miny, maxy are unsigned char, no need to swap.
+	
+	return true;
+}
+

+ 6 - 0
Source/Urho3D/CMakeLists.txt

@@ -150,6 +150,12 @@ if (URHO3D_PHYSICS)
     # This is more practical than patching its header files in many places to make them work with relative path
     list (APPEND INCLUDE_DIRS ${CMAKE_BINARY_DIR}/${DEST_INCLUDE_DIR}/ThirdParty/Bullet)
 endif ()
+
+if (URHO3D_NAVIGATION)
+    # DetourTileCache and DetourCrowd libraries depend on Detour's include dir to be added in the header search path
+    list (APPEND INCLUDE_DIRS ${CMAKE_BINARY_DIR}/${DEST_INCLUDE_DIR}/ThirdParty/Detour)
+endif ()
+
 if (URHO3D_LUA)
     # ditto for Lua/LuaJIT
     list (APPEND INCLUDE_DIRS ${CMAKE_BINARY_DIR}/${DEST_INCLUDE_DIR}/ThirdParty/Lua${JIT})

+ 19 - 0
Source/Urho3D/Graphics/DebugRenderer.cpp

@@ -241,6 +241,25 @@ void DebugRenderer::AddSphere(const Sphere& sphere, const Color& color, bool dep
     }
 }
 
+void DebugRenderer::AddCylinder(const Vector3& position, float radius, float height, const Color& color, bool depthTest)
+{
+	Sphere sphere(position, radius);
+	Vector3 heightVec(0, height, 0);
+	Vector3 offsetXVec(radius, 0, 0);
+	Vector3 offsetZVec(0, 0, radius);
+	for (unsigned i = 0; i < 360; i += 45)
+	{
+		Vector3 p1 = PointOnSphere(sphere, i, 90);
+		Vector3 p2 = PointOnSphere(sphere, i + 45, 90);
+		AddLine(p1, p2, color, depthTest);
+		AddLine(p1 + heightVec, p2 + heightVec, color, depthTest);
+	}
+	AddLine(position + offsetXVec, position + heightVec + offsetXVec, color, depthTest);
+	AddLine(position - offsetXVec, position + heightVec - offsetXVec, color, depthTest);
+	AddLine(position + offsetZVec, position + heightVec + offsetZVec, color, depthTest);
+	AddLine(position - offsetZVec, position + heightVec - offsetZVec, color, depthTest);
+}
+
 void DebugRenderer::AddSkeleton(const Skeleton& skeleton, const Color& color, bool depthTest)
 {
     const Vector<Bone>& bones = skeleton.GetBones();

+ 2 - 0
Source/Urho3D/Graphics/DebugRenderer.h

@@ -126,6 +126,8 @@ public:
     void AddPolyhedron(const Polyhedron& poly, const Color& color, bool depthTest = true);
     /// Add a sphere.
     void AddSphere(const Sphere& sphere, const Color& color, bool depthTest = true);
+	/// Add a cylinder
+	void AddCylinder(const Vector3& position, float radius, float height, const Color& color, bool depthTest = true);
     /// Add a skeleton.
     void AddSkeleton(const Skeleton& skeleton, const Color& color, bool depthTest = true);
     /// Add a triangle mesh.

+ 54 - 0
Source/Urho3D/LuaScript/pkgs/Navigation/CrowdAgent.pkg

@@ -0,0 +1,54 @@
+$#include "Navigation/CrowdAgent.h"
+
+enum CrowdTargetState
+{
+    CROWD_AGENT_TARGET_NONE = 0,
+    CROWD_AGENT_TARGET_FAILED,
+    CROWD_AGENT_TARGET_VALID,
+    CROWD_AGENT_TARGET_REQUESTING,
+    CROWD_AGENT_TARGET_WAITINGFORPATH,
+    CROWD_AGENT_TARGET_WAITINGFORQUEUE,
+    CROWD_AGENT_TARGET_VELOCITY,
+    CROWD_AGENT_TARGET_ARRIVED
+};
+
+enum CrowdAgentState
+{
+    CROWD_AGENT_INVALID = 0,
+    CROWD_AGENT_READY,
+    CROWD_AGENT_TRAVERSINGLINK
+};
+
+
+
+class CrowdAgent : public Component
+{
+    void SetNavigationFilterType(unsigned filterID);
+    bool SetMoveTarget(const Vector3& position);
+    bool SetMoveVelocity(const Vector3& velocity);
+    void SetUpdateNodePosition(bool unodepos);
+    void SetMaxAccel(float val);
+    void SetMaxSpeed(float val);
+    void SetNavigationQuality(NavigationQuality val);
+    void SetNavigationPushiness(NavigationPushiness val);
+
+    unsigned GetNavigationFilterType() const;
+    Vector3 GetDesiredVelocity() const;
+    Vector3 GetActualVelocity() const;
+    const Vector3& GetTargetPosition() const;
+    CrowdAgentState GetAgentState() const;
+    CrowdTargetState GetTargetState() const;
+    bool GetUpdateNodePosition();
+    float GetMaxSpeed();
+    float GetMaxAccel();
+    NavigationQuality GetNavigationQuality() const;
+    NavigationPushiness GetNavigationPushiness() const;
+    Vector3 GetPosition() const;
+    void DrawDebugGeometry(bool depthTest);
+    
+    tolua_property__get_set bool updateNodePosition;
+    tolua_property__get_set NavigationQuality navigationQuality;
+    tolua_property__get_set NavigationPushiness navigationPushiness;
+    tolua_property__get_set float maxSpeed;
+    tolua_property__get_set float maxAccel;
+};

+ 31 - 0
Source/Urho3D/LuaScript/pkgs/Navigation/DetourCrowdManager.pkg

@@ -0,0 +1,31 @@
+$#include "Navigation/DetourCrowdManager.h"
+
+enum NavigationQuality
+{
+    NAVIGATIONQUALITY_LOW = 0,
+    NAVIGATIONQUALITY_MEDIUM = 1,
+    NAVIGATIONQUALITY_HIGH = 2
+};
+enum NavigationPushiness
+{
+    PUSHINESS_LOW,
+    PUSHINESS_MEDIUM,
+    PUSHINESS_HIGH
+};
+
+class DetourCrowdManager : public Component
+{
+    bool CreateCrowd();
+
+    void SetNavigationMesh(NavigationMesh *navMesh);
+    void SetMaxAgents(unsigned agentCt);
+    void SetAreaTypeCost(unsigned filterID, unsigned areaType, float cost);
+    
+    NavigationMesh* GetNavigationMesh();
+    unsigned GetMaxAgents() const;
+    float GetAreaTypeCost(unsigned filterID, unsigned areaType) const;
+    unsigned GetAgentCount() const;
+    
+    tolua_property__get_set NavigationMesh* navigationMesh;
+    tolua_property__get_set int maxAgents;
+};

+ 7 - 0
Source/Urho3D/LuaScript/pkgs/Navigation/DynamicNavigationMesh.pkg

@@ -0,0 +1,7 @@
+$#include "Navigation/DynamicNavigationMesh.h"
+
+
+class DynamicNavigationMesh : public NavigationMesh
+{
+        
+};

+ 0 - 0
Source/Urho3D/LuaScript/pkgs/Navigation/NavArea.pkg


+ 16 - 0
Source/Urho3D/LuaScript/pkgs/Navigation/Obstacle.pkg

@@ -0,0 +1,16 @@
+$#include "Navigation/Obstacle.h"
+
+class Obstacle : public Component
+{
+    void DrawDebugGeometry(bool depthTest);
+    
+    void SetRadius(float radius);
+    void SetHeight(float height);
+    
+    float GetRadius() const;
+    float GetHeight() const;
+    
+    
+    tolua_property__get_set float radius;
+    tolua_property__get_set float height;
+};

+ 6 - 0
Source/Urho3D/LuaScript/pkgs/NavigationLuaAPI.pkg

@@ -1,6 +1,12 @@
 $pfile "Navigation/Navigable.pkg"
 $pfile "Navigation/NavigationMesh.pkg"
+$pfile "Navigation/DynamicNavigationMesh.pkg"
 $pfile "Navigation/OffMeshConnection.pkg"
+$pfile "Navigation/NavArea.pkg"
+$pfile "Navigation/Obstacle.pkg"
+$pfile "Navigation/DetourCrowdManager.pkg"
+$pfile "Navigation/CrowdAgent.pkg"
+
 
 $using namespace Urho3D;
 $#pragma warning(disable:4800)

+ 470 - 0
Source/Urho3D/Navigation/CrowdAgent.cpp

@@ -0,0 +1,470 @@
+//
+// Copyright (c) 2008-2015 the Urho3D project.
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+//
+
+#include "Precompiled.h"
+
+#include "../Scene/Component.h"
+#include "../Core/Context.h"
+#include "../Navigation/CrowdAgent.h"
+#include "../Graphics/DebugRenderer.h"
+#include "../Navigation/DetourCrowdManager.h"
+#include "../IO/Log.h"
+#include "../IO/MemoryBuffer.h"
+#include "../Navigation/NavigationEvents.h"
+#include "../Scene/Node.h"
+#include "../Core/Profiler.h"
+#include "../Scene/Scene.h"
+#include "../Scene/Serializable.h"
+#include "../Core/Variant.h"
+
+#include <Detour/DetourCommon.h>
+#include <DetourCrowd/DetourCrowd.h>
+
+#include "../DebugNew.h"
+
+namespace Urho3D
+{
+
+extern const char* NAVIGATION_CATEGORY;
+
+static const unsigned DEFAULT_AGENT_NAVIGATION_FILTER_TYPE = 0;
+static const float DEFAULT_AGENT_MAX_SPEED = 5.0f;
+static const float DEFAULT_AGENT_MAX_ACCEL = 3.6f;
+static const NavigationQuality DEFAULT_AGENT_AVOIDANCE_QUALITY = NAVIGATIONQUALITY_HIGH;
+static const NavigationPushiness DEFAULT_AGENT_NAVIGATION_PUSHINESS = PUSHINESS_MEDIUM;
+
+const char* crowdAgentAvoidanceQualityNames[] = {
+    "low",
+    "medium",
+    "high",
+    0
+};
+
+const char* crowdAgentPushinessNames[] = {
+    "low",
+    "medium",
+    "high",
+    0
+};
+
+
+CrowdAgent::CrowdAgent(Context* context) :
+    Component(context),
+    inCrowd_(false),
+    agentCrowdId_(-1),
+    targetRef_(-1),
+    updateNodePosition_(true),
+    maxAccel_(DEFAULT_AGENT_MAX_ACCEL),
+    maxSpeed_(DEFAULT_AGENT_MAX_SPEED),
+    radius_(0.0f),
+    height_(0.0f),
+    filterType_(DEFAULT_AGENT_NAVIGATION_FILTER_TYPE),
+    navQuality_(DEFAULT_AGENT_AVOIDANCE_QUALITY),
+    navPushiness_(DEFAULT_AGENT_NAVIGATION_PUSHINESS)
+{
+}
+
+CrowdAgent::~CrowdAgent()
+{
+}
+
+void CrowdAgent::RegisterObject(Context* context)
+{
+    context->RegisterFactory<CrowdAgent>(NAVIGATION_CATEGORY);
+
+    ACCESSOR_ATTRIBUTE("Max Accel", GetMaxAccel, SetMaxAccel, float, DEFAULT_AGENT_MAX_ACCEL, AM_DEFAULT);
+    ACCESSOR_ATTRIBUTE("Max Speed", GetMaxSpeed, SetMaxSpeed, float, DEFAULT_AGENT_MAX_SPEED, AM_DEFAULT);
+    ACCESSOR_ATTRIBUTE("Radius", GetRadius, SetRadius, float, 0.0f, AM_DEFAULT);
+    ACCESSOR_ATTRIBUTE("Height", GetHeight, SetHeight, float, 0.0f, AM_DEFAULT);
+    ACCESSOR_ATTRIBUTE("Navigation Filter", GetNavigationFilterType, SetNavigationFilterType, unsigned, DEFAULT_AGENT_NAVIGATION_FILTER_TYPE, AM_DEFAULT);
+    ENUM_ACCESSOR_ATTRIBUTE("Navigation Pushiness", GetNavigationPushiness, SetNavigationPushiness, NavigationPushiness, crowdAgentPushinessNames, PUSHINESS_LOW, AM_DEFAULT);
+    ENUM_ACCESSOR_ATTRIBUTE("Navigation Quality", GetNavigationQuality, SetNavigationQuality, NavigationQuality, crowdAgentAvoidanceQualityNames, NAVIGATIONQUALITY_LOW, AM_DEFAULT);
+    MIXED_ACCESSOR_ATTRIBUTE("Agent Data", GetAgentDataAttr, SetAgentDataAttr, PODVector<unsigned char>, Variant::emptyBuffer, AM_FILE | AM_NOEDIT);
+}
+
+void CrowdAgent::OnNodeSet(Node* node)
+{
+    if (node)
+    {
+        Scene* scene = GetScene();
+        if (scene)
+        {
+            if (scene == node)
+                LOGERROR(GetTypeName() + " should not be created to the root scene node");		
+            crowdManager_ = scene->GetOrCreateComponent<DetourCrowdManager>();
+            AddAgentToCrowd();
+        }
+
+        node->AddListener(this);
+    }
+}
+
+void CrowdAgent::OnSetEnabled()
+{
+    bool enabled = IsEnabledEffective();
+
+    if (enabled && !inCrowd_)
+        AddAgentToCrowd();
+    else if (!enabled && inCrowd_)
+        RemoveAgentFromCrowd();
+}
+
+void CrowdAgent::DrawDebugGeometry(bool depthTest)
+{
+    Scene* scene = GetScene();
+    if (scene)
+    {
+        DebugRenderer* debug = scene->GetComponent<DebugRenderer>();
+        if (debug)
+            DrawDebugGeometry(debug, depthTest);
+    }
+}
+
+void CrowdAgent::DrawDebugGeometry(DebugRenderer* debug, bool depthTest)
+{
+    if (node_)
+    {
+        const Vector3 pos = GetPosition();
+        const Vector3 vel = GetActualVelocity();
+        const Vector3 desiredVel = GetDesiredVelocity();
+        const Vector3 agentHeightVec(0, height_ * 0.5f, 0);
+
+        debug->AddLine(pos, pos + vel, Color::GREEN, depthTest);
+        debug->AddLine(pos + agentHeightVec, pos + desiredVel + agentHeightVec, Color::RED, depthTest);
+        debug->AddCylinder(pos, radius_, height_, Color::GREEN, depthTest);
+    }
+}
+
+void CrowdAgent::AddAgentToCrowd()
+{
+    if (!crowdManager_ || !crowdManager_->crowd_)
+        return;
+
+    PROFILE(AddAgentToCrowd);
+
+    if (!inCrowd_)
+    {
+        inCrowd_ = true;
+        agentCrowdId_ = crowdManager_->AddAgent(this, node_->GetPosition());
+        if (agentCrowdId_ == -1)
+        {
+            inCrowd_ = false;
+            LOGERROR("AddAgentToCrowd: Could not add agent to crowd");
+            return;
+        }
+        dtCrowdAgentParams& params = crowdManager_->GetCrowd()->getEditableAgent(agentCrowdId_)->params;
+        params.userData = this;
+        crowdManager_->UpdateAgentNavigationQuality(this, navQuality_);
+        crowdManager_->UpdateAgentPushiness(this, navPushiness_);
+    }
+}
+
+void CrowdAgent::RemoveAgentFromCrowd()
+{
+    if (crowdManager_ && agentCrowdId_ != -1 && inCrowd_)
+    {
+        crowdManager_->RemoveAgent(this);
+        inCrowd_ = false;
+        agentCrowdId_ = -1;
+    }
+}
+
+void CrowdAgent::SetNavigationFilterType(unsigned filterType)
+{
+    filterType_ = filterType;
+    if (crowdManager_ && inCrowd_)
+    {
+        // If in the crowd it's necessary to force the update of the query filter
+        dtCrowdAgentParams params = crowdManager_->GetCrowdAgent(agentCrowdId_)->params;
+        params.queryFilterType = (unsigned char)filterType;
+        crowdManager_->GetCrowd()->updateAgentParameters(agentCrowdId_, &params);
+        MarkNetworkUpdate();
+    }
+}
+
+bool CrowdAgent::SetMoveTarget(const Vector3& position)
+{
+    if (crowdManager_ && !inCrowd_)
+        AddAgentToCrowd();
+    if (crowdManager_ && inCrowd_)
+    {
+        targetPosition_ = position;
+        if (crowdManager_->SetAgentTarget(this, position, targetRef_))
+        {
+            MarkNetworkUpdate();
+            return true;
+        }
+    }
+    return false;
+}
+
+bool CrowdAgent::SetMoveVelocity(const Vector3& velocity)
+{
+    if (crowdManager_ && inCrowd_)
+    {
+        const dtCrowdAgent* agent = crowdManager_->GetCrowdAgent(agentCrowdId_);
+        if (agent && agent->active)
+        {
+            crowdManager_->GetCrowd()->requestMoveVelocity(agentCrowdId_, velocity.Data());
+            MarkNetworkUpdate();
+        }
+    }
+    return false;
+}
+
+void CrowdAgent::SetMaxSpeed(float speed)
+{
+    maxSpeed_ = speed;
+    if(crowdManager_ && inCrowd_)
+    {
+        dtCrowdAgentParams params = crowdManager_->GetCrowdAgent(agentCrowdId_)->params;
+        params.maxSpeed = speed;
+        crowdManager_->GetCrowd()->updateAgentParameters(agentCrowdId_, &params);
+        MarkNetworkUpdate();
+    }
+}
+
+void CrowdAgent::SetMaxAccel(float accel)
+{
+    maxAccel_ = accel;
+    if(crowdManager_ && inCrowd_)
+    {
+        dtCrowdAgentParams params = crowdManager_->GetCrowdAgent(agentCrowdId_)->params;
+        params.maxAcceleration = accel;
+        crowdManager_->GetCrowd()->updateAgentParameters(agentCrowdId_, &params);
+        MarkNetworkUpdate();
+    }
+}
+
+void CrowdAgent::SetRadius(float radius)
+{
+    radius_ = radius;
+    if (crowdManager_ && inCrowd_)
+    {
+        dtCrowdAgentParams params = crowdManager_->GetCrowdAgent(agentCrowdId_)->params;
+        params.radius = radius;
+        crowdManager_->GetCrowd()->updateAgentParameters(agentCrowdId_, &params);
+        MarkNetworkUpdate();
+    }
+}
+
+void CrowdAgent::SetHeight(float height)
+{
+    height_ = height;
+    if (crowdManager_ && inCrowd_)
+    {
+        dtCrowdAgentParams params = crowdManager_->GetCrowdAgent(agentCrowdId_)->params;
+        params.height = height;
+        crowdManager_->GetCrowd()->updateAgentParameters(agentCrowdId_, &params);
+        MarkNetworkUpdate();
+    }
+}
+
+void CrowdAgent::SetNavigationQuality(NavigationQuality val)
+{
+    navQuality_=val;
+    if(crowdManager_ && inCrowd_)
+    {
+        crowdManager_->UpdateAgentNavigationQuality(this, navQuality_);
+        MarkNetworkUpdate();
+    }
+}
+
+void CrowdAgent::SetNavigationPushiness(NavigationPushiness val)
+{
+    navPushiness_=val;
+    if(crowdManager_ && inCrowd_)
+    {
+        crowdManager_->UpdateAgentPushiness(this, navPushiness_);
+        MarkNetworkUpdate();
+    }
+}
+
+Vector3 CrowdAgent::GetPosition() const
+{
+    if (crowdManager_ && inCrowd_)
+    {
+        const dtCrowdAgent* agent = crowdManager_->GetCrowdAgent(agentCrowdId_);
+        if (agent && agent->active)
+            return Vector3(agent->npos);
+    }
+    return node_->GetPosition();
+}
+
+Vector3 CrowdAgent::GetDesiredVelocity() const
+{
+    if (crowdManager_ && inCrowd_)
+    {
+        const dtCrowdAgent* agent = crowdManager_->GetCrowdAgent(agentCrowdId_);
+        if (agent && agent->active)
+            return Vector3(agent->dvel);
+    }
+    return Vector3::ZERO;
+}
+
+Vector3 CrowdAgent::GetActualVelocity() const
+{
+    if (crowdManager_ && inCrowd_)
+    {
+        const dtCrowdAgent* agent = crowdManager_->GetCrowdAgent(agentCrowdId_);
+        if (agent && agent->active)
+            return Vector3(agent->vel);
+    }
+    return Vector3::ZERO;
+}
+
+const Vector3& CrowdAgent::GetTargetPosition() const
+{
+    return targetPosition_;
+}
+
+Urho3D::CrowdAgentState CrowdAgent::GetAgentState() const
+{
+    if (crowdManager_ && inCrowd_)
+    {
+        const dtCrowdAgent* agent = crowdManager_->GetCrowdAgent(agentCrowdId_);
+        if (!agent || !agent->active)
+            return CROWD_AGENT_INVALID;
+        return (CrowdAgentState)agent->state;
+    }
+    return CROWD_AGENT_INVALID;
+}
+
+Urho3D::CrowdTargetState CrowdAgent::GetTargetState() const
+{
+    if (crowdManager_ && inCrowd_)
+    {
+        const dtCrowdAgent* agent = crowdManager_->GetCrowdAgent(agentCrowdId_);
+        if (!agent || !agent->active)
+            return CROWD_AGENT_TARGET_NONE;
+
+        // Determine if we've arrived at the target
+        if (agent->targetState == DT_CROWDAGENT_TARGET_VALID)
+        {
+            if (agent->ncorners)
+            {
+                // Is the agent at the end of its path?
+                const bool endOfPath = (agent->cornerFlags[agent->ncorners - 1] & DT_STRAIGHTPATH_END) ? true : false;
+                if (endOfPath)
+                {
+                    // Within its own radius of the goal?
+                    if (dtVdist2D(agent->npos, &agent->cornerVerts[(agent->ncorners - 1) * 3]) <= agent->params.radius)
+                        return CROWD_AGENT_TARGET_ARRIVED;
+
+                }
+            }
+        }
+        return (CrowdTargetState)agent->targetState;
+    }
+    return CROWD_AGENT_TARGET_NONE;
+}
+
+void CrowdAgent::SetUpdateNodePosition(bool unodepos)
+{
+    updateNodePosition_ = unodepos;
+    MarkNetworkUpdate();
+}
+
+bool CrowdAgent::GetUpdateNodePosition()
+{
+    return updateNodePosition_;
+}
+
+void CrowdAgent::OnCrowdAgentReposition(const Vector3& newPos, const Vector3& newDirection)
+{
+    if(node_)
+    {
+        // Notify parent node of the reposition
+        VariantMap& map = GetContext()->GetEventDataMap();
+        map[CrowdAgentReposition::P_POSITION] = newPos;
+        map[CrowdAgentReposition::P_VELOCITY] = GetActualVelocity();
+        SendEvent(E_CROWD_AGENT_REPOSITION, map);
+        
+        ignoreTransformChanges_ = true;
+        if (updateNodePosition_)
+            node_->SetPosition(newPos);
+        ignoreTransformChanges_ = false;
+
+        // Send a notification event if we've reached the destination
+        CrowdTargetState targetState = GetTargetState();
+        switch (targetState)
+        {
+        case CROWD_AGENT_TARGET_ARRIVED:
+            VariantMap& map = GetContext()->GetEventDataMap();
+            map[CrowdAgentStateChanged::P_STATE] = targetState;
+            map[CrowdAgentStateChanged::P_POSITION] = newPos;
+            map[CrowdAgentStateChanged::P_VELOCITY] = GetActualVelocity();
+            SendEvent(E_CROWD_AGENT_STATE_CHANGED, map);
+            break;
+        }
+    }
+}
+
+
+PODVector<unsigned char> CrowdAgent::GetAgentDataAttr() const
+{
+    if (!inCrowd_ || !crowdManager_ || !IsEnabled())
+        return Variant::emptyBuffer;
+    dtCrowd* crowd = crowdManager_->GetCrowd();
+    const dtCrowdAgent* agent = crowd->getAgent(agentCrowdId_);
+    
+    // Reading it back in isn't this simple, see SetAgentDataAttr
+    VectorBuffer ret;
+    ret.Write(agent, sizeof(dtCrowdAgent));
+
+    return ret.GetBuffer();
+}
+
+void CrowdAgent::SetAgentDataAttr(const PODVector<unsigned char>& value)
+{
+    if (value.Empty() || !inCrowd_ || !crowdManager_ || !IsEnabled())
+        return;
+
+    MemoryBuffer buffer(value);
+    dtCrowd* crowd = crowdManager_->GetCrowd();
+    dtCrowdAgent* agent = crowd->getEditableAgent(agentCrowdId_);
+
+    // Path corridor is tricky
+    char corridorData[sizeof(dtPathCorridor)];
+    // Duplicate the existing path corridor into a block
+    memcpy(corridorData, &agent->corridor, sizeof(dtPathCorridor));
+
+    // Read the entire block of the crowd agent
+    buffer.Read(agent, sizeof(dtCrowdAgent));
+    // Restore the values of the original path corridor
+    memcpy(&agent->corridor, corridorData, sizeof(dtPathCorridor));
+    // Tell the path corridor to rebuild the path, it will reevaluate the path, existing velocities maintained
+    agent->corridor.reset(agent->targetRef, agent->targetPos);
+
+    agent->params.userData = this;
+}
+
+void CrowdAgent::OnMarkedDirty(Node* node)
+{
+    if (inCrowd_ && crowdManager_ && !ignoreTransformChanges_) {
+        dtCrowdAgent* agt = crowdManager_->GetCrowd()->getEditableAgent(agentCrowdId_);
+        memcpy(agt->npos, node->GetPosition().Data(), sizeof(float) * 3);
+    }
+}
+
+
+}

+ 171 - 0
Source/Urho3D/Navigation/CrowdAgent.h

@@ -0,0 +1,171 @@
+//
+// Copyright (c) 2008-2015 the Urho3D project.
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+//
+
+#pragma once
+
+#include "../Scene/Component.h"
+#include "../Navigation/DetourCrowdManager.h"
+
+namespace Urho3D
+{
+
+enum CrowdTargetState
+{
+    CROWD_AGENT_TARGET_NONE = 0,
+    CROWD_AGENT_TARGET_FAILED,
+    CROWD_AGENT_TARGET_VALID,
+    CROWD_AGENT_TARGET_REQUESTING,
+    CROWD_AGENT_TARGET_WAITINGFORPATH,
+    CROWD_AGENT_TARGET_WAITINGFORQUEUE,
+    CROWD_AGENT_TARGET_VELOCITY,
+    CROWD_AGENT_TARGET_ARRIVED
+};
+
+enum CrowdAgentState
+{
+    CROWD_AGENT_INVALID = 0,	///< The agent is not in a valid state.
+    CROWD_AGENT_READY,			///< The agent is traversing a normal navigation mesh polygon
+    CROWD_AGENT_TRAVERSINGLINK	///< The agent is traversing an off-mesh connection.
+};
+
+/// DetourCrowd Agent, requires a DetourCrowdManager in the scene
+/// Agent's radius and height is set through the navigation mesh.
+class URHO3D_API CrowdAgent : public Component
+{
+    OBJECT(CrowdAgent);
+    friend class DetourCrowdManager;
+
+public:
+    /// Construct.
+    CrowdAgent(Context* context);
+    /// Destruct.
+    virtual ~CrowdAgent();
+    /// Register object factory.
+    static void RegisterObject(Context* context);
+
+    /// Handle enabled/disabled state change.
+    virtual void OnSetEnabled();
+    /// Draw debug geometry
+    void DrawDebugGeometry(bool);
+    /// Draw debug feelers
+    virtual void DrawDebugGeometry(DebugRenderer* debug, bool depthTest);
+
+    /// Sets the navigation filter type the agent will use
+    void SetNavigationFilterType(unsigned filterTypeID);
+    /// Submits a new move request for this agent.
+    bool SetMoveTarget(const Vector3& position);
+    /// Submits a new move velocity request for this agent.
+    bool SetMoveVelocity(const Vector3& velocity);
+    /// Update the nodes position.
+    void SetUpdateNodePosition(bool unodepos);
+    /// Sets the agent's max acceleration.
+    void SetMaxAccel(float val);
+    /// Sets the agent's max velocity.
+    void SetMaxSpeed(float val);
+    /// Sets the agent's radius
+    void SetRadius(float val);
+    /// Sets the agent's height
+    void SetHeight(float val);
+    /// Sets the agent's navigation quality
+    void SetNavigationQuality(NavigationQuality val);
+    /// Sets the agent's navigation pushiness
+    void SetNavigationPushiness(NavigationPushiness val);
+
+    /// Gets the navigation filter type this agent is using
+    unsigned GetNavigationFilterType() const { return filterType_; }
+    /// Returns the agent's position.
+    Vector3 GetPosition() const;
+    /// Returns the agent's desired velocity.
+    Vector3 GetDesiredVelocity() const;
+    /// Returns the agent's actual velocity.
+    Vector3 GetActualVelocity() const;
+    /// Returns the agent's target position.
+    const Vector3& GetTargetPosition() const;
+    /// Returns the agent's  state.
+    CrowdAgentState   GetAgentState() const;
+    /// Returns the agent's target state.
+    CrowdTargetState  GetTargetState() const;
+    /// Returns if the node's position is updating because of the crowd.
+    bool GetUpdateNodePosition();
+    /// Returns the agent id.
+    int GetAgentCrowdId() const { return agentCrowdId_; }
+    /// Gets the agent's max velocity.
+    float GetMaxSpeed() const { return maxSpeed_; }
+    /// Gets the agent's max acceleration.
+    float GetMaxAccel()	const { return maxAccel_; }
+    /// Gets the agent's radius
+    float GetRadius() const { return radius_; }
+    /// Gets the agent's height
+    float GetHeight() const { return height_; }
+    /// Gets the agent's navigation quality
+    NavigationQuality GetNavigationQuality() const {return navQuality_; }
+    /// Gets the agent's navigation pushiness
+    NavigationPushiness GetNavigationPushiness() const {return navPushiness_; }
+
+    /// Get serialized data of internal state
+    PODVector<unsigned char> GetAgentDataAttr() const;
+    /// Set serialized data of internal state
+    void SetAgentDataAttr(const PODVector<unsigned char>& value);
+
+protected:
+    /// Updates the nodes position if updateNodePosition is set. Is called in DetourCrowdManager::Update().
+    virtual void OnCrowdAgentReposition(const Vector3& newPos, const Vector3& newDirection);
+    /// Handle node being assigned.
+    virtual void OnNodeSet(Node* node);
+    /// \todo Handle node transform being dirtied.
+    virtual void OnMarkedDirty(Node* node);
+private:
+    /// Create or re-add 
+    void AddAgentToCrowd();
+    /// Remove 
+    void RemoveAgentFromCrowd();
+
+    WeakPtr<DetourCrowdManager> crowdManager_;
+    /// in DetourCrowd ? 
+    bool inCrowd_;
+    /// DetourCrowd reference to this agent.
+    int agentCrowdId_;
+    /// Reference to poly closest to requested target position.
+    unsigned int targetRef_;         
+    /// Actual target position, closest to that requested.
+    Vector3 targetPosition_;   
+    /// update nodes position ?
+    bool updateNodePosition_;
+    /// Agent's max acceleration.
+    float maxAccel_;
+    /// Agent's max Velocity.
+    float maxSpeed_;
+    /// Agent's radius, if 0 the navigation mesh's setting will be used
+    float radius_;
+    /// Agent's height, if 0 the navigation mesh's setting will be used
+    float height_;
+    /// Agent's assigned navigation filter type, the actual filter is owned by the DetourCrowdManager the agent belongs to
+    unsigned filterType_;
+    /// Agent's NavigationAvoidanceQuality
+    NavigationQuality navQuality_;
+    /// Agent's Navigation Pushiness
+    NavigationPushiness navPushiness_;
+    /// Ignore transform changes, because it came from us
+    bool ignoreTransformChanges_;
+};
+
+}

+ 521 - 0
Source/Urho3D/Navigation/DetourCrowdManager.cpp

@@ -0,0 +1,521 @@
+//
+// Copyright (c) 2008-2015 the Urho3D project.
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+//
+
+#include "Precompiled.h"
+
+#include "../Scene/Component.h"
+#include "../Core/Context.h"
+#include "../Navigation/CrowdAgent.h"
+#include "../Graphics/DebugRenderer.h"
+#include "../Navigation/DetourCrowdManager.h"
+#include "../Navigation/DynamicNavigationMesh.h"
+#include "../IO/Log.h"
+#include "../Navigation/NavigationEvents.h"
+#include "../Navigation/NavigationMesh.h"
+#include "../Scene/Node.h"
+#include "../Core/Profiler.h"
+#include "../Scene/Scene.h"
+#include "../Scene/SceneEvents.h"
+#include "../Container/Vector.h"
+
+#ifdef URHO3D_PHYSICS
+#include "../Physics/PhysicsEvents.h"
+#endif
+
+#include <DetourCrowd/DetourCrowd.h>
+#include <Recast/Recast.h>
+
+#include "../DebugNew.h"
+
+namespace Urho3D
+{
+    
+extern const char* NAVIGATION_CATEGORY;
+
+static const unsigned DEFAULT_MAX_AGENTS = 512;
+
+DetourCrowdManager::DetourCrowdManager(Context* context) :
+    Component(context),
+    maxAgents_(DEFAULT_MAX_AGENTS),
+    crowd_(0),
+    navigationMesh_(0),
+    agentDebug_(NULL)
+{
+    agentBuffer_.Resize(maxAgents_);
+}
+
+DetourCrowdManager::~DetourCrowdManager()
+{
+    dtFreeCrowd(crowd_);
+    if (agentDebug_)
+        delete agentDebug_;
+}
+
+void DetourCrowdManager::RegisterObject(Context* context)
+{
+    context->RegisterFactory<DetourCrowdManager>(NAVIGATION_CATEGORY);
+    
+    ACCESSOR_ATTRIBUTE("Max Agents", GetMaxAgents, SetMaxAgents, unsigned, DEFAULT_MAX_AGENTS, AM_DEFAULT);
+}
+
+void DetourCrowdManager::SetNavigationMesh(NavigationMesh* navMesh)
+{
+    navigationMesh_ = WeakPtr<NavigationMesh>(navMesh);
+    if (navigationMesh_ && navigationMesh_->navMeshQuery_ == 0)
+        navigationMesh_->InitializeQuery();
+    CreateCrowd();
+    MarkNetworkUpdate();
+}
+
+void DetourCrowdManager::SetAreaTypeCost(unsigned filterID, unsigned areaType, float weight)
+{
+    dtQueryFilter* filter = crowd_->getEditableFilter(filterID);
+    if (filter)
+        filter->setAreaCost((int)areaType, weight);
+}
+
+void DetourCrowdManager::SetMaxAgents(unsigned agentCt)
+{
+    maxAgents_ = agentCt;
+    if (crowd_ && crowd_->getAgentCount() > 0)
+        LOGERROR("DetourCrowdManager contains active agents, their state will be lost");
+    agentBuffer_.Resize(maxAgents_);
+    CreateCrowd();
+    if (crowd_)
+    {
+        PODVector<CrowdAgent*> agents = agents_;
+        // Reset the existing values in the agent
+        for (unsigned i = 0; i < agents.Size(); ++i)
+        {
+            agents[i]->inCrowd_ = false;
+            agents[i]->agentCrowdId_ = -1;
+        }
+        // Add the agents back in
+        for (unsigned i = 0; i < agents.Size() && i < maxAgents_; ++i)
+            agents[i]->AddAgentToCrowd();
+        if (agents.Size() > maxAgents_)
+            LOGERROR("DetourCrowdManager: resize left " + String(agents.Size() - maxAgents_) + " agents orphaned");
+    }
+    MarkNetworkUpdate();
+}
+
+NavigationMesh* DetourCrowdManager::GetNavigationMesh()
+{
+    return navigationMesh_.Get();
+}
+
+float DetourCrowdManager::GetAreaTypeCost(unsigned filterID, unsigned areaType) const
+{
+    if (crowd_ && navigationMesh_)
+    {
+        const dtQueryFilter* filter = crowd_->getFilter((int)filterID);
+        if (filter)
+            return filter->getAreaCost((int)areaType);
+    }
+    return 0.0f;
+}
+
+unsigned DetourCrowdManager::GetAgentCount() const
+{
+    if (crowd_)
+        return crowd_->getAgentCount();
+    return 0;
+}
+
+void DetourCrowdManager::DrawDebugGeometry(DebugRenderer* debug, bool depthTest)
+{
+    if (debug && navigationMesh_.NotNull() && crowd_)
+    {
+        // Current position-to-target line
+        for (int i = 0; i < crowd_->getAgentCount(); i++)
+        {
+            const dtCrowdAgent* ag = crowd_->getAgent(i);
+            if (!ag->active)
+                continue;
+
+            Color color(0.6f, 0.2f, 0.2f, 1.0f);
+
+            // Render line to target:
+            Vector3 pos1(ag->npos[0], ag->npos[1], ag->npos[2]);
+            Vector3 pos2;
+            for (int i = 0; i < ag->ncorners; ++i)
+            {
+                pos2.x_ = ag->cornerVerts[i * 3];
+                pos2.y_ = ag->cornerVerts[i * 3 + 1];
+                pos2.z_ = ag->cornerVerts[i * 3 + 2];
+                debug->AddLine(pos1, pos2, color, depthTest);
+                pos1 = pos2;
+            }
+            pos2.x_ = ag->targetPos[0];
+            pos2.y_ = ag->targetPos[1];
+            pos2.z_ = ag->targetPos[2];
+            debug->AddLine(pos1, pos2, color, depthTest);
+
+            // Target circle
+            debug->AddSphere(Sphere(pos2, 0.5f), color, depthTest);
+        }
+    }
+}
+
+bool DetourCrowdManager::CreateCrowd()
+{
+    if (!navigationMesh_) 
+        return false;
+
+    if (navigationMesh_->navMesh_ == 0)
+        return false;
+
+    if (crowd_)
+    {
+        dtFreeCrowd(crowd_);
+        crowd_ = 0;
+    }
+
+    if (crowd_ == 0)
+        crowd_ = dtAllocCrowd();
+    if (agentDebug_ == NULL)
+        agentDebug_ = new dtCrowdAgentDebugInfo();
+
+    // Initialize the crowd
+    bool b = crowd_->init(maxAgents_, navigationMesh_->GetAgentRadius(), navigationMesh_->navMesh_);
+    if (b == false)
+    {
+        LOGERROR("Could not initialize DetourCrowd");
+        return false;
+    }
+
+    // Setup local avoidance params to different qualities.
+    dtObstacleAvoidanceParams params;
+    memcpy(&params, crowd_->getObstacleAvoidanceParams(0), sizeof(dtObstacleAvoidanceParams));
+
+    // Low (11)
+    params.velBias = 0.5f;
+    params.adaptiveDivs = 5;
+    params.adaptiveRings = 2;
+    params.adaptiveDepth = 1;
+    crowd_->setObstacleAvoidanceParams(0, &params);
+
+    // Medium (22)
+    params.velBias = 0.5f;
+    params.adaptiveDivs = 5;
+    params.adaptiveRings = 2;
+    params.adaptiveDepth = 2;
+    crowd_->setObstacleAvoidanceParams(1, &params);
+
+    // Good (45)
+    params.velBias = 0.5f;
+    params.adaptiveDivs = 7;
+    params.adaptiveRings = 2;
+    params.adaptiveDepth = 3;
+    crowd_->setObstacleAvoidanceParams(2, &params);
+
+    // High (66)
+    params.velBias = 0.5f;
+    params.adaptiveDivs = 7;
+    params.adaptiveRings = 3;
+    params.adaptiveDepth = 3;
+    crowd_->setObstacleAvoidanceParams(3, &params);
+
+    return true;
+}
+
+int DetourCrowdManager::AddAgent(CrowdAgent* agent, const Vector3& pos)
+{
+    if (crowd_ == 0 || navigationMesh_.Expired())
+        return -1;
+    dtCrowdAgentParams params;
+    if (agent->radius_ <= 0.0f)
+        params.radius = agent->radius_ = navigationMesh_->GetAgentRadius();
+    else
+        params.radius = agent->radius_;
+    if (agent->height_ <= 0.0f)
+        params.height = agent->height_ = navigationMesh_->GetAgentHeight();
+    else
+        params.height = agent->height_;
+    params.queryFilterType = (unsigned char)agent->filterType_;
+    params.maxAcceleration = agent->maxAccel_;
+    params.maxSpeed = agent->maxSpeed_;
+    params.collisionQueryRange = params.radius * 8.0f;
+    params.pathOptimizationRange = params.radius * 30.0f;
+    params.updateFlags = DT_CROWD_ANTICIPATE_TURNS
+        | DT_CROWD_OPTIMIZE_VIS
+        | DT_CROWD_OPTIMIZE_TOPO
+        | DT_CROWD_OBSTACLE_AVOIDANCE;
+    params.obstacleAvoidanceType = 3;
+    params.separationWeight = 2.0f;
+    params.queryFilterType = 0;
+    dtPolyRef polyRef;
+    float nearestPos[3];
+    rcVcopy(nearestPos, &pos.x_);
+    dtStatus status = navigationMesh_->navMeshQuery_->findNearestPoly(
+        pos.Data(),
+        crowd_->getQueryExtents(),
+        crowd_->getFilter(agent->filterType_),
+        &polyRef,
+        nearestPos);
+
+    
+    const int agentID = crowd_->addAgent(nearestPos, &params);
+    if (agentID != -1)
+        agents_.Push(agent);
+    return agentID;
+}
+
+void DetourCrowdManager::RemoveAgent(CrowdAgent* agent)
+{
+    if (crowd_ == 0)
+        return;
+    // Clear user data
+    dtCrowdAgent* agt = crowd_->getEditableAgent(agent->GetAgentCrowdId());
+    if (agt)
+        agt->params.userData = 0;
+    crowd_->removeAgent(agent->GetAgentCrowdId());
+    agents_.Remove(agent);
+}
+
+void DetourCrowdManager::UpdateAgentNavigationQuality(CrowdAgent* agent, NavigationQuality nq)
+{
+    if (crowd_ == 0)
+        return;
+
+    dtCrowdAgentParams params = crowd_->getAgent(agent->GetAgentCrowdId())->params;
+    switch (nq)
+    {
+    case NAVIGATIONQUALITY_LOW:
+        {
+            params.updateFlags &= ~0
+                & ~DT_CROWD_ANTICIPATE_TURNS
+                & ~DT_CROWD_OPTIMIZE_VIS
+                & ~DT_CROWD_OPTIMIZE_TOPO
+                & ~DT_CROWD_OBSTACLE_AVOIDANCE;
+        }
+        break;
+
+    case NAVIGATIONQUALITY_MEDIUM:
+        {
+            params.updateFlags |= 0;
+            params.updateFlags &= ~0
+                & ~DT_CROWD_OBSTACLE_AVOIDANCE
+                & ~DT_CROWD_ANTICIPATE_TURNS
+                & ~DT_CROWD_OPTIMIZE_VIS
+                & ~DT_CROWD_OPTIMIZE_TOPO;
+        }
+        break;
+
+    case NAVIGATIONQUALITY_HIGH:
+        {
+            params.obstacleAvoidanceType = 3;
+            params.updateFlags |= 0
+                | DT_CROWD_ANTICIPATE_TURNS
+                | DT_CROWD_OPTIMIZE_VIS
+                | DT_CROWD_OPTIMIZE_TOPO
+                | DT_CROWD_OBSTACLE_AVOIDANCE;
+        }
+        break;
+    }
+
+    crowd_->updateAgentParameters(agent->GetAgentCrowdId(), &params);
+}
+
+void DetourCrowdManager::UpdateAgentPushiness(CrowdAgent* agent, NavigationPushiness pushiness)
+{
+    if (crowd_ == 0)
+        return;
+
+    dtCrowdAgentParams params = crowd_->getAgent(agent->GetAgentCrowdId())->params;
+    switch (pushiness)
+    {
+    case PUSHINESS_LOW:
+        params.separationWeight = 4.0f;
+        params.collisionQueryRange = params.radius * 16.0f;
+        break;
+
+    case PUSHINESS_MEDIUM:
+        params.separationWeight = 2.0f;
+        params.collisionQueryRange = params.radius * 8.0f;
+        break;
+
+    case PUSHINESS_HIGH:
+        params.separationWeight = 0.5f;
+        params.collisionQueryRange = params.radius * 1.0f;
+        break;
+    }
+    crowd_->updateAgentParameters(agent->GetAgentCrowdId(), &params);
+}
+
+bool DetourCrowdManager::SetAgentTarget(CrowdAgent* agent, Vector3 target)
+{
+    if (crowd_ == 0)
+        return false;
+    dtPolyRef polyRef;
+    float nearestPos[3];
+    dtStatus status = navigationMesh_->navMeshQuery_->findNearestPoly(
+        target.Data(),
+        crowd_->getQueryExtents(),
+        crowd_->getFilter(agent->filterType_),
+        &polyRef,
+        nearestPos);
+
+    if (!dtStatusFailed(status))
+    {
+        if (!crowd_->requestMoveTarget(agent->GetAgentCrowdId(), polyRef, nearestPos))
+            return false;
+    }
+    else
+        return false;
+    return true;
+}
+
+bool DetourCrowdManager::SetAgentTarget(CrowdAgent* agent, Vector3 target, unsigned int& targetRef)
+{
+    if (crowd_ == 0)
+        return false;
+    float nearestPos[3];
+    dtStatus status = navigationMesh_->navMeshQuery_->findNearestPoly(
+        target.Data(),
+        crowd_->getQueryExtents(),
+        crowd_->getFilter(agent->filterType_),
+        &targetRef,
+        nearestPos);
+
+    if (!dtStatusFailed(status))
+    {
+        if (!crowd_->requestMoveTarget(agent->GetAgentCrowdId(), targetRef, nearestPos))
+            return false;
+        // Return true if detour has determined it can do something with our move target
+        return crowd_->getAgent(agent->GetAgentCrowdId())->targetState != DT_CROWDAGENT_TARGET_FAILED;
+    }
+    else
+        return false;
+    return true;
+}
+
+Vector3 DetourCrowdManager::GetClosestWalkablePosition(Vector3 pos)
+{
+    if (crowd_ == 0)
+        return Vector3();
+    float closest[3];
+    const static float extents[] = { 1.0f, 20.0f, 1.0f };
+    dtPolyRef closestPoly;
+    dtQueryFilter filter;
+    dtStatus status = navigationMesh_->navMeshQuery_->findNearestPoly(
+        pos.Data(),
+        crowd_->getQueryExtents(),
+        &filter,
+        &closestPoly,
+        closest);
+    return Vector3(closest);
+}
+
+void DetourCrowdManager::Update(float delta)
+{
+    if (crowd_ == 0)
+        return;
+
+    PROFILE(UpdateCrowd);
+        
+    crowd_->update(delta, agentDebug_);
+
+    memset(&agentBuffer_[0], 0, maxAgents_ * sizeof(dtCrowdAgent*));
+    const int count = crowd_->getActiveAgents(&agentBuffer_[0], maxAgents_);
+    
+    {
+        PROFILE(ApplyCrowdUpdates);
+        for (int i = 0; i < count; i++)
+        {
+            dtCrowdAgent* agent = agentBuffer_[i];
+            if (agent)
+            {
+                CrowdAgent* crowdAgent = static_cast<CrowdAgent*>(agent->params.userData);	
+                if (crowdAgent)
+                    crowdAgent->OnCrowdAgentReposition(Vector3(agent->npos), Vector3(agent->vel));
+            }
+        }
+    }
+}
+
+const dtCrowdAgent* DetourCrowdManager::GetCrowdAgent(int agent)
+{
+    if (crowd_ == 0)
+        return NULL;
+    return crowd_->getAgent(agent);
+}
+
+dtCrowd* DetourCrowdManager::GetCrowd()
+{
+    return crowd_;
+}
+
+void DetourCrowdManager::HandleFixedUpdate(StringHash eventType, VariantMap& eventData)
+{
+    using namespace PhysicsPreStep;
+
+    if (IsEnabledEffective())
+        Update(eventData[P_TIMESTEP].GetFloat());
+}
+
+void DetourCrowdManager::HandleNavMeshFullRebuild(StringHash eventType, VariantMap& eventData)
+{
+    using namespace NavigationMeshRebuilt;
+
+    // The mesh being rebuilt may not have existed before
+    NavigationMesh* navMesh = static_cast<NavigationMesh*>(eventData[P_MESH].GetPtr());
+    if (!navigationMesh_ || crowd_ == 0)
+    {
+        SetNavigationMesh(navMesh);
+
+        // Scan for existing agents that are potentially important
+        PODVector<Node*> agents;
+        GetScene()->GetChildrenWithComponent<CrowdAgent>(agents, true);
+        for (unsigned i = 0; i < agents.Size(); ++i)
+        {
+            CrowdAgent* agent = agents[i]->GetComponent<CrowdAgent>();
+            if (agent && agent->IsEnabledEffective())
+                agent->AddAgentToCrowd();
+        }
+    }
+}
+
+void DetourCrowdManager::OnNodeSet(Node* node)
+{
+    // Subscribe to the scene subsystem update, which will trigger the crowd update step, and grab a reference
+    // to the scene's NavigationMesh
+    if (node)
+    {
+        // No physics, then no navigation? Correct or Not?
+#ifdef URHO3D_PHYSICS
+        SubscribeToEvent(E_PHYSICSPRESTEP, HANDLER(DetourCrowdManager, HandleFixedUpdate));
+#endif
+        SubscribeToEvent(node, E_NAVIGATION_MESH_REBUILT, HANDLER(DetourCrowdManager, HandleNavMeshFullRebuild));
+            
+        NavigationMesh* mesh = GetScene()->GetComponent<NavigationMesh>();
+        if (!mesh)
+            mesh = GetScene()->GetComponent<DynamicNavigationMesh>();
+        if (mesh) {
+            SetNavigationMesh(mesh);
+        }
+        else
+            LOGERROR("DetourCrowdManager requires an existing navigation mesh");
+    }
+}
+
+}

+ 139 - 0
Source/Urho3D/Navigation/DetourCrowdManager.h

@@ -0,0 +1,139 @@
+//
+// Copyright (c) 2008-2015 the Urho3D project.
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+//
+
+#pragma once
+
+#include "../Scene/Component.h"
+
+class dtCrowd;
+struct dtCrowdAgent;
+struct dtCrowdAgentDebugInfo;
+
+namespace Urho3D
+{
+
+class CrowdAgent;
+class NavigationMesh;
+
+enum NavigationQuality
+{
+    NAVIGATIONQUALITY_LOW = 0,
+    NAVIGATIONQUALITY_MEDIUM = 1,
+    NAVIGATIONQUALITY_HIGH = 2
+};
+
+enum NavigationPushiness
+{
+    PUSHINESS_LOW,
+    PUSHINESS_MEDIUM,
+    PUSHINESS_HIGH
+};
+
+
+/// Detour Crowd Simulation Scene Component. Should be added only to the root scene node.
+/// Agent's radius and height is set through the navigation mesh.
+/// \todo support multiple agent's radii and heights
+class URHO3D_API DetourCrowdManager : public Component
+{
+    OBJECT(DetourCrowdManager);
+    friend class CrowdAgent;
+              
+public:
+    /// Construct.
+    DetourCrowdManager(Context* context);
+    /// Destruct.
+    virtual ~DetourCrowdManager();
+    /// Register object factory.
+    static void RegisterObject(Context* context);
+
+    /// Assigns the navigation mesh for the crowd.
+    void SetNavigationMesh(NavigationMesh* navMesh);
+    /// Sets the cost of an area-type for the specified navigation filter type
+    void SetAreaTypeCost(unsigned filterTypeID, unsigned areaType, float weight);
+    /// Set the maximum number of agents
+    void SetMaxAgents(unsigned agentCt);
+
+    /// Get the Navigation mesh assigned to the crowd.
+    NavigationMesh* GetNavigationMesh();
+    /// Gets the cost of an area-type for the specified navigation filter type
+    float GetAreaTypeCost(unsigned filterTypeID, unsigned areaType) const;
+    /// Get the maximum number of agents
+    unsigned GetMaxAgents() const { return maxAgents_; }
+    /// Get the current number of active agents
+    unsigned GetAgentCount() const;
+
+    /// Draw the agents' pathing debug data. 
+    virtual void DrawDebugGeometry(DebugRenderer* debug, bool depthTest);
+    /// Get the currently included agents
+    PODVector<CrowdAgent*> GetActiveAgents() const { return agents_; }
+    /// Create detour crowd component for the specified navigation mesh.
+    bool CreateCrowd();
+
+protected:
+    /// Create and adds an detour crowd agent, Agent's radius and height is set through the navigation mesh!
+    int AddAgent(CrowdAgent* agent, const Vector3& pos);
+    /// Removes the detour crowd agent.
+    void RemoveAgent(CrowdAgent* agent);
+
+    /// Update the Navigation Agent's Avoidance Quality for the specified agent.
+    void UpdateAgentNavigationQuality(CrowdAgent* agent, NavigationQuality nq);
+    /// Update the Navigation Agent's Pushiness for the specified agent.
+    void UpdateAgentPushiness(CrowdAgent* agent, NavigationPushiness pushiness);
+
+    /// Sets the move target for the specified agent.
+    bool SetAgentTarget(CrowdAgent* agent, Vector3 target);
+    /// Sets the move target for the specified agent.
+    bool SetAgentTarget(CrowdAgent* agent, Vector3 target, unsigned int& targetRef);
+
+    /// Gets the closest walkable position.
+    Vector3 GetClosestWalkablePosition(Vector3 pos);
+
+protected:
+    /// Update the crowd simulation
+    void Update(float delta);
+    /// Handle node being assigned.
+    virtual void OnNodeSet(Node* node);
+    /// Gets the detour crowd agent.
+    const dtCrowdAgent* GetCrowdAgent(int agent);
+    /// Gets the internal detour crowd component.
+    dtCrowd* GetCrowd();
+
+private:
+    /// Handle the scene subsystem update event, step simulation here.
+    void HandleFixedUpdate(StringHash eventType, VariantMap& eventData);
+    /// Handle full rebuilds of the navigation mesh
+    void HandleNavMeshFullRebuild(StringHash eventType, VariantMap& eventData);
+
+    /// internal crowd component
+    dtCrowd* crowd_;
+    /// NavigationMesh for which the crowd was created
+    WeakPtr<NavigationMesh> navigationMesh_;
+    /// max agents for the crowd 
+    unsigned maxAgents_;	
+    /// internal debug information 
+    dtCrowdAgentDebugInfo* agentDebug_;
+    /// Container for fetching agents from DetourCrowd during update
+    PODVector<dtCrowdAgent*> agentBuffer_;
+    PODVector<CrowdAgent*> agents_;
+};
+
+}

+ 868 - 0
Source/Urho3D/Navigation/DynamicNavigationMesh.cpp

@@ -0,0 +1,868 @@
+//
+// Copyright (c) 2008-2015 the Urho3D project.
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+//
+
+#include "Precompiled.h"
+
+#include "../Navigation/DynamicNavigationMesh.h"
+
+#include "../Math/BoundingBox.h"
+#include "../Core/Context.h"
+#include "../Graphics/DebugRenderer.h"
+#include "../IO/Log.h"
+#include "../IO/MemoryBuffer.h"
+#include "../Navigation/NavBuildData.h"
+#include "../Navigation/NavigationEvents.h"
+#include "../Scene/Node.h"
+#include "../Navigation/OffMeshConnection.h"
+#include "../Core/Profiler.h"
+#include "../Navigation/Obstacle.h"
+#include "../Scene/Scene.h"
+#include "../Scene/SceneEvents.h"
+
+#include <LZ4/lz4.h>
+#include <cfloat>
+#include <Detour/DetourNavMesh.h>
+#include <Detour/DetourNavMeshBuilder.h>
+#include <Detour/DetourNavMeshQuery.h>
+#include <DetourTileCache/DetourTileCache.h>
+#include <DetourTileCache/DetourTileCacheBuilder.h>
+#include <Recast/Recast.h>
+#include <Recast/RecastAlloc.h>
+
+//DebugNew is deliberately not used because the macro 'free' conflicts DetourTileCache's LinearAllocator interface
+//#include "../DebugNew.h"
+
+#define TILECACHE_MAXLAYERS 128
+
+namespace Urho3D
+{
+    
+extern const char* NAVIGATION_CATEGORY;
+
+static const int DEFAULT_MAX_OBSTACLES = 1024;
+
+struct DynamicNavigationMesh::TileCacheData
+{
+    unsigned char* data;
+    int dataSize;
+};
+
+struct TileCompressor : public dtTileCacheCompressor
+{
+    virtual int maxCompressedSize(const int bufferSize)
+    {
+        return (int)(bufferSize* 1.05f);
+    }
+
+    virtual dtStatus compress(const unsigned char* buffer, const int bufferSize,
+        unsigned char* compressed, const int /*maxCompressedSize*/, int* compressedSize)
+    {
+        *compressedSize = LZ4_compress((const char*)buffer, (char*)compressed, bufferSize);
+        return DT_SUCCESS;
+    }
+
+    virtual dtStatus decompress(const unsigned char* compressed, const int compressedSize,
+        unsigned char* buffer, const int maxBufferSize, int* bufferSize)
+    {
+        *bufferSize = LZ4_decompress_safe((const char*)compressed, (char*)buffer, compressedSize, maxBufferSize);
+        return *bufferSize < 0 ? DT_FAILURE : DT_SUCCESS;
+    }
+};
+
+struct MeshProcess : public dtTileCacheMeshProcess
+{
+    DynamicNavigationMesh* owner_;
+    PODVector<Vector3> offMeshVertices_;
+    PODVector<float> offMeshRadii_;
+    PODVector<unsigned short> offMeshFlags_;
+    PODVector<unsigned char> offMeshAreas_;
+    PODVector<unsigned char> offMeshDir_;
+
+    inline MeshProcess(DynamicNavigationMesh* owner) :
+        owner_(owner)
+    {
+    }
+
+    virtual void process(struct dtNavMeshCreateParams* params, unsigned char* polyAreas, unsigned short* polyFlags)
+    {
+        // Update poly flags from areas.
+        // \todo Assignment of flags from areas?
+        for (int i = 0; i < params->polyCount; ++i)
+        {
+            if (polyAreas[i] != RC_NULL_AREA)
+                polyFlags[i] = RC_WALKABLE_AREA;
+        }
+
+        BoundingBox bounds;
+        rcVcopy(&bounds.min_.x_, params->bmin);
+        rcVcopy(&bounds.max_.x_, params->bmin);
+        
+        // collect off-mesh connections
+        PODVector<OffMeshConnection*> offMeshConnections = owner_->CollectOffMeshConnections(bounds);
+
+        if (offMeshConnections.Size() > 0)
+        {
+            if (offMeshConnections.Size() != offMeshRadii_.Size())
+            {
+                Matrix3x4 inverse = owner_->GetNode()->GetWorldTransform().Inverse();
+                ClearConnectionData();
+                for (unsigned i = 0; i < offMeshConnections.Size(); ++i)
+                {
+                    OffMeshConnection* connection = offMeshConnections[i];
+                    Vector3 start = inverse * connection->GetNode()->GetWorldPosition();
+                    Vector3 end = inverse * connection->GetEndPoint()->GetWorldPosition();
+
+                    offMeshVertices_.Push(start);
+                    offMeshVertices_.Push(end);
+                    offMeshRadii_.Push(connection->GetRadius());
+                    offMeshFlags_.Push(connection->GetMask());
+                    offMeshAreas_.Push((unsigned char)connection->GetAreaID());
+                    offMeshDir_.Push(connection->IsBidirectional() ? DT_OFFMESH_CON_BIDIR : 0);
+                }
+            }
+            params->offMeshConCount = offMeshRadii_.Size();
+            params->offMeshConVerts = &offMeshVertices_[0].x_;
+            params->offMeshConRad = &offMeshRadii_[0];
+            params->offMeshConFlags = &offMeshFlags_[0];
+            params->offMeshConAreas = &offMeshAreas_[0];
+            params->offMeshConDir = &offMeshDir_[0];
+        }
+    }
+
+    void ClearConnectionData()
+    {
+        offMeshVertices_.Clear();
+        offMeshRadii_.Clear();
+        offMeshFlags_.Clear();
+        offMeshAreas_.Clear();
+        offMeshDir_.Clear();
+    }
+};
+
+
+// From the Detour/Recast Sample_TempObstacles.cpp
+struct LinearAllocator : public dtTileCacheAlloc
+{
+    unsigned char* buffer;
+    int capacity;
+    int top;
+    int high;
+
+    LinearAllocator(const int cap) : buffer(0), capacity(0), top(0), high(0)
+    {
+        resize(cap);
+    }
+
+    ~LinearAllocator()
+    {
+        dtFree(buffer);
+    }
+
+    void resize(const int cap)
+    {
+        if (buffer) 
+            dtFree(buffer);
+        buffer = (unsigned char*)dtAlloc(cap, DT_ALLOC_PERM);
+        capacity = cap;
+    }
+
+    virtual void reset()
+    {
+        high = Max(high, top);
+        top = 0;
+    }
+
+    virtual void* alloc(const int size)
+    {
+        if (!buffer)
+            return 0;
+        if (top + size > capacity)
+            return 0;
+        unsigned char* mem = &buffer[top];
+        top += size;
+        return mem;
+    }
+
+    virtual void free(void*)
+    {
+    }
+};
+
+
+DynamicNavigationMesh::DynamicNavigationMesh(Context* context) :
+    NavigationMesh(context),
+    tileCache_(0),
+    maxObstacles_(1024)
+{
+    //64 is the largest tile-size that DetourTileCache will tolerate without silently failing
+    tileSize_ = 64; 
+    partitionType_ = NAVMESH_PARTITION_MONOTONE;
+    allocator_ = new LinearAllocator(32000); //32kb to start
+    compressor_ = new TileCompressor();
+    meshProcessor_ = new MeshProcess(this);
+}
+
+DynamicNavigationMesh::~DynamicNavigationMesh()
+{
+    ReleaseNavigationMesh();
+    delete allocator_;
+    delete compressor_;
+    delete meshProcessor_;
+}
+
+void DynamicNavigationMesh::RegisterObject(Context* context)
+{
+    context->RegisterFactory<DynamicNavigationMesh>(NAVIGATION_CATEGORY);
+
+    COPY_BASE_ATTRIBUTES(NavigationMesh);
+    ATTRIBUTE("Max Obstacles", unsigned, maxObstacles_, DEFAULT_MAX_OBSTACLES, AM_DEFAULT);
+}
+
+bool DynamicNavigationMesh::Build()
+{
+    PROFILE(BuildNavigationMesh);
+    // Release existing navigation data and zero the bounding box
+    ReleaseNavigationMesh();
+
+    if (!node_)
+        return false;
+
+    if (!node_->GetWorldScale().Equals(Vector3::ONE))
+        LOGWARNING("Navigation mesh root node has scaling. Agent parameters may not work as intended");
+
+    Vector<NavigationGeometryInfo> geometryList;
+    CollectGeometries(geometryList);
+
+    if (geometryList.Empty())
+        return true; // Nothing to do
+
+    // Build the combined bounding box
+    for (unsigned i = 0; i < geometryList.Size(); ++i)
+        boundingBox_.Merge(geometryList[i].boundingBox_);
+
+    // Expand bounding box by padding
+    boundingBox_.min_ -= padding_;
+    boundingBox_.max_ += padding_;
+
+    {
+        PROFILE(BuildNavigationMesh);
+
+        // Calculate number of tiles
+        int gridW = 0, gridH = 0;
+        float tileEdgeLength = (float)tileSize_ * cellSize_;
+        rcCalcGridSize(&boundingBox_.min_.x_, &boundingBox_.max_.x_, cellSize_, &gridW, &gridH);
+        numTilesX_ = (gridW + tileSize_ - 1) / tileSize_;
+        numTilesZ_ = (gridH + tileSize_ - 1) / tileSize_;
+
+        // Calculate max. number of tiles and polygons, 22 bits available to identify both tile & polygon within tile
+        unsigned maxTiles = NextPowerOfTwo(numTilesX_ * numTilesZ_) * TILECACHE_MAXLAYERS;
+        unsigned tileBits = 0;
+        unsigned temp = maxTiles;
+        while (temp > 1)
+        {
+            temp >>= 1;
+            ++tileBits;
+        }
+
+        unsigned maxPolys = 1 << (22 - tileBits);
+
+        dtNavMeshParams params;
+        rcVcopy(params.orig, &boundingBox_.min_.x_);
+        params.tileWidth = tileEdgeLength;
+        params.tileHeight = tileEdgeLength;
+        params.maxTiles = maxTiles;
+        params.maxPolys = maxPolys;
+
+        navMesh_ = dtAllocNavMesh();
+        if (!navMesh_)
+        {
+            LOGERROR("Could not allocate navigation mesh");
+            return false;
+        }
+
+        if (dtStatusFailed(navMesh_->init(&params)))
+        {
+            LOGERROR("Could not initialize navigation mesh");
+            ReleaseNavigationMesh();
+            return false;
+        }
+
+        dtTileCacheParams tileCacheParams;
+        memset(&tileCacheParams, 0, sizeof(tileCacheParams));
+        rcVcopy(tileCacheParams.orig, &boundingBox_.min_.x_);
+        tileCacheParams.ch = cellHeight_;
+        tileCacheParams.cs = cellSize_;
+        tileCacheParams.width = tileSize_;
+        tileCacheParams.height = tileSize_;
+        tileCacheParams.maxSimplificationError = edgeMaxError_;
+        tileCacheParams.maxTiles = numTilesX_ * numTilesZ_ * TILECACHE_MAXLAYERS;
+        tileCacheParams.maxObstacles = maxObstacles_;
+        // Settings from NavigationMesh
+        tileCacheParams.walkableClimb = agentMaxClimb_;
+        tileCacheParams.walkableHeight = agentHeight_;
+        tileCacheParams.walkableRadius = agentRadius_;
+
+        tileCache_ = dtAllocTileCache();
+        if (!tileCache_)
+        {
+            LOGERROR("Could not allocate tile cache");
+            ReleaseNavigationMesh();
+            return false;
+        }
+
+        if (dtStatusFailed(tileCache_->init(&tileCacheParams, allocator_, compressor_, meshProcessor_)))
+        {
+            LOGERROR("Could not initialize tile cache");
+            ReleaseNavigationMesh();
+            return false;
+        }
+
+        // Build each tile
+        unsigned numTiles = 0;
+
+        for (int z = 0; z < numTilesZ_; ++z)
+        {
+            for (int x = 0; x < numTilesX_; ++x)
+            {
+                TileCacheData tiles[TILECACHE_MAXLAYERS];
+                int layerCt = BuildTile(geometryList, x, z, tiles);
+                for (int i = 0; i < layerCt; ++i)
+                {
+                    dtCompressedTileRef tileRef;
+                    int status = tileCache_->addTile(tiles[i].data, tiles[i].dataSize, DT_COMPRESSEDTILE_FREE_DATA, &tileRef);
+                    if (dtStatusFailed(status))
+                    {
+                        dtFree(tiles[i].data);
+                        tiles[i].data = 0x0;
+                    }
+                }
+                ++numTiles;
+            }
+            for (int x = 0; x < numTilesX_; ++x)
+                tileCache_->buildNavMeshTilesAt(x, z, navMesh_);
+        }
+            
+        // For a full build it's necessary to update the nav mesh
+        // not doing so will cause dependent components to crash, like DetourCrowdManager
+        tileCache_->update(0, navMesh_);
+
+        LOGDEBUG("Built navigation mesh with " + String(numTiles) + " tiles");
+
+        // Send a notification event to concerned parties that we've been fully rebuilt
+        {
+            using namespace NavigationMeshRebuilt;
+            VariantMap& buildEventParams = GetContext()->GetEventDataMap();
+            buildEventParams[P_NODE] = node_;
+            buildEventParams[P_MESH] = this;
+            SendEvent(E_NAVIGATION_MESH_REBUILT, buildEventParams);
+        }
+
+        // Scan for obstacles to insert into us
+        PODVector<Node*> obstacles;
+        GetScene()->GetChildrenWithComponent<Obstacle>(obstacles, true);
+        for (unsigned i = 0; i < obstacles.Size(); ++i)
+        {
+            Obstacle* obs = obstacles[i]->GetComponent<Obstacle>();
+            if (obs && obs->IsEnabledEffective())
+                AddObstacle(obs);
+        }
+
+        return true;
+    }
+}
+
+bool DynamicNavigationMesh::Build(const BoundingBox& boundingBox)
+{
+    PROFILE(BuildPartialNavigationMesh);
+
+    if (!node_)
+        return false;
+
+    if (!navMesh_)
+    {
+        LOGERROR("Navigation mesh must first be built fully before it can be partially rebuilt");
+        return false;
+    }
+
+    if (!node_->GetWorldScale().Equals(Vector3::ONE))
+        LOGWARNING("Navigation mesh root node has scaling. Agent parameters may not work as intended");
+
+    BoundingBox localSpaceBox = boundingBox.Transformed(node_->GetWorldTransform().Inverse());
+
+    float tileEdgeLength = (float)tileSize_ * cellSize_;
+
+    Vector<NavigationGeometryInfo> geometryList;
+    CollectGeometries(geometryList);
+
+    int sx = Clamp((int)((localSpaceBox.min_.x_ - boundingBox_.min_.x_) / tileEdgeLength), 0, numTilesX_ - 1);
+    int sz = Clamp((int)((localSpaceBox.min_.z_ - boundingBox_.min_.z_) / tileEdgeLength), 0, numTilesZ_ - 1);
+    int ex = Clamp((int)((localSpaceBox.max_.x_ - boundingBox_.min_.x_) / tileEdgeLength), 0, numTilesX_ - 1);
+    int ez = Clamp((int)((localSpaceBox.max_.z_ - boundingBox_.min_.z_) / tileEdgeLength), 0, numTilesZ_ - 1);
+
+    unsigned numTiles = 0;
+
+    for (int z = sz; z <= ez; ++z)
+    {
+        for (int x = sx; x <= ex; ++x)
+        {
+            dtCompressedTileRef existing[TILECACHE_MAXLAYERS];
+            const int existingCt = tileCache_->getTilesAt(x, z, existing, TILECACHE_MAXLAYERS);
+            for (int i = 0; i < existingCt; ++i)
+            {
+                unsigned char* data = 0x0;
+                if (!dtStatusFailed(tileCache_->removeTile(existing[i], &data, 0)) && data != 0x0)
+                    dtFree(data);
+            }
+
+            TileCacheData tiles[TILECACHE_MAXLAYERS];
+            int layerCt = BuildTile(geometryList, x, z, tiles);
+            for (int i = 0; i < layerCt; ++i)
+            {
+                dtCompressedTileRef tileRef;
+                int status = tileCache_->addTile(tiles[i].data, tiles[i].dataSize, DT_COMPRESSEDTILE_FREE_DATA, &tileRef);
+                if (dtStatusFailed(status))
+                {
+                    dtFree(tiles[i].data);
+                    tiles[i].data = 0x0;
+                }
+                else
+                {
+                    tileCache_->buildNavMeshTile(tileRef, navMesh_);
+                    ++numTiles;
+                }
+            }
+        }
+    }
+
+    LOGDEBUG("Rebuilt " + String(numTiles) + " tiles of the navigation mesh");
+    return true;
+}
+
+void DynamicNavigationMesh::SetNavigationDataAttr(const PODVector<unsigned char>& value)
+{
+    ReleaseNavigationMesh();
+        
+    if (value.Empty())
+        return;
+
+    MemoryBuffer buffer(value);
+    boundingBox_ = buffer.ReadBoundingBox();
+    numTilesX_ = buffer.ReadInt();
+    numTilesZ_ = buffer.ReadInt();
+
+    dtNavMeshParams params;
+    buffer.Read(&params, sizeof(dtNavMeshParams));
+
+    navMesh_ = dtAllocNavMesh();
+    if (!navMesh_)
+    {
+        LOGERROR("Could not allocate navigation mesh");
+        return;
+    }
+
+    if (dtStatusFailed(navMesh_->init(&params)))
+    {
+        LOGERROR("Could not initialize navigation mesh");
+        ReleaseNavigationMesh();
+        return;
+    }
+
+    dtTileCacheParams tcParams;
+    buffer.Read(&tcParams, sizeof(tcParams));
+
+    tileCache_ = dtAllocTileCache();
+    if (!tileCache_)
+    {
+        LOGERROR("Could not allocate tile cache");
+        ReleaseNavigationMesh();
+        return;
+    }
+    if (dtStatusFailed(tileCache_->init(&tcParams, allocator_, compressor_, meshProcessor_)))
+    {
+        LOGERROR("Could not initialize tile cache");
+        ReleaseNavigationMesh();
+        return;
+    }
+
+    while (!buffer.IsEof())
+    {
+        dtTileCacheLayerHeader header;
+        buffer.Read(&header, sizeof(dtTileCacheLayerHeader));
+        const int dataSize = buffer.ReadInt();
+        unsigned char* data = (unsigned char*)dtAlloc(dataSize, DT_ALLOC_PERM);
+        buffer.Read(data, dataSize);
+
+        if (dtStatusFailed(tileCache_->addTile(data, dataSize, DT_TILE_FREE_DATA, 0)))
+        {
+            LOGERROR("Failed to add tile");
+            dtFree(data);
+            return;
+        }
+    }
+
+    for (int x = 0; x < numTilesX_; ++x)
+    {
+        for (int z = 0; z < numTilesZ_; ++z)
+            tileCache_->buildNavMeshTilesAt(x, z, navMesh_);
+    }
+
+    tileCache_->update(0, navMesh_);
+}
+
+PODVector<unsigned char> DynamicNavigationMesh::GetNavigationDataAttr() const
+{
+    VectorBuffer ret;
+    if (navMesh_ && tileCache_)
+    {
+        ret.WriteBoundingBox(boundingBox_);
+        ret.WriteInt(numTilesX_);
+        ret.WriteInt(numTilesZ_);
+
+        const dtNavMeshParams* params = navMesh_->getParams();
+        ret.Write(params, sizeof(dtNavMeshParams));
+
+        const dtTileCacheParams* tcParams = tileCache_->getParams();
+        ret.Write(tcParams, sizeof(dtTileCacheParams));
+
+        for (int z = 0; z < numTilesZ_; ++z)
+        {
+            for (int x = 0; x < numTilesX_; ++x)
+            {
+                dtCompressedTileRef tiles[TILECACHE_MAXLAYERS];
+                const int ct = tileCache_->getTilesAt(x, z, tiles, TILECACHE_MAXLAYERS);
+                for (int i = 0; i < ct; ++i)
+                {
+                    const dtCompressedTile* tile = tileCache_->getTileByRef(tiles[i]);
+                    if (!tile || !tile->header || !tile->dataSize)
+                        continue; // Don't write "void-space" tiles
+                    // The header conveniently has the majority of the information required
+                    ret.Write(tile->header, sizeof(dtTileCacheLayerHeader));
+                    ret.WriteInt(tile->dataSize);
+                    ret.Write(tile->data, tile->dataSize);
+                }
+            }
+        }
+    }
+    return ret.GetBuffer();
+}
+
+int DynamicNavigationMesh::BuildTile(Vector<NavigationGeometryInfo>& geometryList, int x, int z, TileCacheData* tiles)
+{
+    PROFILE(BuildNavigationMeshTile);
+
+    tileCache_->removeTile(navMesh_->getTileRefAt(x, z, 0), 0, 0);
+
+    float tileEdgeLength = (float)tileSize_ * cellSize_;
+
+    BoundingBox tileBoundingBox(Vector3(
+        boundingBox_.min_.x_ + tileEdgeLength * (float)x,
+        boundingBox_.min_.y_,
+        boundingBox_.min_.z_ + tileEdgeLength * (float)z
+        ),
+        Vector3(
+        boundingBox_.min_.x_ + tileEdgeLength * (float)(x + 1),
+        boundingBox_.max_.y_,
+        boundingBox_.min_.z_ + tileEdgeLength * (float)(z + 1)
+        ));
+
+    DynamicNavBuildData build(allocator_);
+
+    rcConfig cfg;
+    memset(&cfg, 0, sizeof cfg);
+    cfg.cs = cellSize_;
+    cfg.ch = cellHeight_;
+    cfg.walkableSlopeAngle = agentMaxSlope_;
+    cfg.walkableHeight = (int)ceilf(agentHeight_ / cfg.ch);
+    cfg.walkableClimb = (int)floorf(agentMaxClimb_ / cfg.ch);
+    cfg.walkableRadius = (int)ceilf(agentRadius_ / cfg.cs);
+    cfg.maxEdgeLen = (int)(edgeMaxLength_ / cellSize_);
+    cfg.maxSimplificationError = edgeMaxError_;
+    cfg.minRegionArea = (int)sqrtf(regionMinSize_);
+    cfg.mergeRegionArea = (int)sqrtf(regionMergeSize_);
+    cfg.maxVertsPerPoly = 6;
+    cfg.tileSize = tileSize_;
+    cfg.borderSize = cfg.walkableRadius + 3; // Add padding
+    cfg.width = cfg.tileSize + cfg.borderSize * 2;
+    cfg.height = cfg.tileSize + cfg.borderSize * 2;
+    cfg.detailSampleDist = detailSampleDistance_ < 0.9f ? 0.0f : cellSize_ * detailSampleDistance_;
+    cfg.detailSampleMaxError = cellHeight_ * detailSampleMaxError_;
+
+    rcVcopy(cfg.bmin, &tileBoundingBox.min_.x_);
+    rcVcopy(cfg.bmax, &tileBoundingBox.max_.x_);
+    cfg.bmin[0] -= cfg.borderSize * cfg.cs;
+    cfg.bmin[2] -= cfg.borderSize * cfg.cs;
+    cfg.bmax[0] += cfg.borderSize * cfg.cs;
+    cfg.bmax[2] += cfg.borderSize * cfg.cs;
+
+    BoundingBox expandedBox(*reinterpret_cast<Vector3*>(cfg.bmin), *reinterpret_cast<Vector3*>(cfg.bmax));
+    GetTileGeometry(&build, geometryList, expandedBox);
+
+    if (build.vertices_.Empty() || build.indices_.Empty())
+        return 0; // Nothing to do
+
+    build.heightField_ = rcAllocHeightfield();
+    if (!build.heightField_)
+    {
+        LOGERROR("Could not allocate heightfield");
+        return 0;
+    }
+
+    if (!rcCreateHeightfield(build.ctx_, *build.heightField_, cfg.width, cfg.height, cfg.bmin, cfg.bmax, cfg.cs,
+        cfg.ch))
+    {
+        LOGERROR("Could not create heightfield");
+        return 0;
+    }
+
+    unsigned numTriangles = build.indices_.Size() / 3;
+    SharedArrayPtr<unsigned char> triAreas(new unsigned char[numTriangles]);
+    memset(triAreas.Get(), 0, numTriangles);
+
+    rcMarkWalkableTriangles(build.ctx_, cfg.walkableSlopeAngle, &build.vertices_[0].x_, build.vertices_.Size(),
+        &build.indices_[0], numTriangles, triAreas.Get());
+    rcRasterizeTriangles(build.ctx_, &build.vertices_[0].x_, build.vertices_.Size(), &build.indices_[0],
+        triAreas.Get(), numTriangles, *build.heightField_, cfg.walkableClimb);
+    rcFilterLowHangingWalkableObstacles(build.ctx_, cfg.walkableClimb, *build.heightField_);
+    //\todo figure out why behavior of rcFilterLedgeSpans differs between regular tiled nav mesh and cached tiled nav mesh
+    //rcFilterLedgeSpans(build.ctx_, cfg.walkableHeight, cfg.walkableClimb, *build.heightField_);
+    rcFilterWalkableLowHeightSpans(build.ctx_, cfg.walkableHeight, *build.heightField_);
+
+    build.compactHeightField_ = rcAllocCompactHeightfield();
+    if (!build.compactHeightField_)
+    {
+        LOGERROR("Could not allocate create compact heightfield");
+        return 0;
+    }
+    if (!rcBuildCompactHeightfield(build.ctx_, cfg.walkableHeight, cfg.walkableClimb, *build.heightField_,
+        *build.compactHeightField_))
+    {
+        LOGERROR("Could not build compact heightfield");
+        return 0;
+    }
+    if (!rcErodeWalkableArea(build.ctx_, cfg.walkableRadius, *build.compactHeightField_))
+    {
+        LOGERROR("Could not erode compact heightfield");
+        return 0;
+    }
+
+    // area volumes
+    for (unsigned i = 0; i < build.navAreas_.Size(); ++i)
+        rcMarkBoxArea(build.ctx_, &build.navAreas_[i].bounds_.min_.x_, &build.navAreas_[i].bounds_.max_.x_, build.navAreas_[i].areaID_, *build.compactHeightField_);
+
+    if (this->partitionType_ == NAVMESH_PARTITION_WATERSHED)
+    {
+        if (!rcBuildDistanceField(build.ctx_, *build.compactHeightField_))
+        {
+            LOGERROR("Could not build distance field");
+            return 0;
+        }
+        if (!rcBuildRegions(build.ctx_, *build.compactHeightField_, cfg.borderSize, cfg.minRegionArea,
+            cfg.mergeRegionArea))
+        {
+            LOGERROR("Could not build regions");
+            return 0;
+        }
+    }
+    else
+    {
+        if (!rcBuildRegionsMonotone(build.ctx_, *build.compactHeightField_, cfg.borderSize, cfg.minRegionArea, cfg.mergeRegionArea))
+        {
+            LOGERROR("Could not build monotone regions");
+            return 0;
+        }
+    }
+        
+    build.heightFieldLayers_ = rcAllocHeightfieldLayerSet();
+    if (!build.heightFieldLayers_)
+    {
+        LOGERROR("Could not allocate height field layer set");
+        return 0;
+    }
+
+    if (!rcBuildHeightfieldLayers(build.ctx_, *build.compactHeightField_, cfg.borderSize, cfg.walkableHeight, *build.heightFieldLayers_))
+    {
+        LOGERROR("Could not build height field layers");
+        return 0;
+    }
+
+    int retCt = 0;
+    for (int i = 0; i < build.heightFieldLayers_->nlayers; ++i)
+    {
+        dtTileCacheLayerHeader header;
+        header.magic = DT_TILECACHE_MAGIC;
+        header.version = DT_TILECACHE_VERSION;
+        header.tx = x;
+        header.ty = z;
+        header.tlayer = i;
+
+        rcHeightfieldLayer* layer = &build.heightFieldLayers_->layers[i];
+
+        // Tile info.
+        rcVcopy(header.bmin, layer->bmin);
+        rcVcopy(header.bmax, layer->bmax);
+        header.width = (unsigned char)layer->width;
+        header.height = (unsigned char)layer->height;
+        header.minx = (unsigned char)layer->minx;
+        header.maxx = (unsigned char)layer->maxx;
+        header.miny = (unsigned char)layer->miny;
+        header.maxy = (unsigned char)layer->maxy;
+        header.hmin = (unsigned short)layer->hmin;
+        header.hmax = (unsigned short)layer->hmax;
+
+        if (dtStatusFailed(dtBuildTileCacheLayer(compressor_/*compressor*/, &header, layer->heights, layer->areas/*areas*/, layer->cons, &(tiles[retCt].data), &tiles[retCt].dataSize)))
+        {
+            LOGERROR("Failed to build tile cache layers");
+            return 0;
+        }
+        else
+            ++retCt;
+    }
+
+    // Send a notification of the rebuild of this tile to anyone interested
+    {
+        using namespace NavigationAreaRebuilt;
+        VariantMap& eventData = GetContext()->GetEventDataMap();
+        eventData[P_BOUNDSMIN] = Variant(tileBoundingBox.min_);
+        eventData[P_BOUNDSMAX] = Variant(tileBoundingBox.max_);
+        SendEvent(E_NAVIGATION_AREA_REBUILT, eventData);
+    }
+
+    return retCt;
+}
+
+PODVector<OffMeshConnection*> DynamicNavigationMesh::CollectOffMeshConnections(const BoundingBox& bounds)
+{
+    PODVector<OffMeshConnection*> connections;
+    node_->GetComponents<OffMeshConnection>(connections, true);
+    for (unsigned i = 0; i < connections.Size(); ++i)
+    {
+        OffMeshConnection* connection = connections[i];
+        if (!(connection->IsEnabledEffective() && connection->GetEndPoint()))
+        {
+            // discard this connection
+            connections.Erase(i);
+            --i;
+        }
+    }
+
+    return connections;
+}
+
+void DynamicNavigationMesh::ReleaseNavigationMesh()
+{
+    NavigationMesh::ReleaseNavigationMesh();
+    ReleaseTileCache();
+}
+
+void DynamicNavigationMesh::ReleaseTileCache()
+{
+    dtFreeTileCache(tileCache_);
+    tileCache_ = 0;
+}
+
+void DynamicNavigationMesh::OnNodeSet(Node* node)
+{
+    // Subscribe to the scene subsystem update, which will trigger the tile cache to update the nav mesh
+    if (node)
+    {
+        SubscribeToEvent(node, E_SCENESUBSYSTEMUPDATE, HANDLER(DynamicNavigationMesh, HandleSceneSubsystemUpdate));
+    }
+    else
+    {
+        UnsubscribeFromAllEvents();
+    }
+}
+
+void DynamicNavigationMesh::AddObstacle(Obstacle* obstacle, bool silent)
+{
+    if (tileCache_)
+    {
+        float pos[3];
+        Vector3 obsPos = obstacle->GetNode()->GetWorldPosition();
+        rcVcopy(pos, &obsPos.x_);
+        dtObstacleRef refHolder;
+        if (dtStatusFailed(tileCache_->addObstacle(pos, obstacle->GetRadius(), obstacle->GetHeight(), &refHolder)))
+        {
+            LOGERROR("Failed to add obstacle");
+            return;
+        }
+        obstacle->obstacleId_ = refHolder;
+        tileCache_->update(1, navMesh_);
+        
+        if (!silent)
+        {
+            using namespace NavigationObstacleAdded;
+            VariantMap& eventData = GetContext()->GetEventDataMap();
+            eventData[P_NODE] = obstacle->GetNode();
+            eventData[P_POSITION] = obstacle->GetNode()->GetWorldPosition();
+            eventData[P_RADIUS] = obstacle->GetRadius();
+            eventData[P_HEIGHT] = obstacle->GetHeight();
+            SendEvent(E_NAVIGATION_OBSTACLE_ADDED, eventData);
+        }
+    }
+}
+
+void DynamicNavigationMesh::ObstacleChanged(Obstacle* obstacle)
+{
+    if (tileCache_)
+    {
+        RemoveObstacle(obstacle, true);
+        AddObstacle(obstacle, true);
+    }
+}
+
+void DynamicNavigationMesh::RemoveObstacle(Obstacle* obstacle, bool silent)
+{
+    if (tileCache_ && obstacle->obstacleId_ > 0)
+    {
+        if (dtStatusFailed(tileCache_->removeObstacle(obstacle->obstacleId_)))
+        {
+            LOGERROR("Failed to remove obstacle");
+            return;
+        }
+        // Require a node in order to send an event
+        if (!silent && obstacle->GetNode())
+        {
+            obstacle->obstacleId_ = 0;
+            using namespace NavigationObstacleRemoved;
+            VariantMap& eventData = GetContext()->GetEventDataMap();
+            eventData[P_NODE] = obstacle->GetNode();
+            eventData[P_POSITION] = obstacle->GetNode()->GetWorldPosition();
+            eventData[P_RADIUS] = obstacle->GetRadius();
+            eventData[P_HEIGHT] = obstacle->GetHeight();
+            SendEvent(E_NAVIGATION_OBSTACLE_ADDED, eventData);
+        }
+    }
+}
+
+void DynamicNavigationMesh::HandleSceneSubsystemUpdate(StringHash eventType, VariantMap& eventData)
+{
+    using namespace SceneSubsystemUpdate;
+
+    const float deltaTime = eventData[P_TIMESTEP].GetFloat();
+    if (tileCache_ && navMesh_)
+    {
+        tileCache_->update(deltaTime, navMesh_);
+    }
+}
+
+}

+ 104 - 0
Source/Urho3D/Navigation/DynamicNavigationMesh.h

@@ -0,0 +1,104 @@
+//
+// Copyright (c) 2008-2015 the Urho3D project.
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+//
+
+#pragma once
+
+#include "../Navigation/NavigationMesh.h"
+
+class dtTileCache;
+struct dtTileCacheAlloc;
+struct dtTileCacheCompressor;
+struct dtTileCacheMeshProcess;
+struct dtTileCacheLayer;
+struct dtTileCacheContourSet;
+struct dtTileCachePolyMesh;
+
+namespace Urho3D
+{
+
+    class OffMeshConnection;
+    class Obstacle;
+
+    class URHO3D_API DynamicNavigationMesh : public NavigationMesh
+    {
+        OBJECT(DynamicNavigationMesh)
+        friend class Obstacle;
+        friend struct MeshProcess;
+
+    public:
+        /// Constructor
+        DynamicNavigationMesh(Context*);
+        /// Destructor
+        ~DynamicNavigationMesh();
+
+        /// Register with engine context
+        static void RegisterObject(Context*);
+
+        /// Builds/rebuilds the entire navigation mesh
+        virtual bool Build();
+        /// Builds/rebuilds a portion of the navigation mesh
+        virtual bool Build(const BoundingBox& boundingBox);
+
+        /// Set navigation data attribute.
+        virtual void SetNavigationDataAttr(const PODVector<unsigned char>& value);
+        /// Return navigation data attribute.
+        virtual PODVector<unsigned char> GetNavigationDataAttr() const;
+
+    protected:
+        struct TileCacheData;
+
+        /// Subscribe to events when assigned to a node
+        virtual void OnNodeSet(Node*);
+        /// Triggers the tile cache to make updates to the nav mesh if necessary
+        void HandleSceneSubsystemUpdate(StringHash eventType, VariantMap& eventData);
+
+        /// Used by Obstacle class to add itself to the tile cache, if 'silent' an event will not be raised
+        void AddObstacle(Obstacle* obstacle, bool silent = false);
+        /// Used by Obstacle class to update itself
+        void ObstacleChanged(Obstacle* obstacle);
+        /// Used by Obstacle class to remove itself from the tile cache, if 'silent' an event will not be raised
+        void RemoveObstacle(Obstacle*, bool silent = false);
+
+        /// Build one tile of the navigation mesh. Return true if successful.
+        int BuildTile(Vector<NavigationGeometryInfo>& geometryList, int x, int z, TileCacheData*);
+        /// Off-mesh connections to be rebuilt in the mesh processor
+        PODVector<OffMeshConnection*> CollectOffMeshConnections(const BoundingBox& bounds);
+        /// Release the navigation mesh, query, and tile cache
+        virtual void ReleaseNavigationMesh();
+
+    private:
+        /// Frees the tile cache
+        void ReleaseTileCache();
+
+        /// Detour tile cache instance that works with the nav mesh
+        dtTileCache* tileCache_;
+        /// Used by dtTileCache to allocate blocks of memory
+        dtTileCacheAlloc* allocator_;
+        /// Used by dtTileCache to compress the original tiles to use when reconstructing for changes
+        dtTileCacheCompressor* compressor_;
+        /// Mesh processer used by Detour, in this case a 'pass-through' processor
+        dtTileCacheMeshProcess* meshProcessor_;
+        /// Maximum number of obstacle objects allowed
+        unsigned maxObstacles_;
+    };
+
+}

+ 85 - 0
Source/Urho3D/Navigation/NavArea.cpp

@@ -0,0 +1,85 @@
+//
+// Copyright (c) 2008-2015 the Urho3D project.
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+//
+
+#include "Precompiled.h"
+
+#include "../Core/Context.h"
+#include "../Graphics/DebugRenderer.h"
+#include "../Scene/Component.h"
+#include "../Scene/Node.h"
+#include "../Navigation/NavArea.h"
+
+namespace Urho3D
+{
+    static const Vector3 DEFAULT_BOUNDING_BOX_MIN(-10.0f, -10.0f, -10.0f);
+    static const Vector3 DEFAULT_BOUNDING_BOX_MAX(10.0f, 10.0f, 10.0f);
+    static const unsigned DEFAULT_MASK_FLAG = 0;
+    static const unsigned DEFAULT_AREA = 0;
+
+    extern const char* NAVIGATION_CATEGORY;
+
+    NavArea::NavArea(Context* context) :
+        Component(context),
+        areaType_(0),
+        boundingBox_(DEFAULT_BOUNDING_BOX_MIN, DEFAULT_BOUNDING_BOX_MAX)
+    {
+
+    }
+
+    NavArea::~NavArea()
+    {
+
+    }
+    
+    void NavArea::RegisterObject(Context* context)
+    {
+        context->RegisterFactory<NavArea>(NAVIGATION_CATEGORY);
+
+        COPY_BASE_ATTRIBUTES(Component);
+        ATTRIBUTE("Bounding Box Min", Vector3, boundingBox_.min_, DEFAULT_BOUNDING_BOX_MIN, AM_DEFAULT);
+        ATTRIBUTE("Bounding Box Max", Vector3, boundingBox_.max_, DEFAULT_BOUNDING_BOX_MAX, AM_DEFAULT);
+        ACCESSOR_ATTRIBUTE("Area Type", GetAreaType, SetAreaType, unsigned, DEFAULT_AREA, AM_DEFAULT);
+    }
+
+    void NavArea::SetAreaType(unsigned newType)
+    {
+        areaType_ = newType;
+        MarkNetworkUpdate();
+    }
+
+    BoundingBox NavArea::GetTransformedBounds() const
+    {
+        Matrix3x4 mat;
+        mat.SetTranslation(node_->GetWorldPosition());
+        return boundingBox_.Transformed(mat);
+    }
+
+    void NavArea::DrawDebugGeometry(DebugRenderer* debug, bool depthTest) 
+    {
+        if (debug && IsEnabledEffective())
+        {
+            Matrix3x4 mat;
+            mat.SetTranslation(node_->GetWorldPosition());
+            debug->AddBoundingBox(boundingBox_, mat, Color::GREEN, depthTest);
+        }
+    }
+}

+ 66 - 0
Source/Urho3D/Navigation/NavArea.h

@@ -0,0 +1,66 @@
+//
+// Copyright (c) 2008-2015 the Urho3D project.
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+//
+
+#pragma once
+
+#include "../Scene/Component.h"
+#include "../Math/BoundingBox.h"
+
+namespace Urho3D
+{
+    class URHO3D_API NavArea : public Component
+    {
+        OBJECT(NavArea);
+
+    public:
+        /// Construct
+        NavArea(Context*);
+        /// Destruct
+        virtual ~NavArea();
+        /// Register object factory and attributes
+        static void RegisterObject(Context*);
+
+        /// Render debug geometry for the bounds
+        virtual void DrawDebugGeometry(DebugRenderer* debug, bool depthTest);
+
+        /// Gets the area id for this volume
+        unsigned GetAreaType() const { return areaType_; }
+        /// Sets the area id for this volume
+        void SetAreaType(unsigned);
+
+        /// Gets the bounding box of this navigation area, in local space
+        BoundingBox GetBounds() const { return boundingBox_; }
+        /// Sets the bounding box of this area, in local space
+        void SetBounds(const BoundingBox& bnds) { boundingBox_ = bnds; }
+
+        /// Gets the bounds of this navigation area in world space
+        BoundingBox GetTransformedBounds() const;
+
+    private:
+        /// Bounds of area to mark
+        BoundingBox boundingBox_;
+        /// Flags to assign to the marked area of the navigation map
+        unsigned flags_;
+        /// Area identifier to assign to the marked area
+        unsigned areaType_;
+    };
+}

+ 100 - 0
Source/Urho3D/Navigation/NavBuildData.cpp

@@ -0,0 +1,100 @@
+//
+// Copyright (c) 2008-2015 the Urho3D project.
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+//
+
+#include "Precompiled.h"
+
+#include "../Navigation/NavBuildData.h"
+
+#include <Recast/Recast.h>
+#include <Detour/DetourNavMesh.h>
+#include <Detour/DetourNavMeshBuilder.h>
+#include <Detour/DetourNavMeshQuery.h>
+#include <DetourTileCache/DetourTileCache.h>
+#include <DetourTileCache/DetourTileCacheBuilder.h>
+
+namespace Urho3D
+{
+    /// Construct.
+    NavBuildData::NavBuildData() :
+        ctx_(new rcContext(false)),
+        heightField_(0),
+        compactHeightField_(0)
+    {
+        ctx_ = new rcContext(true);
+    }
+
+    NavBuildData::~NavBuildData()
+    {
+        if (ctx_)
+            delete(ctx_);
+        rcFreeHeightField(heightField_);
+        rcFreeCompactHeightfield(compactHeightField_);
+
+        ctx_ = 0;
+        heightField_ = 0;
+        compactHeightField_ = 0;
+        
+    }
+
+    SimpleNavBuildData::SimpleNavBuildData() :
+        NavBuildData(),
+        contourSet_(0),
+        polyMesh_(0),
+        polyMeshDetail_(0)
+    {
+
+    }
+
+    SimpleNavBuildData::~SimpleNavBuildData()
+    {
+        rcFreeContourSet(contourSet_);
+        rcFreePolyMesh(polyMesh_);
+        rcFreePolyMeshDetail(polyMeshDetail_);
+
+        contourSet_ = 0;
+        polyMesh_ = 0;
+        polyMeshDetail_ = 0;
+    }
+
+    DynamicNavBuildData::DynamicNavBuildData(dtTileCacheAlloc* allocator) :
+        contourSet_(0),
+        heightFieldLayers_(0),
+        polyMesh_(0),
+        alloc_(allocator)
+    {
+
+    }
+
+    DynamicNavBuildData::~DynamicNavBuildData()
+    {
+        if (contourSet_)
+            dtFreeTileCacheContourSet(alloc_, contourSet_);
+        if (polyMesh_)
+            dtFreeTileCachePolyMesh(alloc_, polyMesh_);
+        if (heightFieldLayers_)
+            rcFreeHeightfieldLayerSet(heightFieldLayers_);
+
+        contourSet_ = 0;
+        polyMesh_ = 0;
+        heightFieldLayers_ = 0;
+    }
+}

+ 107 - 0
Source/Urho3D/Navigation/NavBuildData.h

@@ -0,0 +1,107 @@
+//
+// Copyright (c) 2008-2015 the Urho3D project.
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+//
+
+#pragma once
+
+#include "../Container/Vector.h"
+#include "../Math/BoundingBox.h"
+#include "../Math/Vector3.h"
+
+class rcContext;
+struct rcHeightfield;
+struct rcCompactHeightfield;
+struct rcContourSet;
+struct rcPolyMesh;
+struct rcPolyMeshDetail;
+
+struct rcHeightfieldLayerSet;
+struct dtTileCacheContourSet;
+struct dtTileCachePolyMesh;
+struct dtTileCacheAlloc;
+
+namespace Urho3D
+{
+    struct URHO3D_API NavAreaStub
+    {
+        BoundingBox bounds_;
+        unsigned char areaID_;
+    };
+
+    struct URHO3D_API NavBuildData
+    {
+        NavBuildData();
+        virtual ~NavBuildData();
+
+        /// World-space bounding box of the navigation mesh tile.
+        BoundingBox worldBoundingBox_;
+        /// Vertices from geometries.
+        PODVector<Vector3> vertices_;
+        /// Triangle indices from geometries.
+        PODVector<int> indices_;
+        /// Offmesh connection vertices.
+        PODVector<Vector3> offMeshVertices_;
+        /// Offmesh connection radii.
+        PODVector<float> offMeshRadii_;
+        /// Offmesh connection flags.
+        PODVector<unsigned short> offMeshFlags_;
+        /// Offmesh connection areas.
+        PODVector<unsigned char> offMeshAreas_;
+        /// Offmesh connection direction.
+        PODVector<unsigned char> offMeshDir_;
+        /// Recast context.
+        rcContext* ctx_;
+        /// Recast heightfield
+        rcHeightfield* heightField_;
+        /// Recast compact heightfield.
+        rcCompactHeightfield* compactHeightField_;
+        /// Pretransformed navigation areas, no correlation to the geometry above
+        PODVector<NavAreaStub> navAreas_;
+    };
+
+    struct SimpleNavBuildData : public NavBuildData
+    {
+        SimpleNavBuildData();
+        virtual ~SimpleNavBuildData();
+
+        /// Recast contour set.
+        rcContourSet* contourSet_;
+        /// Recast poly mesh.
+        rcPolyMesh* polyMesh_;
+        /// Recast detail poly mesh.
+        rcPolyMeshDetail* polyMeshDetail_;
+    };
+
+    struct DynamicNavBuildData : public NavBuildData
+    {
+        DynamicNavBuildData(dtTileCacheAlloc* alloc);
+        virtual ~DynamicNavBuildData();
+
+        /// TileCache specific recast contour set
+        dtTileCacheContourSet* contourSet_;
+        /// TileCache specific recast poly mesh
+        dtTileCachePolyMesh* polyMesh_;
+        /// Recast heightfield layer set
+        rcHeightfieldLayerSet* heightFieldLayers_;
+        /// Allocator from DynamicNavigationMesh instance
+        dtTileCacheAlloc* alloc_;
+    };
+}

+ 77 - 0
Source/Urho3D/Navigation/NavigationEvents.h

@@ -0,0 +1,77 @@
+//
+// Copyright (c) 2008-2015 the Urho3D project.
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+//
+
+#pragma once
+
+#include "../Core/Object.h"
+
+namespace Urho3D
+{
+
+/// Complete rebuild of navigation mesh
+EVENT(E_NAVIGATION_MESH_REBUILT, NavigationMeshRebuilt)
+{
+    PARAM(P_NODE, Node); // Node pointer
+    PARAM(P_MESH, Mesh); // NavigationMesh pointer
+}
+
+/// Partial bounding box rebuild of navigation mesh
+EVENT(E_NAVIGATION_AREA_REBUILT, NavigationAreaRebuilt)
+{
+    PARAM(P_BOUNDSMIN, BoundsMin); // Vector3
+    PARAM(P_BOUNDSMAX, BoundsMax); // Vector3
+}
+
+/// Crowd agent has been repositioned
+EVENT(E_CROWD_AGENT_REPOSITION, CrowdAgentReposition)
+{
+    PARAM(P_POSITION, Position); // Vector3
+    PARAM(P_VELOCITY, Velocity); // Vector3
+}
+
+/// Crowd agent's state has been changed, reached goal
+EVENT(E_CROWD_AGENT_STATE_CHANGED, CrowdAgentStateChanged)
+{
+    PARAM(P_POSITION, Position); // Vector3
+    PARAM(P_VELOCITY, Velocity); // Vector3
+    PARAM(P_STATE, State); // int
+}
+
+/// Addition of obstacle to dynamic navigation mesh
+EVENT(E_NAVIGATION_OBSTACLE_ADDED, NavigationObstacleAdded)
+{
+    PARAM(P_NODE, Node); // Node pointer
+    PARAM(P_POSITION, Position); // Vector3
+    PARAM(P_RADIUS, Radius); // float
+    PARAM(P_HEIGHT, Height); // float
+}
+
+/// Removal of obstacle from dynamic navigation mesh
+EVENT(E_NAVIGATION_OBSTACLE_REMOVED, NavigationObstacleRemoved)
+{
+    PARAM(P_NODE, Node); // Node pointer
+    PARAM(P_POSITION, Position); // Vector3
+    PARAM(P_RADIUS, Radius); // float
+    PARAM(P_HEIGHT, Height); // float
+}
+
+}

+ 148 - 100
Source/Urho3D/Navigation/NavigationMesh.cpp

@@ -30,8 +30,13 @@
 #include "../IO/Log.h"
 #include "../IO/MemoryBuffer.h"
 #include "../Graphics/Model.h"
+#include "../Navigation/DynamicNavigationMesh.h"
+#include "../Navigation/NavArea.h"
+#include "../Navigation/NavBuildData.h"
 #include "../Navigation/Navigable.h"
+#include "../Navigation/NavigationEvents.h"
 #include "../Navigation/NavigationMesh.h"
+#include "../Navigation/Obstacle.h"
 #include "../Navigation/OffMeshConnection.h"
 #include "../Core/Profiler.h"
 #include "../Scene/Scene.h"
@@ -45,11 +50,21 @@
 #include <Detour/DetourNavMeshQuery.h>
 #include <Recast/Recast.h>
 
+#include "../Navigation/CrowdAgent.h"
+#include "../Navigation/DetourCrowdManager.h"
+
 #include "../DebugNew.h"
 
 namespace Urho3D
 {
 
+const char* navmeshPartitionTypeNames[] =
+{
+    "watershed",
+    "monotone",
+    0
+};
+
 const char* NAVIGATION_CATEGORY = "Navigation";
 
 static const int DEFAULT_TILE_SIZE = 128;
@@ -69,66 +84,7 @@ static const float DEFAULT_DETAIL_SAMPLE_MAX_ERROR = 1.0f;
 static const int MAX_POLYS = 2048;
 
 /// Temporary data for building one tile of the navigation mesh.
-struct NavigationBuildData
-{
-    /// Construct.
-    NavigationBuildData() :
-        ctx_(new rcContext(false)),
-        heightField_(0),
-        compactHeightField_(0),
-        contourSet_(0),
-        polyMesh_(0),
-        polyMeshDetail_(0)
-    {
-    }
 
-    /// Destruct.
-    ~NavigationBuildData()
-    {
-        delete(ctx_);
-        rcFreeHeightField(heightField_);
-        rcFreeCompactHeightfield(compactHeightField_);
-        rcFreeContourSet(contourSet_);
-        rcFreePolyMesh(polyMesh_);
-        rcFreePolyMeshDetail(polyMeshDetail_);
-
-        ctx_ = 0;
-        heightField_ = 0;
-        compactHeightField_ = 0;
-        contourSet_ = 0;
-        polyMesh_ = 0;
-        polyMeshDetail_ = 0;
-    }
-
-    /// World-space bounding box of the navigation mesh tile.
-    BoundingBox worldBoundingBox_;
-    /// Vertices from geometries.
-    PODVector<Vector3> vertices_;
-    /// Triangle indices from geometries.
-    PODVector<int> indices_;
-    /// Offmesh connection vertices.
-    PODVector<Vector3> offMeshVertices_;
-    /// Offmesh connection radii.
-    PODVector<float> offMeshRadii_;
-    /// Offmesh connection flags.
-    PODVector<unsigned short> offMeshFlags_;
-    /// Offmesh connection areas.
-    PODVector<unsigned char> offMeshAreas_;
-    /// Offmesh connection direction.
-    PODVector<unsigned char> offMeshDir_;
-    /// Recast context.
-    rcContext* ctx_;
-    /// Recast heightfield.
-    rcHeightfield* heightField_;
-    /// Recast compact heightfield.
-    rcCompactHeightfield* compactHeightField_;
-    /// Recast contour set.
-    rcContourSet* contourSet_;
-    /// Recast poly mesh.
-    rcPolyMesh* polyMesh_;
-    /// Recast detail poly mesh.
-    rcPolyMeshDetail* polyMeshDetail_;
-};
 
 /// Temporary data for finding a path.
 struct FindPathData
@@ -141,6 +97,8 @@ struct FindPathData
     Vector3 pathPoints_[MAX_POLYS];
     // Flags on the path.
     unsigned char pathFlags_[MAX_POLYS];
+    //	Arera Ids on the path
+    unsigned char pathAreras_[MAX_POLYS];
 };
 
 NavigationMesh::NavigationMesh(Context* context) :
@@ -164,7 +122,9 @@ NavigationMesh::NavigationMesh(Context* context) :
     detailSampleMaxError_(DEFAULT_DETAIL_SAMPLE_MAX_ERROR),
     padding_(Vector3::ONE),
     numTilesX_(0),
-    numTilesZ_(0)
+    numTilesZ_(0),
+    partitionType_(NAVMESH_PARTITION_WATERSHED),
+    keepInterResults_(false)
 {
 }
 
@@ -198,6 +158,7 @@ void NavigationMesh::RegisterObject(Context* context)
     ACCESSOR_ATTRIBUTE("Detail Sample Max Error", GetDetailSampleMaxError, SetDetailSampleMaxError, float, DEFAULT_DETAIL_SAMPLE_MAX_ERROR, AM_DEFAULT);
     ACCESSOR_ATTRIBUTE("Bounding Box Padding", GetPadding, SetPadding, Vector3, Vector3::ONE, AM_DEFAULT);
     MIXED_ACCESSOR_ATTRIBUTE("Navigation Data", GetNavigationDataAttr, SetNavigationDataAttr, PODVector<unsigned char>, Variant::emptyBuffer, AM_FILE | AM_NOEDIT);
+    ENUM_ACCESSOR_ATTRIBUTE("Partition Type", GetPartitionType, SetPartitionType, NavmeshPartitionType, navmeshPartitionTypeNames, NAVMESH_PARTITION_WATERSHED, AM_DEFAULT);
 }
 
 void NavigationMesh::DrawDebugGeometry(DebugRenderer* debug, bool depthTest)
@@ -234,6 +195,11 @@ void NavigationMesh::DrawDebugGeometry(DebugRenderer* debug, bool depthTest)
     }
 }
 
+void NavigationMesh::SetMeshName(const String& newName)
+{
+    meshName_ = newName;
+}
+
 void NavigationMesh::SetTileSize(int size)
 {
     tileSize_ = Max(size, 16);
@@ -415,6 +381,16 @@ bool NavigationMesh::Build()
         }
 
         LOGDEBUG("Built navigation mesh with " + String(numTiles) + " tiles");
+
+        // Send a notification event to concerned parties that we've been fully rebuilt
+        {
+            using namespace NavigationMeshRebuilt;
+            VariantMap& buildEventParams = GetContext()->GetEventDataMap();
+            buildEventParams[P_NODE] = node_;
+            buildEventParams[P_MESH] = this;
+            SendEvent(E_NAVIGATION_MESH_REBUILT, buildEventParams);
+        }
+
         return true;
     }
 }
@@ -521,7 +497,7 @@ void NavigationMesh::FindPath(PODVector<Vector3>& dest, const Vector3& start, co
 
     Vector3 localStart = inverse * start;
     Vector3 localEnd = inverse * end;
-
+    
     dtPolyRef startRef;
     dtPolyRef endRef;
     navMeshQuery_->findNearestPoly(&localStart.x_, &extents.x_, queryFilter_, &startRef, 0);
@@ -647,6 +623,12 @@ void NavigationMesh::DrawDebugGeometry(bool depthTest)
     }
 }
 
+void NavigationMesh::SetAreaTypeCost(unsigned areaType, float cost)
+{
+    if (queryFilter_)
+        queryFilter_->setAreaCost((int)areaType, cost);
+}
+
 BoundingBox NavigationMesh::GetWorldBoundingBox() const
 {
     return node_ ? boundingBox_.Transformed(node_->GetWorldTransform()) : boundingBox_;
@@ -789,6 +771,22 @@ void NavigationMesh::CollectGeometries(Vector<NavigationGeometryInfo>& geometryL
             geometryList.Push(info);
         }
     }
+
+    // Get nav area volumes
+    PODVector<NavArea*> navAreas;
+    node_->GetComponents<NavArea>(navAreas, true);
+    for (unsigned i = 0; i < navAreas.Size(); ++i)
+    {
+        NavArea* area = navAreas[i];
+        // ignore disabled AND any areas that have no meaningful settings
+        if (area->IsEnabledEffective() && area->GetAreaType() != 0)
+        {
+            NavigationGeometryInfo info;
+            info.component_ = area;
+            info.boundingBox_ = area->GetTransformedBounds();
+            geometryList.Push(info);
+        }
+    }
 }
 
 void NavigationMesh::CollectGeometries(Vector<NavigationGeometryInfo>& geometryList, Node* node, HashSet<Node*>& processedNodes, bool recursive)
@@ -865,7 +863,7 @@ void NavigationMesh::CollectGeometries(Vector<NavigationGeometryInfo>& geometryL
     }
 }
 
-void NavigationMesh::GetTileGeometry(NavigationBuildData& build, Vector<NavigationGeometryInfo>& geometryList, BoundingBox& box)
+void NavigationMesh::GetTileGeometry(NavBuildData* build, Vector<NavigationGeometryInfo>& geometryList, BoundingBox& box)
 {
     Matrix3x4 inverse = node_->GetWorldTransform().Inverse();
 
@@ -881,14 +879,23 @@ void NavigationMesh::GetTileGeometry(NavigationBuildData& build, Vector<Navigati
                 Vector3 start = inverse * connection->GetNode()->GetWorldPosition();
                 Vector3 end = inverse * connection->GetEndPoint()->GetWorldPosition();
 
-                build.offMeshVertices_.Push(start);
-                build.offMeshVertices_.Push(end);
-                build.offMeshRadii_.Push(connection->GetRadius());
-                /// \todo Allow to define custom flags
-                build.offMeshFlags_.Push(0x1);
+                build->offMeshVertices_.Push(start);
+                build->offMeshVertices_.Push(end);
+                build->offMeshRadii_.Push(connection->GetRadius());
+                build->offMeshFlags_.Push(connection->GetMask());
+                build->offMeshAreas_.Push((unsigned char)connection->GetAreaID());
+                build->offMeshDir_.Push(connection->IsBidirectional() ? DT_OFFMESH_CON_BIDIR : 0);
+                continue;
+            }
 
-                build.offMeshAreas_.Push(0);
-                build.offMeshDir_.Push(connection->IsBidirectional() ? DT_OFFMESH_CON_BIDIR : 0);
+            if (geometryList[i].component_->GetType() == NavArea::GetTypeStatic())
+            {
+                NavArea* area = static_cast<NavArea*>(geometryList[i].component_);
+                NavAreaStub stub;
+                //\todo is there an alternative to casting down? Attributes restricts area ID/type to being an unsigned int
+                stub.areaID_ = (unsigned char)area->GetAreaType();
+                stub.bounds_ = area->GetTransformedBounds();
+                build->navAreas_.Push(stub);
                 continue;
             }
 
@@ -918,28 +925,28 @@ void NavigationMesh::GetTileGeometry(NavigationBuildData& build, Vector<Navigati
 
                         unsigned numVertices = data->vertexCount_;
                         unsigned numIndices = data->indexCount_;
-                        unsigned destVertexStart = build.vertices_.Size();
+                        unsigned destVertexStart = build->vertices_.Size();
 
                         for (unsigned j = 0; j < numVertices; ++j)
-                            build.vertices_.Push(transform * data->vertexData_[j]);
+                            build->vertices_.Push(transform * data->vertexData_[j]);
 
                         for (unsigned j = 0; j < numIndices; ++j)
-                            build.indices_.Push(data->indexData_[j] + destVertexStart);
+                            build->indices_.Push(data->indexData_[j] + destVertexStart);
                     }
                     break;
 
                 case SHAPE_BOX:
                     {
-                        unsigned destVertexStart = build.vertices_.Size();
+                        unsigned destVertexStart = build->vertices_.Size();
 
-                        build.vertices_.Push(transform * Vector3(-0.5f, 0.5f, -0.5f));
-                        build.vertices_.Push(transform * Vector3(0.5f, 0.5f, -0.5f));
-                        build.vertices_.Push(transform * Vector3(0.5f, -0.5f, -0.5f));
-                        build.vertices_.Push(transform * Vector3(-0.5f, -0.5f, -0.5f));
-                        build.vertices_.Push(transform * Vector3(-0.5f, 0.5f, 0.5f));
-                        build.vertices_.Push(transform * Vector3(0.5f, 0.5f, 0.5f));
-                        build.vertices_.Push(transform * Vector3(0.5f, -0.5f, 0.5f));
-                        build.vertices_.Push(transform * Vector3(-0.5f, -0.5f, 0.5f));
+                        build->vertices_.Push(transform * Vector3(-0.5f, 0.5f, -0.5f));
+                        build->vertices_.Push(transform * Vector3(0.5f, 0.5f, -0.5f));
+                        build->vertices_.Push(transform * Vector3(0.5f, -0.5f, -0.5f));
+                        build->vertices_.Push(transform * Vector3(-0.5f, -0.5f, -0.5f));
+                        build->vertices_.Push(transform * Vector3(-0.5f, 0.5f, 0.5f));
+                        build->vertices_.Push(transform * Vector3(0.5f, 0.5f, 0.5f));
+                        build->vertices_.Push(transform * Vector3(0.5f, -0.5f, 0.5f));
+                        build->vertices_.Push(transform * Vector3(-0.5f, -0.5f, 0.5f));
 
                         const unsigned indices[] = {
                             0, 1, 2, 0, 2, 3, 1, 5, 6, 1, 6, 2, 4, 5, 1, 4, 1, 0, 5, 4, 7, 5, 7, 6,
@@ -947,7 +954,7 @@ void NavigationMesh::GetTileGeometry(NavigationBuildData& build, Vector<Navigati
                         };
 
                         for (unsigned j = 0; j < 36; ++j)
-                            build.indices_.Push(indices[j] + destVertexStart);
+                            build->indices_.Push(indices[j] + destVertexStart);
                     }
                     break;
 
@@ -970,7 +977,7 @@ void NavigationMesh::GetTileGeometry(NavigationBuildData& build, Vector<Navigati
     }
 }
 
-void NavigationMesh::AddTriMeshGeometry(NavigationBuildData& build, Geometry* geometry, const Matrix3x4& transform)
+void NavigationMesh::AddTriMeshGeometry(NavBuildData* build, Geometry* geometry, const Matrix3x4& transform)
 {
     if (!geometry)
         return;
@@ -993,12 +1000,12 @@ void NavigationMesh::AddTriMeshGeometry(NavigationBuildData& build, Geometry* ge
     if (!srcIndexCount)
         return;
 
-    unsigned destVertexStart = build.vertices_.Size();
+    unsigned destVertexStart = build->vertices_.Size();
 
     for (unsigned k = srcVertexStart; k < srcVertexStart + srcVertexCount; ++k)
     {
         Vector3 vertex = transform * *((const Vector3*)(&vertexData[k * vertexSize]));
-        build.vertices_.Push(vertex);
+        build->vertices_.Push(vertex);
     }
 
     // Copy remapped indices
@@ -1009,7 +1016,7 @@ void NavigationMesh::AddTriMeshGeometry(NavigationBuildData& build, Geometry* ge
 
         while (indices < indicesEnd)
         {
-            build.indices_.Push(*indices - srcVertexStart + destVertexStart);
+            build->indices_.Push(*indices - srcVertexStart + destVertexStart);
             ++indices;
         }
     }
@@ -1020,7 +1027,7 @@ void NavigationMesh::AddTriMeshGeometry(NavigationBuildData& build, Geometry* ge
 
         while (indices < indicesEnd)
         {
-            build.indices_.Push(*indices - srcVertexStart + destVertexStart);
+            build->indices_.Push(*indices - srcVertexStart + destVertexStart);
             ++indices;
         }
     }
@@ -1046,7 +1053,7 @@ bool NavigationMesh::BuildTile(Vector<NavigationGeometryInfo>& geometryList, int
         boundingBox_.min_.z_ + tileEdgeLength * (float)(z + 1)
     ));
 
-    NavigationBuildData build;
+    SimpleNavBuildData build;
 
     rcConfig cfg;
     memset(&cfg, 0, sizeof cfg);
@@ -1076,7 +1083,7 @@ bool NavigationMesh::BuildTile(Vector<NavigationGeometryInfo>& geometryList, int
     cfg.bmax[2] += cfg.borderSize * cfg.cs;
 
     BoundingBox expandedBox(*reinterpret_cast<Vector3*>(cfg.bmin), *reinterpret_cast<Vector3*>(cfg.bmax));
-    GetTileGeometry(build, geometryList, expandedBox);
+    GetTileGeometry(&build, geometryList, expandedBox);
 
     if (build.vertices_.Empty() || build.indices_.Empty())
         return true; // Nothing to do
@@ -1104,8 +1111,9 @@ bool NavigationMesh::BuildTile(Vector<NavigationGeometryInfo>& geometryList, int
     rcRasterizeTriangles(build.ctx_, &build.vertices_[0].x_, build.vertices_.Size(), &build.indices_[0],
         triAreas.Get(), numTriangles, *build.heightField_, cfg.walkableClimb);
     rcFilterLowHangingWalkableObstacles(build.ctx_, cfg.walkableClimb, *build.heightField_);
-    rcFilterLedgeSpans(build.ctx_, cfg.walkableHeight, cfg.walkableClimb, *build.heightField_);
+    
     rcFilterWalkableLowHeightSpans(build.ctx_, cfg.walkableHeight, *build.heightField_);
+    rcFilterLedgeSpans(build.ctx_, cfg.walkableHeight, cfg.walkableClimb, *build.heightField_);
 
     build.compactHeightField_ = rcAllocCompactHeightfield();
     if (!build.compactHeightField_)
@@ -1124,16 +1132,32 @@ bool NavigationMesh::BuildTile(Vector<NavigationGeometryInfo>& geometryList, int
         LOGERROR("Could not erode compact heightfield");
         return false;
     }
-    if (!rcBuildDistanceField(build.ctx_, *build.compactHeightField_))
+
+    // Mark area volumes
+    for (unsigned i = 0; i < build.navAreas_.Size(); ++i)
+        rcMarkBoxArea(build.ctx_, &build.navAreas_[i].bounds_.min_.x_, &build.navAreas_[i].bounds_.max_.x_, build.navAreas_[i].areaID_, *build.compactHeightField_);
+
+    if (this->partitionType_ == NAVMESH_PARTITION_WATERSHED)
     {
-        LOGERROR("Could not build distance field");
-        return false;
+        if (!rcBuildDistanceField(build.ctx_, *build.compactHeightField_))
+        {
+            LOGERROR("Could not build distance field");
+            return false;
+        }
+        if (!rcBuildRegions(build.ctx_, *build.compactHeightField_, cfg.borderSize, cfg.minRegionArea,
+            cfg.mergeRegionArea))
+        {
+            LOGERROR("Could not build regions");
+            return false;
+        }
     }
-    if (!rcBuildRegions(build.ctx_, *build.compactHeightField_, cfg.borderSize, cfg.minRegionArea,
-        cfg.mergeRegionArea))
+    else
     {
-        LOGERROR("Could not build regions");
-        return false;
+        if (!rcBuildRegionsMonotone(build.ctx_, *build.compactHeightField_, cfg.borderSize, cfg.minRegionArea, cfg.mergeRegionArea))
+        {
+            LOGERROR("Could not build monotone regions");
+            return false;
+        }
     }
 
     build.contourSet_ = rcAllocContourSet();
@@ -1175,10 +1199,10 @@ bool NavigationMesh::BuildTile(Vector<NavigationGeometryInfo>& geometryList, int
     }
 
     // Set polygon flags
-    /// \todo Allow to define custom flags
+    /// \todo Assignment of flags from navigation areas?
     for (int i = 0; i < build.polyMesh_->npolys; ++i)
     {
-        if (build.polyMesh_->areas[i] == RC_WALKABLE_AREA)
+        if (build.polyMesh_->areas[i] != RC_NULL_AREA)
             build.polyMesh_->flags[i] = 0x1;
     }
 
@@ -1234,6 +1258,14 @@ bool NavigationMesh::BuildTile(Vector<NavigationGeometryInfo>& geometryList, int
         return false;
     }
 
+    // Send a notification of the rebuild of this tile to anyone interested
+    {
+        using namespace NavigationAreaRebuilt;
+        VariantMap& eventData = GetContext()->GetEventDataMap();
+        eventData[P_BOUNDSMIN] = Variant(tileBoundingBox.min_);
+        eventData[P_BOUNDSMAX] = Variant(tileBoundingBox.max_);
+        SendEvent(E_NAVIGATION_AREA_REBUILT, eventData);
+    }
     return true;
 }
 
@@ -1275,11 +1307,27 @@ void NavigationMesh::ReleaseNavigationMesh()
     boundingBox_.defined_ = false;
 }
 
+void NavigationMesh::SetPartitionType(NavmeshPartitionType ptype)
+{
+    partitionType_ = ptype;
+    MarkNetworkUpdate();
+}
+
+NavmeshPartitionType NavigationMesh::GetPartitionType() const
+{
+    return partitionType_;
+}
+
 void RegisterNavigationLibrary(Context* context)
 {
     Navigable::RegisterObject(context);
     NavigationMesh::RegisterObject(context);
     OffMeshConnection::RegisterObject(context);
+    CrowdAgent::RegisterObject(context);
+    DetourCrowdManager::RegisterObject(context);
+    DynamicNavigationMesh::RegisterObject(context);
+    Obstacle::RegisterObject(context);
+    NavArea::RegisterObject(context);
 }
 
-}
+}

+ 48 - 10
Source/Urho3D/Navigation/NavigationMesh.h

@@ -28,17 +28,33 @@
 #include "../Container/HashSet.h"
 #include "../Math/Matrix3x4.h"
 
+
 class dtNavMesh;
 class dtNavMeshQuery;
 class dtQueryFilter;
+struct dtNavMeshCreateParams;
+class rcContext;
+struct rcHeightfield;
+struct rcCompactHeightfield;
+struct rcContourSet;
+struct rcPolyMesh;
+struct rcPolyMeshDetail;
+struct rcHeightFieldLayerSet;
+
 
 namespace Urho3D
 {
 
+enum NavmeshPartitionType
+{
+    NAVMESH_PARTITION_WATERSHED,
+    NAVMESH_PARTITION_MONOTONE,
+};
+
 class Geometry;
 
 struct FindPathData;
-struct NavigationBuildData;
+struct NavBuildData;
 
 /// Description of a navigation mesh geometry component, with transform and bounds information.
 struct NavigationGeometryInfo
@@ -57,6 +73,8 @@ struct NavigationGeometryInfo
 class URHO3D_API NavigationMesh : public Component
 {
     OBJECT(NavigationMesh);
+    friend class DetourCrowdManager;
+    friend class AnnotationBuilder;
 
 public:
     /// Construct.
@@ -98,9 +116,9 @@ public:
     /// Set padding of the navigation mesh bounding box. Having enough padding allows to add geometry on the extremities of the navigation mesh when doing partial rebuilds.
     void SetPadding(const Vector3& padding);
     /// Rebuild the navigation mesh. Return true if successful.
-    bool Build();
+    virtual bool Build();
     /// Rebuild part of the navigation mesh contained by the world-space bounding box. Return true if successful.
-    bool Build(const BoundingBox& boundingBox);
+    virtual bool Build(const BoundingBox& boundingBox);
     /// Find the nearest point on the navigation mesh to a given point. Extens specifies how far out from the specified point to check along each axis.
     Vector3 FindNearestPoint(const Vector3& point, const Vector3& extents=Vector3::ONE);
     /// Try to move along the surface from one point to another
@@ -117,7 +135,13 @@ public:
     Vector3 Raycast(const Vector3& start, const Vector3& end, const Vector3& extents = Vector3::ONE);
     /// Add debug geometry to the debug renderer.
     void DrawDebugGeometry(bool depthTest);
+    /// Sets the cost of an area
+    void SetAreaTypeCost(unsigned areaType, float cost);
 
+    ///	Return the given name of this navigation mesh
+    String GetMeshName() const { return meshName_; }
+    /// Set the name of this navigation mesh
+    void SetMeshName(const String& newName);
     /// Return tile size.
     int GetTileSize() const { return tileSize_; }
     /// Return cell size.
@@ -155,27 +179,34 @@ public:
     /// Return number of tiles.
     IntVector2 GetNumTiles() const { return IntVector2(numTilesX_, numTilesZ_); }
 
+    /// Sets the partition type used for polygon generation
+    void SetPartitionType(NavmeshPartitionType aType);
+    /// Return Partition Type.
+    NavmeshPartitionType GetPartitionType() const;
+
     /// Set navigation data attribute.
-    void SetNavigationDataAttr(const PODVector<unsigned char>& value);
+    virtual void SetNavigationDataAttr(const PODVector<unsigned char>& value);
     /// Return navigation data attribute.
-    PODVector<unsigned char> GetNavigationDataAttr() const;
+    virtual PODVector<unsigned char> GetNavigationDataAttr() const;
 
-private:
+protected:
     /// Collect geometry from under Navigable components.
     void CollectGeometries(Vector<NavigationGeometryInfo>& geometryList);
     /// Visit nodes and collect navigable geometry.
     void CollectGeometries(Vector<NavigationGeometryInfo>& geometryList, Node* node, HashSet<Node*>& processedNodes, bool recursive);
     /// Get geometry data within a bounding box.
-    void GetTileGeometry(NavigationBuildData& build, Vector<NavigationGeometryInfo>& geometryList, BoundingBox& box);
+    void GetTileGeometry(NavBuildData* build, Vector<NavigationGeometryInfo>& geometryList, BoundingBox& box);
     /// Add a triangle mesh to the geometry data.
-    void AddTriMeshGeometry(NavigationBuildData& build, Geometry* geometry, const Matrix3x4& transform);
+    void AddTriMeshGeometry(NavBuildData* build, Geometry* geometry, const Matrix3x4& transform);
     /// Build one tile of the navigation mesh. Return true if successful.
-    bool BuildTile(Vector<NavigationGeometryInfo>& geometryList, int x, int z);
+    virtual bool BuildTile(Vector<NavigationGeometryInfo>& geometryList, int x, int z);
     /// Ensure that the navigation mesh query is initialized. Return true if successful.
     bool InitializeQuery();
     /// Release the navigation mesh and the query.
-    void ReleaseNavigationMesh();
+    virtual void ReleaseNavigationMesh();
 
+    /// Identifying name for this navigation mesh
+    String meshName_;
     /// Detour navigation mesh.
     dtNavMesh* navMesh_;
     /// Detour navigation mesh query.
@@ -218,6 +249,13 @@ private:
     int numTilesZ_;
     /// Whole navigation mesh bounding box.
     BoundingBox boundingBox_;
+
+    /// Type of the heightfield partitioning.
+    NavmeshPartitionType partitionType_;
+    /// keep internal build resources for debug draw modes.
+    bool keepInterResults_;
+    /// internal build resources for creating the navmesh.
+    HashMap<Pair<int, int>, NavBuildData*> builds_;
 };
 
 /// Register Navigation library objects.

+ 135 - 0
Source/Urho3D/Navigation/Obstacle.cpp

@@ -0,0 +1,135 @@
+//
+// Copyright (c) 2008-2015 the Urho3D project.
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+//
+
+#include "Precompiled.h"
+
+#include "../Navigation/Obstacle.h"
+
+#include "../Core/Context.h"
+#include "../Graphics/DebugRenderer.h"
+#include "../Navigation/DynamicNavigationMesh.h"
+#include "../IO/Log.h"
+#include "../Navigation/NavigationEvents.h"
+#include "../Scene/Scene.h"
+
+#include "../DebugNew.h"
+
+namespace Urho3D
+{
+
+extern const char* NAVIGATION_CATEGORY;
+
+Obstacle::Obstacle(Context* context) :
+    Component(context),
+    height_(5.0f),
+    radius_(5.0f),
+    obstacleId_(0)
+{
+
+}
+
+Obstacle::~Obstacle()
+{
+    if (obstacleId_ > 0 && !ownerMesh_.Expired())
+        ownerMesh_.Get()->RemoveObstacle(this);
+}
+
+void Obstacle::RegisterObject(Context* context)
+{
+    context->RegisterFactory<Obstacle>(NAVIGATION_CATEGORY);
+    COPY_BASE_ATTRIBUTES(Component);
+    ACCESSOR_ATTRIBUTE("Radius", GetRadius, SetRadius, float, 5.0f, AM_DEFAULT);
+    ACCESSOR_ATTRIBUTE("Height", GetHeight, SetHeight, float, 5.0f, AM_DEFAULT);
+}
+
+void Obstacle::OnSetEnabled()
+{
+    bool enabled = IsEnabledEffective();
+
+    if (ownerMesh_)
+    {
+        if (enabled)
+        {
+            ownerMesh_.Get()->AddObstacle(this);
+        }
+        else
+        {
+            ownerMesh_.Get()->RemoveObstacle(this);
+        }
+    }
+}
+
+void Obstacle::SetHeight(float newHeight)
+{
+    height_ = newHeight;
+    if (ownerMesh_)
+        ownerMesh_->ObstacleChanged(this);
+    MarkNetworkUpdate();
+}
+
+void Obstacle::SetRadius(float newRadius)
+{
+    radius_ = newRadius;
+    if (ownerMesh_)
+        ownerMesh_->ObstacleChanged(this);
+    MarkNetworkUpdate();
+}
+
+void Obstacle::OnNodeSet(Node* node)
+{
+    if (node)
+    {
+        if (GetScene() == node)
+        {
+            LOGWARNING(GetTypeName() + " should not be created to the root scene node");
+            return;
+        }
+        if (ownerMesh_)
+            return;
+        ownerMesh_ = GetScene()->GetComponent<DynamicNavigationMesh>();
+        if (ownerMesh_)
+        {
+            ownerMesh_.Get()->AddObstacle(this);
+        }
+    }
+}
+
+void Obstacle::DrawDebugGeometry(DebugRenderer* debug, bool depthTest)
+{
+    if (IsEnabledEffective())
+    {
+        debug->AddCylinder(node_->GetWorldPosition(), radius_, height_, Color(0.0f, 1.0f, 1.0f), depthTest);
+    }
+}
+
+void Obstacle::DrawDebugGeometry(bool depthTest)
+{
+    Scene* scene = GetScene();
+    if (scene)
+    {
+        DebugRenderer* debug = scene->GetComponent<DebugRenderer>();
+        if (debug)
+            DrawDebugGeometry(debug, depthTest);
+    }
+}
+
+}

+ 81 - 0
Source/Urho3D/Navigation/Obstacle.h

@@ -0,0 +1,81 @@
+//
+// Copyright (c) 2008-2015 the Urho3D project.
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+//
+
+#pragma once
+
+#include "../Container/Ptr.h"
+#include "../Scene/Component.h"
+
+namespace Urho3D
+{
+    class DynamicNavigationMesh;
+
+    class URHO3D_API Obstacle : public Component
+    {
+        OBJECT(Obstacle)
+        friend class DynamicNavigationMesh;
+
+    public:
+        /// Construct
+        Obstacle(Context*);
+        /// Destruct
+        virtual ~Obstacle();
+
+        /// Register Obstacle with engine context
+        static void RegisterObject(Context*);
+
+        /// Updates the owning mesh when enabled status has changed
+        virtual void OnSetEnabled();
+
+        /// Gets the height of this obstacle
+        float GetHeight() const { return height_; }
+        /// Sets the height of this obstacle
+        void SetHeight(float);
+        /// Gets the blocking radius of this obstacle
+        float GetRadius() const { return radius_; }
+        /// Sets the blocking radius of this obstacle
+        void SetRadius(float);
+
+        /// Gets the internal obstacle ID
+        unsigned GetObstacleID() const { return obstacleId_; }
+
+        /// Render debug information
+        virtual void DrawDebugGeometry(DebugRenderer*, bool depthTest);
+        /// Simplified rendering of debug information for script usage
+        void DrawDebugGeometry(bool depthTest);
+
+    protected:
+        /// Handle node being assigned, identify our DynamicNavigationMesh
+        virtual void OnNodeSet(Node* node);
+
+    private:
+        /// radius of this obstacle
+        float radius_;
+        /// height of this obstacle, extends 1/2 height below and 1/2 height above the owning node's position
+        float height_;
+
+        /// id received from tile cache
+        unsigned obstacleId_;
+        /// pointer to the navigation mesh we belong to
+        WeakPtr<DynamicNavigationMesh> ownerMesh_;
+    };
+}

+ 16 - 0
Source/Urho3D/Navigation/OffMeshConnection.cpp

@@ -33,6 +33,8 @@ namespace Urho3D
 extern const char* NAVIGATION_CATEGORY;
 
 static const float DEFAULT_RADIUS = 1.0f;
+static const unsigned DEFAULT_MASK_FLAG = 1;
+static const unsigned DEFAULT_AREA = 0;
 
 OffMeshConnection::OffMeshConnection(Context* context) :
     Component(context),
@@ -55,6 +57,8 @@ void OffMeshConnection::RegisterObject(Context* context)
     ATTRIBUTE("Endpoint NodeID", int, endPointID_, 0, AM_DEFAULT | AM_NODEID);
     ATTRIBUTE("Radius", float, radius_, DEFAULT_RADIUS, AM_DEFAULT);
     ATTRIBUTE("Bidirectional", bool, bidirectional_, true, AM_DEFAULT);
+    ATTRIBUTE("Flags Mask", unsigned, mask_, DEFAULT_MASK_FLAG, AM_DEFAULT);
+    ATTRIBUTE("Area Type", unsigned, areaId_, DEFAULT_AREA, AM_DEFAULT);
 }
 
 void OffMeshConnection::OnSetAttribute(const AttributeInfo& attr, const Variant& src)
@@ -100,6 +104,18 @@ void OffMeshConnection::SetBidirectional(bool enabled)
     MarkNetworkUpdate();
 }
 
+void OffMeshConnection::SetMask(unsigned newMask)
+{
+    mask_ = newMask;
+    MarkNetworkUpdate();
+}
+
+void OffMeshConnection::SetAreaID(unsigned newAreaID)
+{
+    areaId_ = newAreaID;
+    MarkNetworkUpdate();
+}
+
 void OffMeshConnection::SetEndPoint(Node* node)
 {
     endPoint_ = node;

+ 12 - 0
Source/Urho3D/Navigation/OffMeshConnection.h

@@ -53,6 +53,10 @@ public:
     void SetRadius(float radius);
     /// Set bidirectional flag. Default true.
     void SetBidirectional(bool enabled);
+    /// Set a user assigned mask
+    void SetMask(unsigned newMask);
+    /// Sets the assigned area Id for the connection
+    void SetAreaID(unsigned newAreaID);
     
     /// Return endpoint node.
     Node* GetEndPoint() const;
@@ -60,6 +64,10 @@ public:
     float GetRadius() const { return radius_; }
     /// Return whether is bidirectional.
     bool IsBidirectional() const { return bidirectional_; }
+    /// Return the user assigned mask
+    unsigned GetMask() const { return mask_; }
+    /// Return the user assigned area ID
+    unsigned GetAreaID() const { return areaId_; }
     
 private:
     /// Endpoint node.
@@ -72,6 +80,10 @@ private:
     bool bidirectional_;
     /// Endpoint changed flag.
     bool endPointDirty_;
+    /// Flags mask to represent properties of this mesh
+    unsigned mask_;
+    /// Area id to be used for this off mesh connection's internal poly
+    unsigned areaId_;
 };
 
 }

+ 164 - 41
Source/Urho3D/Script/NavigationAPI.cpp

@@ -23,8 +23,13 @@
 #ifdef URHO3D_NAVIGATION
 #include "../Script/APITemplates.h"
 #include "../Navigation/Navigable.h"
+#include "../Navigation/CrowdAgent.h"
 #include "../Navigation/NavigationMesh.h"
+#include "../Navigation/DetourCrowdManager.h"
+#include "../Navigation/DynamicNavigationMesh.h"
 #include "../Navigation/OffMeshConnection.h"
+#include "../Navigation/NavArea.h"
+#include "../Navigation/Obstacle.h"
 
 namespace Urho3D
 {
@@ -43,51 +48,79 @@ static CScriptArray* NavigationMeshFindPath(const Vector3& start, const Vector3&
     return VectorToArray<Vector3>(dest, "Array<Vector3>");
 }
 
+static CScriptArray* DynamicNavigationMeshFindPath(const Vector3& start, const Vector3& end, const Vector3& extents, DynamicNavigationMesh* ptr)
+{
+	PODVector<Vector3> dest;
+	ptr->FindPath(dest, start, end, extents);
+	return VectorToArray<Vector3>(dest, "Array<Vector3>");
+}
+
+static CScriptArray* DetourCrowdManagerGetActiveAgents(DetourCrowdManager* crowd)
+{
+	PODVector<CrowdAgent*> agents = crowd->GetActiveAgents();
+	return VectorToHandleArray<CrowdAgent>(agents, "Array<CrowdAgent@>");
+}
+
+template<class T> static void RegisterNavMeshBase(asIScriptEngine* engine, const char* name)
+{
+	engine->RegisterObjectMethod(name, "bool Build()", asMETHODPR(T, Build, (void), bool), asCALL_THISCALL);
+	engine->RegisterObjectMethod(name, "bool Build(const BoundingBox&in)", asMETHODPR(T, Build, (const BoundingBox&), bool), asCALL_THISCALL);
+	engine->RegisterObjectMethod(name, "void SetAreaTypeCost(uint, float)", asMETHOD(T, SetAreaTypeCost), asCALL_THISCALL);
+	engine->RegisterObjectMethod(name, "Vector3 FindNearestPoint(const Vector3&in, const Vector3&in extents = Vector3(1.0, 1.0, 1.0))", asMETHOD(T, FindNearestPoint), asCALL_THISCALL);
+	engine->RegisterObjectMethod(name, "Vector3 MoveAlongSurface(const Vector3&in, const Vector3&in, const Vector3&in extents = Vector3(1.0, 1.0, 1.0), uint = 3)", asMETHOD(T, MoveAlongSurface), asCALL_THISCALL);
+	//engine->RegisterObjectMethod(name, "Array<Vector3>@ FindPath(const Vector3&in, const Vector3&in, const Vector3&in extents = Vector3(1.0, 1.0, 1.0))", asFUNCTION(NavigationMeshFindPath), asCALL_CDECL_OBJLAST);
+	engine->RegisterObjectMethod(name, "Vector3 GetRandomPoint()", asMETHOD(T, GetRandomPoint), asCALL_THISCALL);
+	engine->RegisterObjectMethod(name, "Vector3 GetRandomPointInCircle(const Vector3&in, float, const Vector3&in extents = Vector3(1.0, 1.0, 1.0))", asMETHOD(T, GetRandomPointInCircle), asCALL_THISCALL);
+	engine->RegisterObjectMethod(name, "float GetDistanceToWall(const Vector3&in, float, const Vector3&in extents = Vector3(1.0, 1.0, 1.0))", asMETHOD(T, GetDistanceToWall), asCALL_THISCALL);
+	engine->RegisterObjectMethod(name, "Vector3 Raycast(const Vector3&in, const Vector3&in, const Vector3&in extents = Vector3(1.0, 1.0, 1.0))", asMETHOD(T, Raycast), asCALL_THISCALL);
+	engine->RegisterObjectMethod(name, "void DrawDebugGeometry(bool)", asMETHODPR(NavigationMesh, DrawDebugGeometry, (bool), void), asCALL_THISCALL);
+	engine->RegisterObjectMethod(name, "void set_tileSize(int)", asMETHOD(T, SetTileSize), asCALL_THISCALL);
+	engine->RegisterObjectMethod(name, "int get_tileSize() const", asMETHOD(T, GetTileSize), asCALL_THISCALL);
+	engine->RegisterObjectMethod(name, "void set_cellSize(float)", asMETHOD(T, SetCellSize), asCALL_THISCALL);
+	engine->RegisterObjectMethod(name, "float get_cellSize() const", asMETHOD(T, GetCellSize), asCALL_THISCALL);
+	engine->RegisterObjectMethod(name, "void set_cellHeight(float)", asMETHOD(T, SetCellHeight), asCALL_THISCALL);
+	engine->RegisterObjectMethod(name, "float get_cellHeight() const", asMETHOD(T, GetCellHeight), asCALL_THISCALL);
+	engine->RegisterObjectMethod(name, "void set_agentHeight(float)", asMETHOD(T, SetAgentHeight), asCALL_THISCALL);
+	engine->RegisterObjectMethod(name, "float get_agentHeight() const", asMETHOD(T, GetAgentHeight), asCALL_THISCALL);
+	engine->RegisterObjectMethod(name, "void set_agentRadius(float)", asMETHOD(T, SetAgentRadius), asCALL_THISCALL);
+	engine->RegisterObjectMethod(name, "float get_agentRadius() const", asMETHOD(T, GetAgentRadius), asCALL_THISCALL);
+	engine->RegisterObjectMethod(name, "void set_agentMaxClimb(float)", asMETHOD(T, SetAgentMaxClimb), asCALL_THISCALL);
+	engine->RegisterObjectMethod(name, "float get_agentMaxClimb() const", asMETHOD(T, GetAgentMaxClimb), asCALL_THISCALL);
+	engine->RegisterObjectMethod(name, "void set_agentMaxSlope(float)", asMETHOD(T, SetAgentMaxSlope), asCALL_THISCALL);
+	engine->RegisterObjectMethod(name, "float get_agentMaxSlope() const", asMETHOD(T, GetAgentMaxSlope), asCALL_THISCALL);
+	engine->RegisterObjectMethod(name, "void set_regionMinSize(float)", asMETHOD(T, SetRegionMinSize), asCALL_THISCALL);
+	engine->RegisterObjectMethod(name, "float get_regionMinSize() const", asMETHOD(T, GetRegionMinSize), asCALL_THISCALL);
+	engine->RegisterObjectMethod(name, "void set_regionMergeSize(float)", asMETHOD(T, SetRegionMergeSize), asCALL_THISCALL);
+	engine->RegisterObjectMethod(name, "float get_regionMergeSize() const", asMETHOD(T, GetRegionMergeSize), asCALL_THISCALL);
+	engine->RegisterObjectMethod(name, "void set_edgeMaxLength(float)", asMETHOD(T, SetEdgeMaxLength), asCALL_THISCALL);
+	engine->RegisterObjectMethod(name, "float get_edgeMaxLength() const", asMETHOD(T, GetEdgeMaxLength), asCALL_THISCALL);
+	engine->RegisterObjectMethod(name, "void set_edgeMaxError(float)", asMETHOD(T, SetEdgeMaxError), asCALL_THISCALL);
+	engine->RegisterObjectMethod(name, "float get_edgeMaxError() const", asMETHOD(T, GetEdgeMaxError), asCALL_THISCALL);
+	engine->RegisterObjectMethod(name, "void set_detailSampleDistance(float)", asMETHOD(T, SetDetailSampleDistance), asCALL_THISCALL);
+	engine->RegisterObjectMethod(name, "float get_detailSampleDistance() const", asMETHOD(T, GetDetailSampleDistance), asCALL_THISCALL);
+	engine->RegisterObjectMethod(name, "void set_detailSampleMaxError(float)", asMETHOD(T, SetDetailSampleMaxError), asCALL_THISCALL);
+	engine->RegisterObjectMethod(name, "float get_detailSampleMaxError() const", asMETHOD(T, GetDetailSampleMaxError), asCALL_THISCALL);
+	engine->RegisterObjectMethod(name, "void set_padding(const Vector3&in)", asMETHOD(T, SetPadding), asCALL_THISCALL);
+	engine->RegisterObjectMethod(name, "const Vector3& get_padding() const", asMETHOD(T, GetPadding), asCALL_THISCALL);
+	engine->RegisterObjectMethod(name, "bool get_initialized() const", asMETHOD(T, IsInitialized), asCALL_THISCALL);
+	engine->RegisterObjectMethod(name, "const BoundingBox& get_boundingBox() const", asMETHOD(T, GetBoundingBox), asCALL_THISCALL);
+	engine->RegisterObjectMethod(name, "BoundingBox get_worldBoundingBox() const", asMETHOD(T, GetWorldBoundingBox), asCALL_THISCALL);
+	engine->RegisterObjectMethod(name, "IntVector2 get_numTiles() const", asMETHOD(T, GetNumTiles), asCALL_THISCALL);
+}
+
 void RegisterNavigationMesh(asIScriptEngine* engine)
 {
     RegisterComponent<NavigationMesh>(engine, "NavigationMesh");
-    engine->RegisterObjectMethod("NavigationMesh", "bool Build()", asMETHODPR(NavigationMesh, Build, (void), bool), asCALL_THISCALL);
-    engine->RegisterObjectMethod("NavigationMesh", "bool Build(const BoundingBox&in)", asMETHODPR(NavigationMesh, Build, (const BoundingBox&), bool), asCALL_THISCALL);
-    engine->RegisterObjectMethod("NavigationMesh", "Vector3 FindNearestPoint(const Vector3&in, const Vector3&in extents = Vector3(1.0, 1.0, 1.0))", asMETHOD(NavigationMesh, FindNearestPoint), asCALL_THISCALL);
-    engine->RegisterObjectMethod("NavigationMesh", "Vector3 MoveAlongSurface(const Vector3&in, const Vector3&in, const Vector3&in extents = Vector3(1.0, 1.0, 1.0), uint = 3)", asMETHOD(NavigationMesh, MoveAlongSurface), asCALL_THISCALL);
+	RegisterNavMeshBase<NavigationMesh>(engine, "NavigationMesh");
     engine->RegisterObjectMethod("NavigationMesh", "Array<Vector3>@ FindPath(const Vector3&in, const Vector3&in, const Vector3&in extents = Vector3(1.0, 1.0, 1.0))", asFUNCTION(NavigationMeshFindPath), asCALL_CDECL_OBJLAST);
-    engine->RegisterObjectMethod("NavigationMesh", "Vector3 GetRandomPoint()", asMETHOD(NavigationMesh, GetRandomPoint), asCALL_THISCALL);
-    engine->RegisterObjectMethod("NavigationMesh", "Vector3 GetRandomPointInCircle(const Vector3&in, float, const Vector3&in extents = Vector3(1.0, 1.0, 1.0))", asMETHOD(NavigationMesh, GetRandomPointInCircle), asCALL_THISCALL);
-    engine->RegisterObjectMethod("NavigationMesh", "float GetDistanceToWall(const Vector3&in, float, const Vector3&in extents = Vector3(1.0, 1.0, 1.0))", asMETHOD(NavigationMesh, GetDistanceToWall), asCALL_THISCALL);
-    engine->RegisterObjectMethod("NavigationMesh", "Vector3 Raycast(const Vector3&in, const Vector3&in, const Vector3&in extents = Vector3(1.0, 1.0, 1.0))", asMETHOD(NavigationMesh, Raycast), asCALL_THISCALL);
-    engine->RegisterObjectMethod("NavigationMesh", "void DrawDebugGeometry(bool)", asMETHODPR(NavigationMesh, DrawDebugGeometry, (bool), void), asCALL_THISCALL);
-    engine->RegisterObjectMethod("NavigationMesh", "void set_tileSize(int)", asMETHOD(NavigationMesh, SetTileSize), asCALL_THISCALL);
-    engine->RegisterObjectMethod("NavigationMesh", "int get_tileSize() const", asMETHOD(NavigationMesh, GetTileSize), asCALL_THISCALL);
-    engine->RegisterObjectMethod("NavigationMesh", "void set_cellSize(float)", asMETHOD(NavigationMesh, SetCellSize), asCALL_THISCALL);
-    engine->RegisterObjectMethod("NavigationMesh", "float get_cellSize() const", asMETHOD(NavigationMesh, GetCellSize), asCALL_THISCALL);
-    engine->RegisterObjectMethod("NavigationMesh", "void set_cellHeight(float)", asMETHOD(NavigationMesh, SetCellHeight), asCALL_THISCALL);
-    engine->RegisterObjectMethod("NavigationMesh", "float get_cellHeight() const", asMETHOD(NavigationMesh, GetCellHeight), asCALL_THISCALL);
-    engine->RegisterObjectMethod("NavigationMesh", "void set_agentHeight(float)", asMETHOD(NavigationMesh, SetAgentHeight), asCALL_THISCALL);
-    engine->RegisterObjectMethod("NavigationMesh", "float get_agentHeight() const", asMETHOD(NavigationMesh, GetAgentHeight), asCALL_THISCALL);
-    engine->RegisterObjectMethod("NavigationMesh", "void set_agentRadius(float)", asMETHOD(NavigationMesh, SetAgentRadius), asCALL_THISCALL);
-    engine->RegisterObjectMethod("NavigationMesh", "float get_agentRadius() const", asMETHOD(NavigationMesh, GetAgentRadius), asCALL_THISCALL);
-    engine->RegisterObjectMethod("NavigationMesh", "void set_agentMaxClimb(float)", asMETHOD(NavigationMesh, SetAgentMaxClimb), asCALL_THISCALL);
-    engine->RegisterObjectMethod("NavigationMesh", "float get_agentMaxClimb() const", asMETHOD(NavigationMesh, GetAgentMaxClimb), asCALL_THISCALL);
-    engine->RegisterObjectMethod("NavigationMesh", "void set_agentMaxSlope(float)", asMETHOD(NavigationMesh, SetAgentMaxSlope), asCALL_THISCALL);
-    engine->RegisterObjectMethod("NavigationMesh", "float get_agentMaxSlope() const", asMETHOD(NavigationMesh, GetAgentMaxSlope), asCALL_THISCALL);
-    engine->RegisterObjectMethod("NavigationMesh", "void set_regionMinSize(float)", asMETHOD(NavigationMesh, SetRegionMinSize), asCALL_THISCALL);
-    engine->RegisterObjectMethod("NavigationMesh", "float get_regionMinSize() const", asMETHOD(NavigationMesh, GetRegionMinSize), asCALL_THISCALL);
-    engine->RegisterObjectMethod("NavigationMesh", "void set_regionMergeSize(float)", asMETHOD(NavigationMesh, SetRegionMergeSize), asCALL_THISCALL);
-    engine->RegisterObjectMethod("NavigationMesh", "float get_regionMergeSize() const", asMETHOD(NavigationMesh, GetRegionMergeSize), asCALL_THISCALL);
-    engine->RegisterObjectMethod("NavigationMesh", "void set_edgeMaxLength(float)", asMETHOD(NavigationMesh, SetEdgeMaxLength), asCALL_THISCALL);
-    engine->RegisterObjectMethod("NavigationMesh", "float get_edgeMaxLength() const", asMETHOD(NavigationMesh, GetEdgeMaxLength), asCALL_THISCALL);
-    engine->RegisterObjectMethod("NavigationMesh", "void set_edgeMaxError(float)", asMETHOD(NavigationMesh, SetEdgeMaxError), asCALL_THISCALL);
-    engine->RegisterObjectMethod("NavigationMesh", "float get_edgeMaxError() const", asMETHOD(NavigationMesh, GetEdgeMaxError), asCALL_THISCALL);
-    engine->RegisterObjectMethod("NavigationMesh", "void set_detailSampleDistance(float)", asMETHOD(NavigationMesh, SetDetailSampleDistance), asCALL_THISCALL);
-    engine->RegisterObjectMethod("NavigationMesh", "float get_detailSampleDistance() const", asMETHOD(NavigationMesh, GetDetailSampleDistance), asCALL_THISCALL);
-    engine->RegisterObjectMethod("NavigationMesh", "void set_detailSampleMaxError(float)", asMETHOD(NavigationMesh, SetDetailSampleMaxError), asCALL_THISCALL);
-    engine->RegisterObjectMethod("NavigationMesh", "float get_detailSampleMaxError() const", asMETHOD(NavigationMesh, GetDetailSampleMaxError), asCALL_THISCALL);
-    engine->RegisterObjectMethod("NavigationMesh", "void set_padding(const Vector3&in)", asMETHOD(NavigationMesh, SetPadding), asCALL_THISCALL);
-    engine->RegisterObjectMethod("NavigationMesh", "const Vector3& get_padding() const", asMETHOD(NavigationMesh, GetPadding), asCALL_THISCALL);
-    engine->RegisterObjectMethod("NavigationMesh", "bool get_initialized() const", asMETHOD(NavigationMesh, IsInitialized), asCALL_THISCALL);
-    engine->RegisterObjectMethod("NavigationMesh", "const BoundingBox& get_boundingBox() const", asMETHOD(NavigationMesh, GetBoundingBox), asCALL_THISCALL);
-    engine->RegisterObjectMethod("NavigationMesh", "BoundingBox get_worldBoundingBox() const", asMETHOD(NavigationMesh, GetWorldBoundingBox), asCALL_THISCALL);
-    engine->RegisterObjectMethod("NavigationMesh", "IntVector2 get_numTiles() const", asMETHOD(NavigationMesh, GetNumTiles), asCALL_THISCALL);
+}
+
+void RegisterDynamicNavigationMesh(asIScriptEngine* engine)
+{
+	RegisterComponent<DynamicNavigationMesh>(engine, "DynamicNavigationMesh");
+	RegisterSubclass<NavigationMesh, DynamicNavigationMesh>(engine, "NavigationMesh", "DynamicNavigationMesh");
+	RegisterNavMeshBase<DynamicNavigationMesh>(engine, "DynamicNavigationMesh");
+	engine->RegisterObjectMethod("DynamicNavigationMesh", "Array<Vector3>@ FindPath(const Vector3&in, const Vector3&in, const Vector3&in extents = Vector3(1.0, 1.0, 1.0))", asFUNCTION(DynamicNavigationMeshFindPath), asCALL_CDECL_OBJLAST);
 }
 
 void RegisterOffMeshConnection(asIScriptEngine* engine)
@@ -101,11 +134,101 @@ void RegisterOffMeshConnection(asIScriptEngine* engine)
     engine->RegisterObjectMethod("OffMeshConnection", "bool get_bidirectional() const", asMETHOD(OffMeshConnection, IsBidirectional), asCALL_THISCALL);
 }
 
+void RegisterObstacle(asIScriptEngine* engine)
+{
+	RegisterComponent<Obstacle>(engine, "Obstacle");
+	engine->RegisterObjectMethod("Obstacle", "float get_radius() const", asMETHOD(Obstacle, GetRadius), asCALL_THISCALL);
+	engine->RegisterObjectMethod("Obstacle", "void set_radius(float)", asMETHOD(Obstacle, SetRadius), asCALL_THISCALL);
+	engine->RegisterObjectMethod("Obstacle", "float get_height() const", asMETHOD(Obstacle, GetHeight), asCALL_THISCALL);
+	engine->RegisterObjectMethod("Obstacle", "void set_height(float)", asMETHOD(Obstacle, SetHeight), asCALL_THISCALL);
+	engine->RegisterObjectMethod("Obstacle", "uint get_obstacleId() const", asMETHOD(Obstacle, GetObstacleID), asCALL_THISCALL);
+    engine->RegisterObjectMethod("Obstacle", "void DrawDebugGeometry(bool)", asMETHODPR(Obstacle, DrawDebugGeometry, (bool), void), asCALL_THISCALL);
+}
+
+void RegisterNavArea(asIScriptEngine* engine)
+{
+	RegisterComponent<NavArea>(engine, "NavArea");
+	engine->RegisterObjectMethod("NavArea", "BoundingBox get_bounds() const", asMETHOD(NavArea, GetBounds), asCALL_THISCALL);
+	engine->RegisterObjectMethod("NavArea", "void set_bounds(const BoundingBox&in)", asMETHOD(NavArea, SetBounds), asCALL_THISCALL);
+	engine->RegisterObjectMethod("NavArea", "uint get_areaType() const", asMETHOD(NavArea, GetAreaType), asCALL_THISCALL);
+	engine->RegisterObjectMethod("NavArea", "void set_areaType(uint)", asMETHOD(NavArea, SetAreaType), asCALL_THISCALL);
+	engine->RegisterObjectMethod("NavArea", "BoundingBox get_transformedBounds() const", asMETHOD(NavArea, GetTransformedBounds), asCALL_THISCALL);
+}
+
+void RegisterDetourCrowdManager(asIScriptEngine* engine)
+{
+	RegisterComponent<DetourCrowdManager>(engine, "DetourCrowdManager");
+	engine->RegisterObjectMethod("DetourCrowdManager", "void CreateCrowd()", asMETHOD(DetourCrowdManager, CreateCrowd), asCALL_THISCALL);
+	engine->RegisterObjectMethod("DetourCrowdManager", "void set_navMesh(NavigationMesh@+)", asMETHOD(DetourCrowdManager, SetNavigationMesh), asCALL_THISCALL);
+	engine->RegisterObjectMethod("DetourCrowdManager", "NavigationMesh@+ get_navMesh() const", asMETHOD(DetourCrowdManager, GetNavigationMesh), asCALL_THISCALL);
+    engine->RegisterObjectMethod("DetourCrowdManager", "int get_maxAgents() const", asMETHOD(DetourCrowdManager, GetMaxAgents), asCALL_THISCALL);
+    engine->RegisterObjectMethod("DetourCrowdManager", "void set_maxAgents(int)", asMETHOD(DetourCrowdManager, SetMaxAgents), asCALL_THISCALL);
+	engine->RegisterObjectMethod("DetourCrowdManager", "Array<CrowdAgent@>@ GetActiveAgents()", asFUNCTION(DetourCrowdManagerGetActiveAgents), asCALL_CDECL_OBJLAST);
+	engine->RegisterObjectMethod("DetourCrowdManager", "void SetAreaTypeCost(uint, uint, float)", asMETHOD(DetourCrowdManager, SetAreaTypeCost), asCALL_THISCALL);
+	engine->RegisterObjectMethod("DetourCrowdManager", "float GetAreaTypeCost(uint, uint)", asMETHOD(DetourCrowdManager, GetAreaTypeCost), asCALL_THISCALL);
+}
+
+void RegisterCrowdAgent(asIScriptEngine* engine)
+{
+	engine->RegisterEnum("CrowdTargetState");
+	engine->RegisterEnumValue("CrowdTargetState", "CROWD_AGENT_TARGET_NONE", CROWD_AGENT_TARGET_NONE);
+	engine->RegisterEnumValue("CrowdTargetState", "CROWD_AGENT_TARGET_FAILED", CROWD_AGENT_TARGET_FAILED);
+	engine->RegisterEnumValue("CrowdTargetState", "CROWD_AGENT_TARGET_VALID", CROWD_AGENT_TARGET_VALID);
+	engine->RegisterEnumValue("CrowdTargetState", "CROWD_AGENT_TARGET_REQUESTING", CROWD_AGENT_TARGET_REQUESTING);
+	engine->RegisterEnumValue("CrowdTargetState", "CROWD_AGENT_TARGET_WAITINGFORPATH", CROWD_AGENT_TARGET_WAITINGFORPATH);
+	engine->RegisterEnumValue("CrowdTargetState", "CROWD_AGENT_TARGET_WAITINGFORQUEUE", CROWD_AGENT_TARGET_WAITINGFORQUEUE);
+	engine->RegisterEnumValue("CrowdTargetState", "CROWD_AGENT_TARGET_VELOCITY", CROWD_AGENT_TARGET_VELOCITY);
+	engine->RegisterEnumValue("CrowdTargetState", "CROWD_AGENT_TARGET_ARRIVED", CROWD_AGENT_TARGET_ARRIVED);
+
+	engine->RegisterEnum("CrowdAgentState");
+	engine->RegisterEnumValue("CrowdAgentState", "NAV_AGENT_INVALID", CROWD_AGENT_INVALID);
+	engine->RegisterEnumValue("CrowdAgentState", "NAV_AGENT_READY", CROWD_AGENT_READY);
+	engine->RegisterEnumValue("CrowdAgentState", "NAV_AGENT_TRAVERSINGLINK", CROWD_AGENT_TRAVERSINGLINK);
+
+	engine->RegisterEnum("NavigationAvoidanceQuality");
+	engine->RegisterEnumValue("NavigationAvoidanceQuality", "NAVIGATIONQUALITY_LOW", NAVIGATIONQUALITY_LOW);
+	engine->RegisterEnumValue("NavigationAvoidanceQuality", "NAVIGATIONQUALITY_MEDIUM", NAVIGATIONQUALITY_MEDIUM);
+	engine->RegisterEnumValue("NavigationAvoidanceQuality", "NAVIGATIONQUALITY_HIGH", NAVIGATIONQUALITY_HIGH);
+
+	engine->RegisterEnum("NavigationPushiness");
+	engine->RegisterEnumValue("NavigationPushiness", "PUSHINESS_LOW", PUSHINESS_LOW);
+	engine->RegisterEnumValue("NavigationPushiness", "PUSHINESS_MEDIUM", PUSHINESS_MEDIUM);
+	engine->RegisterEnumValue("NavigationPushiness", "PUSHINESS_HIGH", PUSHINESS_HIGH);
+	
+	RegisterComponent<CrowdAgent>(engine, "CrowdAgent");
+	engine->RegisterObjectMethod("CrowdAgent", "void DrawDebugGeometry(bool)", asMETHODPR(CrowdAgent, DrawDebugGeometry, (bool), void), asCALL_THISCALL);
+	engine->RegisterObjectMethod("CrowdAgent", "uint get_navigationFilterType()", asMETHOD(CrowdAgent, GetNavigationFilterType), asCALL_THISCALL);
+	engine->RegisterObjectMethod("CrowdAgent", "void set_navigationFilterType(uint)", asMETHOD(CrowdAgent, SetNavigationFilterType), asCALL_THISCALL);
+	engine->RegisterObjectMethod("CrowdAgent", "bool SetMoveTarget(const Vector3&in)", asMETHOD(CrowdAgent, SetMoveTarget), asCALL_THISCALL);
+	engine->RegisterObjectMethod("CrowdAgent", "bool SetMoveVelocity(const Vector3&in)", asMETHOD(CrowdAgent, SetMoveVelocity), asCALL_THISCALL);
+	engine->RegisterObjectMethod("CrowdAgent", "void set_updateNodePosition(bool)", asMETHOD(CrowdAgent, SetUpdateNodePosition), asCALL_THISCALL);
+	engine->RegisterObjectMethod("CrowdAgent", "bool get_updateNodePosition()", asMETHOD(CrowdAgent, GetUpdateNodePosition), asCALL_THISCALL);
+	engine->RegisterObjectMethod("CrowdAgent", "void set_maxAccel(float)", asMETHOD(CrowdAgent, SetMaxAccel), asCALL_THISCALL);
+	engine->RegisterObjectMethod("CrowdAgent", "float get_maxAccel()", asMETHOD(CrowdAgent, GetMaxAccel), asCALL_THISCALL);
+	engine->RegisterObjectMethod("CrowdAgent", "void set_maxSpeed(float)", asMETHOD(CrowdAgent, SetMaxSpeed), asCALL_THISCALL);
+	engine->RegisterObjectMethod("CrowdAgent", "float get_maxSpeed()", asMETHOD(CrowdAgent, GetMaxSpeed), asCALL_THISCALL);
+	engine->RegisterObjectMethod("CrowdAgent", "void set_navigationQuality(NavigationAvoidanceQuality)", asMETHOD(CrowdAgent, SetNavigationQuality), asCALL_THISCALL);
+	engine->RegisterObjectMethod("CrowdAgent", "NavigationAvoidanceQuality get_navigationQuality()", asMETHOD(CrowdAgent, GetNavigationQuality), asCALL_THISCALL);
+	engine->RegisterObjectMethod("CrowdAgent", "void set_navigationPushiness(NavigationPushiness)", asMETHOD(CrowdAgent, SetNavigationPushiness), asCALL_THISCALL);
+	engine->RegisterObjectMethod("CrowdAgent", "NavigationPushiness get_navigationPushiness()", asMETHOD(CrowdAgent, GetNavigationPushiness), asCALL_THISCALL);
+	engine->RegisterObjectMethod("CrowdAgent", "Vector3 get_desiredVelocity() const", asMETHOD(CrowdAgent, GetDesiredVelocity), asCALL_THISCALL);
+	engine->RegisterObjectMethod("CrowdAgent", "Vector3 get_actualVelocity() const", asMETHOD(CrowdAgent, GetActualVelocity), asCALL_THISCALL);
+	engine->RegisterObjectMethod("CrowdAgent", "Vector3 get_targetPosition() const", asMETHOD(CrowdAgent, GetTargetPosition), asCALL_THISCALL);
+	engine->RegisterObjectMethod("CrowdAgent", "CrowdAgentState GetAgentState() const", asMETHOD(CrowdAgent, GetAgentState), asCALL_THISCALL);
+	engine->RegisterObjectMethod("CrowdAgent", "CrowdTargetState get_targetState() const", asMETHOD(CrowdAgent, GetTargetState), asCALL_THISCALL);
+	engine->RegisterObjectMethod("CrowdAgent", "Vector3 get_position() const", asMETHOD(CrowdAgent, GetPosition), asCALL_THISCALL);
+}
+
 void RegisterNavigationAPI(asIScriptEngine* engine)
 {
     RegisterNavigable(engine);
     RegisterNavigationMesh(engine);
+	RegisterDynamicNavigationMesh(engine);
     RegisterOffMeshConnection(engine);
+	RegisterCrowdAgent(engine);
+	RegisterDetourCrowdManager(engine);
+	RegisterObstacle(engine);
+	RegisterNavArea(engine);
 }
 
 }

+ 441 - 0
bin/Data/LuaScripts/39_CrowdNavigation.lua

@@ -0,0 +1,441 @@
+-- CrowdNavigation example.
+-- This sample demonstrates:
+--     - Generating a cached navigation mesh into the scene
+--     - Performing path queries to the navigation mesh
+--     - Adding and removing obstacles at runtime from the dynamic mesh
+--     - Visualizing custom debug geometry
+--     - Raycasting drawable components
+--     - Making a node follow the Detour path
+--     - Crowd movement management
+
+require "LuaScripts/Utilities/Sample"
+
+local endPos = nil
+local currentPath = {}
+local jackNodes = {}
+local crowdManager = nil
+local mushrooms = {}
+
+function Start()
+    -- Execute the common startup for samples
+    SampleStart()
+
+    -- Create the scene content
+    CreateScene()
+
+    -- Create the UI content
+    CreateUI()
+
+    -- Setup the viewport for displaying the scene
+    SetupViewport()
+
+    -- Hook up to the frame update and render post-update events
+    SubscribeToEvents()
+end
+
+function CreateScene()
+    scene_ = Scene()
+    -- Create octree, use default volume (-1000, -1000, -1000) to (1000, 1000, 1000)
+    -- Also create a DebugRenderer component so that we can draw debug geometry
+    scene_:CreateComponent("Octree")
+    scene_:CreateComponent("DebugRenderer")
+    scene_:CreateComponent("PhysicsWorld")
+
+    -- Create scene node & StaticModel component for showing a static plane
+    local planeNode = scene_:CreateChild("Plane")
+    planeNode.scale = Vector3(100.0, 1.0, 100.0)
+    local planeObject = planeNode:CreateComponent("StaticModel")
+    planeObject.model = cache:GetResource("Model", "Models/Plane.mdl")
+    planeObject.material = cache:GetResource("Material", "Materials/StoneTiled.xml")
+
+    -- Create a Zone component for ambient lighting & fog control
+    local zoneNode = scene_:CreateChild("Zone")
+    local zone = zoneNode:CreateComponent("Zone")
+    zone.boundingBox = BoundingBox(-1000.0, 1000.0)
+    zone.ambientColor = Color(0.15, 0.15, 0.15)
+    zone.fogColor = Color(0.5, 0.5, 0.7)
+    zone.fogStart = 100.0
+    zone.fogEnd = 300.0
+
+    -- Create a directional light to the world. Enable cascaded shadows on it
+    local lightNode = scene_:CreateChild("DirectionalLight")
+    lightNode.direction = Vector3(0.6, -1.0, 0.8)
+    local light = lightNode:CreateComponent("Light")
+    light.lightType = LIGHT_DIRECTIONAL
+    light.castShadows = true
+    light.shadowBias = BiasParameters(0.00025, 0.5)
+    -- Set cascade splits at 10, 50 and 200 world units, fade shadows out at 80% of maximum shadow distance
+    light.shadowCascade = CascadeParameters(10.0, 50.0, 200.0, 0.0, 0.8)
+
+    -- Create randomly sized boxes. If boxes are big enough, make them occluders. Occluders will be software rasterized before
+    -- rendering to a low-resolution depth-only buffer to test the objects in the view frustum for visibility
+    local NUM_BOXES = 20
+    for i = 1, NUM_BOXES do
+        local boxNode = scene_:CreateChild("Box")
+        local size = 1.0 + Random(10.0)
+        boxNode.position = Vector3(Random(80.0) - 40.0, size * 0.5, Random(80.0) - 40.0)
+        boxNode:SetScale(size)
+        local boxObject = boxNode:CreateComponent("StaticModel")
+        boxObject.model = cache:GetResource("Model", "Models/Box.mdl")
+        boxObject.material = cache:GetResource("Material", "Materials/Stone.xml")
+        boxObject.castShadows = true
+        if size >= 3.0 then
+            boxObject.occluder = true
+        end
+    end
+
+    -- Create a DynamicNavigationMesh component to the scene root
+    local navMesh = scene_:CreateComponent("DynamicNavigationMesh")
+    -- Set nav mesh tilesize to something reasonable
+    navMesh.tileSize = 64;
+    -- Create a Navigable component to the scene root. This tags all of the geometry in the scene as being part of the
+    -- navigation mesh. By default this is recursive, but the recursion could be turned off from Navigable
+    scene_:CreateComponent("Navigable")
+    -- Add padding to the navigation mesh in Y-direction so that we can add objects on top of the tallest boxes
+    -- in the scene and still update the mesh correctly
+    navMesh.padding = Vector3(0.0, 10.0, 0.0)
+    -- Now build the navigation geometry. This will take some time. Note that the navigation mesh will prefer to use
+    -- physics geometry from the scene nodes, as it often is simpler, but if it can not find any (like in this example)
+    -- it will use renderable geometry instead
+    navMesh:Build()
+
+    crowdManager = scene_:CreateComponent("DetourCrowdManager")
+    
+    -- Create Jack node that will follow the path
+    SpawnJack(Vector3(-5, 0, 20))
+    
+    -- Create some mushrooms
+    local NUM_MUSHROOMS = 100
+    for i = 1, NUM_MUSHROOMS do
+        CreateMushroom(Vector3(Random(90.0) - 45.0, 0.0, Random(90.0) - 45.0))
+    end
+    
+    -- Create the camera. Limit far clip distance to match the fog
+    cameraNode = scene_:CreateChild("Camera")
+    local camera = cameraNode:CreateComponent("Camera")
+    camera.farClip = 300.0
+
+    -- Set an initial position for the camera scene node above the plane
+    cameraNode.position = Vector3(0.0, 5.0, 0.0)
+end
+
+function CreateUI()
+    -- Create a Cursor UI element because we want to be able to hide and show it at will. When hidden, the mouse cursor will
+    -- control the camera, and when visible, it will point the raycast target
+    local style = cache:GetResource("XMLFile", "UI/DefaultStyle.xml")
+    local cursor = Cursor:new()
+    cursor:SetStyleAuto(style)
+    ui.cursor = cursor
+    -- Set starting position of the cursor at the rendering window center
+    cursor:SetPosition(graphics.width / 2, graphics.height / 2)
+
+    -- Construct new Text object, set string to display and font to use
+    local instructionText = ui.root:CreateChild("Text")
+    instructionText.text = "Use WASD keys to move, RMB to rotate view\n"..
+        "LMB to set destination, SHIFT+LMB to spawn a jack\n"..
+        "MMB to add or remove obstacles\n"..
+        "Space to toggle debug geometry"
+    instructionText:SetFont(cache:GetResource("Font", "Fonts/Anonymous Pro.ttf"), 15)
+    -- The text has multiple rows. Center them in relation to each other
+    instructionText.textAlignment = HA_CENTER
+
+    -- Position the text relative to the screen center
+    instructionText.horizontalAlignment = HA_CENTER
+    instructionText.verticalAlignment = VA_CENTER
+    instructionText:SetPosition(0, ui.root.height / 4)
+end
+
+function SetupViewport()
+    -- Set up a viewport to the Renderer subsystem so that the 3D scene can be seen
+    local viewport = Viewport:new(scene_, cameraNode:GetComponent("Camera"))
+    renderer:SetViewport(0, viewport)
+end
+
+function SubscribeToEvents()
+    -- Subscribe HandleUpdate() function for processing update events
+    SubscribeToEvent("Update", "HandleUpdate")
+
+    -- Subscribe HandlePostRenderUpdate() function for processing the post-render update event, during which we request
+    -- debug geometry
+    SubscribeToEvent("PostRenderUpdate", "HandlePostRenderUpdate")
+end
+
+function MoveCamera(timeStep)
+    -- Right mouse button controls mouse cursor visibility: hide when pressed
+    ui.cursor.visible = not input:GetMouseButtonDown(MOUSEB_RIGHT)
+
+    -- Do not move if the UI has a focused element (the console)
+    if ui.focusElement ~= nil then
+        return
+    end
+
+    -- Movement speed as world units per second
+    local MOVE_SPEED = 20.0
+    -- Mouse sensitivity as degrees per pixel
+    local MOUSE_SENSITIVITY = 0.1
+
+    -- Use this frame's mouse motion to adjust camera node yaw and pitch. Clamp the pitch between -90 and 90 degrees
+    -- Only move the camera when the cursor is hidden
+    if not ui.cursor.visible then
+        local mouseMove = input.mouseMove
+        yaw = yaw + MOUSE_SENSITIVITY * mouseMove.x
+        pitch = pitch + MOUSE_SENSITIVITY * mouseMove.y
+        pitch = Clamp(pitch, -90.0, 90.0)
+
+        -- Construct new orientation for the camera scene node from yaw and pitch. Roll is fixed to zero
+        cameraNode.rotation = Quaternion(pitch, yaw, 0.0)
+    end
+
+    -- Read WASD keys and move the camera scene node to the corresponding direction if they are pressed
+    if input:GetKeyDown(KEY_W) then
+        cameraNode:Translate(Vector3(0.0, 0.0, 1.0) * MOVE_SPEED * timeStep)
+    end
+    if input:GetKeyDown(KEY_S) then
+        cameraNode:Translate(Vector3(0.0, 0.0, -1.0) * MOVE_SPEED * timeStep)
+    end
+    if input:GetKeyDown(KEY_A) then
+        cameraNode:Translate(Vector3(-1.0, 0.0, 0.0) * MOVE_SPEED * timeStep)
+    end
+    if input:GetKeyDown(KEY_D) then
+        cameraNode:Translate(Vector3(1.0, 0.0, 0.0) * MOVE_SPEED * timeStep)
+    end
+    -- Set destination or spawn a jack with left mouse button
+    if input:GetMouseButtonPress(MOUSEB_LEFT) then
+        SetPathPoint()
+    end
+    -- Add or remove objects with middle mouse button, then rebuild navigation mesh partially
+    if input:GetMouseButtonPress(MOUSEB_MIDDLE) then
+        AddOrRemoveObject()
+    end
+    
+    -- Toggle debug geometry with space
+    if input:GetKeyPress(KEY_SPACE) then
+        drawDebug = not drawDebug
+    end
+end
+
+function SetPathPoint()
+    local result, hitPos, hitDrawable = Raycast(250.0)
+    local navMesh = scene_:GetComponent("DynamicNavigationMesh")
+    
+    if result then
+        local pathPos = navMesh:FindNearestPoint(hitPos, Vector3.ONE)
+        
+        if input:GetQualifierDown(QUAL_SHIFT) then
+            -- spawn a jack
+            SpawnJack(pathPos)
+        else
+            -- Calculate path from Jack's current position to the end point
+            local ct = table.maxn(jackNodes)
+            if ct > 0 then
+                for i = 1, ct do
+                    local agt = jackNodes[i]:GetComponent("CrowdAgent")
+                    agt.enabled = true
+                    if i == 1 then
+                        -- The first agent will always move to the exact target
+                        agt:SetMoveTarget(pathPos)
+                    else
+                        -- Keep the random point on the navigation mesh
+                        local targetPos = navMesh:FindNearestPoint(pathPos + Vector3(Random(7.0), 0, Random(7.0)), Vector3.ONE)
+                        agt:SetMoveTarget(targetPos)
+                    end
+                end
+            end
+        end
+    end
+end
+
+function GetIndexOf(tableTarget, objectTarget)
+    for k,v in pairs(tableTarget) do
+        if objectTarget == v then
+            return k
+        end
+    end
+    return -1
+end
+
+function AddOrRemoveObject()
+    -- Raycast and check if we hit a mushroom node. If yes, remove it, if no, create a new one
+    local result, hitPos, hitDrawable = Raycast(250.0)
+    if result then
+
+        local hitNode = hitDrawable:GetNode()
+        if hitNode.name == "Mushroom" then
+            local idx = GetIndexOf(mushrooms, hitNode)
+            if idx >= 0 then
+                table.remove(mushrooms, idx)
+            end
+            hitNode:Remove()
+        else
+            local newNode = CreateMushroom(hitPos)
+            local newObject = newNode:GetComponent("StaticModel")
+        end
+    end
+end
+
+function SpawnJack(pos)
+    local jackNode = scene_:CreateChild("Jack")
+    jackNode.position = pos
+    local modelObject = jackNode:CreateComponent("AnimatedModel")
+    modelObject.model = cache:GetResource("Model", "Models/Jack.mdl")
+    modelObject.material = cache:GetResource("Material", "Materials/Jack.xml")
+    modelObject.castShadows = true
+    local agent = jackNode:CreateComponent("CrowdAgent")
+    agent.enabled = false;
+    table.insert(jackNodes, jackNode)
+end
+
+function CreateMushroom(pos)
+    local mushroomNode = scene_:CreateChild("Mushroom")
+    mushroomNode.position = pos
+    mushroomNode.rotation = Quaternion(0.0, Random(360.0), 0.0)
+    mushroomNode:SetScale(2.0 + Random(0.5))
+    local mushroomObject = mushroomNode:CreateComponent("StaticModel")
+    mushroomObject.model = cache:GetResource("Model", "Models/Mushroom.mdl")
+    mushroomObject.material = cache:GetResource("Material", "Materials/Mushroom.xml")
+    mushroomObject.castShadows = true
+    local obstacleObject = mushroomNode:CreateComponent("Obstacle")
+    obstacleObject.radius = 2.5
+    table.insert(mushrooms, mushroomNode)
+    return mushroomNode
+end
+
+function Raycast(maxDistance)
+    local hitPos = nil
+    local hitDrawable = nil
+
+    local pos = ui.cursorPosition
+    -- Check the cursor is visible and there is no UI element in front of the cursor
+    if (not ui.cursor.visible) or (ui:GetElementAt(pos, true) ~= nil) then
+        return false, nil, nil
+    end
+
+    local camera = cameraNode:GetComponent("Camera")
+    local cameraRay = camera:GetScreenRay(pos.x / graphics.width, pos.y / graphics.height)
+    -- Pick only geometry objects, not eg. zones or lights, only get the first (closest) hit
+    local octree = scene_:GetComponent("Octree")
+    local result = octree:RaycastSingle(cameraRay, RAY_TRIANGLE, maxDistance, DRAWABLE_GEOMETRY)
+    if result.drawable ~= nil then
+        -- Calculate hit position in world space
+        hitPos = cameraRay.origin + cameraRay.direction * result.distance
+        hitDrawable = result.drawable
+        return true, hitPos, hitDrawable
+    end
+
+    return false, nil, nil
+end
+
+function HandleUpdate(eventType, eventData)
+    -- Take the frame time step, which is stored as a float
+    local timeStep = eventData:GetFloat("TimeStep")
+
+    -- Move the camera, scale movement with time step
+    MoveCamera(timeStep)
+    
+    -- Make the CrowdAgents face the direction of their velocity
+    local ct = table.maxn(jackNodes)
+    if ct > 0 then
+        for i = 1, ct do
+            local agent = jackNodes[i]:GetComponent("CrowdAgent")
+            jackNodes[i].worldDirection = agent:GetActualVelocity()
+        end
+    end
+end
+
+function HandlePostRenderUpdate(eventType, eventData)
+    -- If draw debug mode is enabled, draw navigation mesh debug geometry
+    if drawDebug then
+        local navMesh = scene_:GetComponent("DynamicNavigationMesh")
+        navMesh:DrawDebugGeometry(true)
+        
+        -- Visualize the start and end points and the last calculated path
+        local size = table.maxn(jackNodes)
+        if size > 0 then
+            for i = 1, size do
+                local agent = jackNodes[i]:GetComponent("CrowdAgent")
+                agent:DrawDebugGeometry(true)
+            end
+        end
+        
+        size = table.maxn(mushrooms)
+        if size > 0 then
+            for i = 1, size do
+                local obstacle = mushrooms[i]:GetComponent("Obstacle")
+                obstacle:DrawDebugGeometry(true)
+            end
+        end
+    end
+end
+
+-- Create XML patch instructions for screen joystick layout specific to this sample app
+function GetScreenJoystickPatchString()
+    return
+        "<patch>" ..
+        "    <add sel=\"/element\">" ..
+        "        <element type=\"Button\">" ..
+        "            <attribute name=\"Name\" value=\"Button3\" />" ..
+        "            <attribute name=\"Position\" value=\"-120 -120\" />" ..
+        "            <attribute name=\"Size\" value=\"96 96\" />" ..
+        "            <attribute name=\"Horiz Alignment\" value=\"Right\" />" ..
+        "            <attribute name=\"Vert Alignment\" value=\"Bottom\" />" ..
+        "            <attribute name=\"Texture\" value=\"Texture2D;Textures/TouchInput.png\" />" ..
+        "            <attribute name=\"Image Rect\" value=\"96 0 192 96\" />" ..
+        "            <attribute name=\"Hover Image Offset\" value=\"0 0\" />" ..
+        "            <attribute name=\"Pressed Image Offset\" value=\"0 0\" />" ..
+        "            <element type=\"Text\">" ..
+        "                <attribute name=\"Name\" value=\"Label\" />" ..
+        "                <attribute name=\"Horiz Alignment\" value=\"Center\" />" ..
+        "                <attribute name=\"Vert Alignment\" value=\"Center\" />" ..
+        "                <attribute name=\"Color\" value=\"0 0 0 1\" />" ..
+        "                <attribute name=\"Text\" value=\"Spawn Jack\" />" ..
+        "            </element>" ..
+        "            <element type=\"Text\">" ..
+        "                <attribute name=\"Name\" value=\"KeyBinding\" />" ..
+        "                <attribute name=\"Text\" value=\"LSHIFT\" />" ..
+        "            </element>" ..
+        "            <element type=\"Text\">" ..
+        "                <attribute name=\"Name\" value=\"MouseButtonBinding\" />" ..
+        "                <attribute name=\"Text\" value=\"LEFT\" />" ..
+        "            </element>" ..
+        "        </element>" ..
+        "        <element type=\"Button\">" ..
+        "            <attribute name=\"Name\" value=\"Button4\" />" ..
+        "            <attribute name=\"Position\" value=\"-120 -12\" />" ..
+        "            <attribute name=\"Size\" value=\"96 96\" />" ..
+        "            <attribute name=\"Horiz Alignment\" value=\"Right\" />" ..
+        "            <attribute name=\"Vert Alignment\" value=\"Bottom\" />" ..
+        "            <attribute name=\"Texture\" value=\"Texture2D;Textures/TouchInput.png\" />" ..
+        "            <attribute name=\"Image Rect\" value=\"96 0 192 96\" />" ..
+        "            <attribute name=\"Hover Image Offset\" value=\"0 0\" />" ..
+        "            <attribute name=\"Pressed Image Offset\" value=\"0 0\" />" ..
+        "            <element type=\"Text\">" ..
+        "                <attribute name=\"Name\" value=\"Label\" />" ..
+        "                <attribute name=\"Horiz Alignment\" value=\"Center\" />" ..
+        "                <attribute name=\"Vert Alignment\" value=\"Center\" />" ..
+        "                <attribute name=\"Color\" value=\"0 0 0 1\" />" ..
+        "                <attribute name=\"Text\" value=\"Obstacles\" />" ..
+        "            </element>" ..
+        "            <element type=\"Text\">" ..
+        "                <attribute name=\"Name\" value=\"MouseButtonBinding\" />" ..
+        "                <attribute name=\"Text\" value=\"MIDDLE\" />" ..
+        "            </element>" ..
+        "        </element>" ..
+        "    </add>" ..
+        "    <remove sel=\"/element/element[./attribute[@name='Name' and @value='Button0']]/attribute[@name='Is Visible']\" />" ..
+        "    <replace sel=\"/element/element[./attribute[@name='Name' and @value='Button0']]/element[./attribute[@name='Name' and @value='Label']]/attribute[@name='Text']/@value\">Set</replace>" ..
+        "    <add sel=\"/element/element[./attribute[@name='Name' and @value='Button0']]\">" ..
+        "        <element type=\"Text\">" ..
+        "            <attribute name=\"Name\" value=\"MouseButtonBinding\" />" ..
+        "            <attribute name=\"Text\" value=\"LEFT\" />" ..
+        "        </element>" ..
+        "    </add>" ..
+        "    <remove sel=\"/element/element[./attribute[@name='Name' and @value='Button1']]/attribute[@name='Is Visible']\" />" ..
+        "    <replace sel=\"/element/element[./attribute[@name='Name' and @value='Button1']]/element[./attribute[@name='Name' and @value='Label']]/attribute[@name='Text']/@value\">Debug</replace>" ..
+        "    <add sel=\"/element/element[./attribute[@name='Name' and @value='Button1']]\">" ..
+        "        <element type=\"Text\">" ..
+        "            <attribute name=\"Name\" value=\"KeyBinding\" />" ..
+        "            <attribute name=\"Text\" value=\"SPACE\" />" ..
+        "        </element>" ..
+        "    </add>" ..
+        "</patch>"
+end

+ 477 - 0
bin/Data/Scripts/39_CrowdNavigation.as

@@ -0,0 +1,477 @@
+// Navigation example.
+// This sample demonstrates:
+//     - Generating a navigation mesh into the scene
+//     - Performing path queries to the navigation mesh
+//     - Rebuilding the navigation mesh partially when adding or removing objects
+//     - Visualizing custom debug geometry
+//     - Raycasting drawable components
+//     - Making a node follow the Detour path
+
+#include "Scripts/Utilities/Sample.as"
+
+Vector3 endPos;
+Array<Vector3> currentPath;
+Array<Node@> jackNodes;
+Array<Node@> mushroomNodes;
+
+void Start()
+{
+    // Execute the common startup for samples
+    SampleStart();
+
+    // Create the scene content
+    CreateScene();
+
+    // Create the UI content
+    CreateUI();
+
+    // Setup the viewport for displaying the scene
+    SetupViewport();
+
+    // Hook up to the frame update and render post-update events
+    SubscribeToEvents();
+}
+
+void CreateScene()
+{
+    scene_ = Scene();
+
+    // Create octree, use default volume (-1000, -1000, -1000) to (1000, 1000, 1000)
+    // Also create a DebugRenderer component so that we can draw debug geometry
+    scene_.CreateComponent("Octree");
+    scene_.CreateComponent("DebugRenderer");
+    scene_.CreateComponent("PhysicsWorld");
+
+    // Create scene node & StaticModel component for showing a static plane
+    Node@ planeNode = scene_.CreateChild("Plane");
+    planeNode.scale = Vector3(100.0f, 1.0f, 100.0f);
+    StaticModel@ planeObject = planeNode.CreateComponent("StaticModel");
+    planeObject.model = cache.GetResource("Model", "Models/Plane.mdl");
+    planeObject.material = cache.GetResource("Material", "Materials/StoneTiled.xml");
+
+    // Create a Zone component for ambient lighting & fog control
+    Node@ zoneNode = scene_.CreateChild("Zone");
+    Zone@ zone = zoneNode.CreateComponent("Zone");
+    zone.boundingBox = BoundingBox(-1000.0f, 1000.0f);
+    zone.ambientColor = Color(0.15f, 0.15f, 0.15f);
+    zone.fogColor = Color(0.5f, 0.5f, 0.7f);
+    zone.fogStart = 100.0f;
+    zone.fogEnd = 300.0f;
+
+    // Create a directional light to the world. Enable cascaded shadows on it
+    Node@ lightNode = scene_.CreateChild("DirectionalLight");
+    lightNode.direction = Vector3(0.6f, -1.0f, 0.8f);
+    Light@ light = lightNode.CreateComponent("Light");
+    light.lightType = LIGHT_DIRECTIONAL;
+    light.castShadows = true;
+    light.shadowBias = BiasParameters(0.00025f, 0.5f);
+    // Set cascade splits at 10, 50 and 200 world units, fade shadows out at 80% of maximum shadow distance
+    light.shadowCascade = CascadeParameters(10.0f, 50.0f, 200.0f, 0.0f, 0.8f);
+
+    // Create randomly sized boxes. If boxes are big enough, make them occluders. Occluders will be software rasterized before
+    // rendering to a low-resolution depth-only buffer to test the objects in the view frustum for visibility
+    const uint NUM_BOXES = 15;
+    Random(3.0f);
+    for (uint i = 0; i < NUM_BOXES; ++i)
+    {
+        Node@ boxNode = scene_.CreateChild("Box");
+        float size = 1.0f + Random(5.0f);
+        boxNode.position = Vector3(Random(80.0f) - 40.0f, size * 0.5f, Random(80.0f) - 40.0f);
+        boxNode.SetScale(size);
+        StaticModel@ boxObject = boxNode.CreateComponent("StaticModel");
+        boxObject.model = cache.GetResource("Model", "Models/Box.mdl");
+        boxObject.material = cache.GetResource("Material", "Materials/Stone.xml");
+        boxObject.castShadows = true;
+        //if (size >= 3.0f)
+        //    boxObject.occluder = true;
+    }
+
+    // Create a DynamicNavigationMesh component to the scene root
+    DynamicNavigationMesh@ navMesh = scene_.CreateComponent("DynamicNavigationMesh");
+    navMesh.tileSize = 64;
+    // Create a Navigable component to the scene root. This tags all of the geometry in the scene as being part of the
+    // navigation mesh. By default this is recursive, but the recursion could be turned off from Navigable
+    scene_.CreateComponent("Navigable");
+    // Add padding to the navigation mesh in Y-direction so that we can add objects on top of the tallest boxes
+    // in the scene and still update the mesh correctly
+    navMesh.padding = Vector3(0.0f, 10.0f, 0.0f);
+    // Now build the navigation geometry. This will take some time. Note that the navigation mesh will prefer to use
+    // physics geometry from the scene nodes, as it often is simpler, but if it can not find any (like in this example)
+    // it will use renderable geometry instead
+    navMesh.Build();
+    
+    // Create a detour crowd manager component
+    DetourCrowdManager@ crowdManager = scene_.CreateComponent("DetourCrowdManager");
+    //crowdManager.navMesh = navMesh;
+    //crowdManager.CreateCrowd();
+    
+    // Create Jack node that will follow the path
+    Node@ jackNode = SpawnJack(Vector3(-5.0f, 0, -20.0f));
+    
+    // Because the navigation mesh is a cache the mushrooms can be created after building
+    // Create some mushrooms
+    const uint NUM_MUSHROOMS = 70;
+    for (uint i = 0; i < NUM_MUSHROOMS; ++i)
+        CreateMushroom(Vector3(Random(90.0f) - 45.0f, 0.0f, Random(90.0f) - 45.0f));
+    //navMesh.ForceUpdate();
+    
+    // Create the camera. Limit far clip distance to match the fog
+    cameraNode = scene_.CreateChild("Camera");
+    Camera@ camera = cameraNode.CreateComponent("Camera");
+    camera.farClip = 300.0f;
+
+    // Set an initial position for the camera scene node above the plane
+    cameraNode.position = Vector3(0.0f, 5.0f, 0.0f);
+}
+
+void CreateUI()
+{
+    // Create a Cursor UI element because we want to be able to hide and show it at will. When hidden, the mouse cursor will
+    // control the camera, and when visible, it will point the raycast target
+    XMLFile@ style = cache.GetResource("XMLFile", "UI/DefaultStyle.xml");
+    Cursor@ cursor = Cursor();
+    cursor.SetStyleAuto(style);
+    ui.cursor = cursor;
+    // Set starting position of the cursor at the rendering window center
+    cursor.SetPosition(graphics.width / 2, graphics.height / 2);
+
+    // Construct new Text object, set string to display and font to use
+    Text@ instructionText = ui.root.CreateChild("Text");
+    instructionText.text =
+        "Use WASD keys to move, RMB to rotate view\n"
+        "LMB to set destination, SHIFT+LMB to Spawn a Jack\n"
+        "MMB to add or remove obstacles\n"
+        "F5 To Save The Scene, F7 to Reload the Scene\n"
+        "Space to toggle debug geometry";
+    instructionText.SetFont(cache.GetResource("Font", "Fonts/Anonymous Pro.ttf"), 15);
+    // The text has multiple rows. Center them in relation to each other
+    instructionText.textAlignment = HA_CENTER;
+
+    // Position the text relative to the screen center
+    instructionText.horizontalAlignment = HA_CENTER;
+    instructionText.verticalAlignment = VA_CENTER;
+    instructionText.SetPosition(0, ui.root.height / 4);
+}
+
+void SetupViewport()
+{
+    // Set up a viewport to the Renderer subsystem so that the 3D scene can be seen
+    Viewport@ viewport = Viewport(scene_, cameraNode.GetComponent("Camera"));
+    renderer.viewports[0] = viewport;
+}
+
+void SubscribeToEvents()
+{
+    // Subscribe HandleUpdate() function for processing update events
+    SubscribeToEvent("Update", "HandleUpdate");
+
+    // Subscribe HandlePostRenderUpdate() function for processing the post-render update event, during which we request
+    // debug geometry
+    SubscribeToEvent("PostRenderUpdate", "HandlePostRenderUpdate");
+}
+
+void MoveCamera(float timeStep)
+{
+    // Right mouse button controls mouse cursor visibility: hide when pressed
+    ui.cursor.visible = !input.mouseButtonDown[MOUSEB_RIGHT];
+
+    // Do not move if the UI has a focused element (the console)
+    if (ui.focusElement !is null)
+        return;
+
+    // Movement speed as world units per second
+    const float MOVE_SPEED = 20.0f;
+    // Mouse sensitivity as degrees per pixel
+    const float MOUSE_SENSITIVITY = 0.1f;
+
+    // Use this frame's mouse motion to adjust camera node yaw and pitch. Clamp the pitch between -90 and 90 degrees
+    // Only move the camera when the cursor is hidden
+    if (!ui.cursor.visible)
+    {
+        IntVector2 mouseMove = input.mouseMove;
+        yaw += MOUSE_SENSITIVITY * mouseMove.x;
+        pitch += MOUSE_SENSITIVITY * mouseMove.y;
+        pitch = Clamp(pitch, -90.0f, 90.0f);
+
+        // Construct new orientation for the camera scene node from yaw and pitch. Roll is fixed to zero
+        cameraNode.rotation = Quaternion(pitch, yaw, 0.0f);
+    }
+
+    // Read WASD keys and move the camera scene node to the corresponding direction if they are pressed
+    if (input.keyDown['W'])
+        cameraNode.Translate(Vector3(0.0f, 0.0f, 1.0f) * MOVE_SPEED * timeStep);
+    if (input.keyDown['S'])
+        cameraNode.Translate(Vector3(0.0f, 0.0f, -1.0f) * MOVE_SPEED * timeStep);
+    if (input.keyDown['A'])
+        cameraNode.Translate(Vector3(-1.0f, 0.0f, 0.0f) * MOVE_SPEED * timeStep);
+    if (input.keyDown['D'])
+        cameraNode.Translate(Vector3(1.0f, 0.0f, 0.0f) * MOVE_SPEED * timeStep);
+
+    // Set destination or spawn a jack with left mouse button
+    if (input.mouseButtonPress[MOUSEB_LEFT])
+        SetPathPoint();
+    // Add or remove objects with middle mouse button, then rebuild navigation mesh partially
+    if (input.mouseButtonPress[MOUSEB_MIDDLE])
+        AddOrRemoveObject();
+
+    // Toggle debug geometry with space
+    if (input.keyPress[KEY_SPACE])
+        drawDebug = !drawDebug;
+}
+
+void SetPathPoint()
+{
+    Vector3 hitPos;
+    Drawable@ hitDrawable;
+    DynamicNavigationMesh@ navMesh = scene_.GetComponent("DynamicNavigationMesh");
+
+    if (Raycast(250.0f, hitPos, hitDrawable))
+    {
+        Vector3 pathPos = navMesh.FindNearestPoint(hitPos, Vector3(1.0f, 1.0f, 1.0f));
+
+        if (input.qualifierDown[QUAL_SHIFT])
+        {
+            // Spawn a jack
+            SpawnJack(Vector3(pathPos.x, 0, pathPos.z));
+        }
+        else
+        {
+            // Calculate path from Jack's current position to the end point
+            endPos = pathPos;
+            for (uint i = 0; i < jackNodes.length; ++i)
+            {
+                CrowdAgent@ agent = jackNodes[i].GetComponent("CrowdAgent");
+                agent.enabled = true;
+                if (i == 0)
+                {
+                    // The first agent will always move to the exact target
+                    agent.SetMoveTarget(endPos);
+                }
+                else
+                {
+                    // Keep the random point on the navigation mesh
+                    Vector3 targetPos = navMesh.FindNearestPoint(endPos + Vector3(Random(7.0f), 0.0f, Random(7.0f)), Vector3(1.0f, 1.0f, 1.0f));
+                    agent.SetMoveTarget(targetPos);
+                }
+            }
+            //currentPath = navMesh.FindPath(jackNode.position, endPos);
+        }
+    }
+}
+
+void AddOrRemoveObject()
+{
+    // Raycast and check if we hit a mushroom node. If yes, remove it, if no, create a new one
+    Vector3 hitPos;
+    Drawable@ hitDrawable;
+
+    if (Raycast(250.0f, hitPos, hitDrawable))
+    {
+        // The part of the navigation mesh we must update, which is the world bounding box of the associated
+        // drawable component
+        BoundingBox updateBox;
+
+        Node@ hitNode = hitDrawable.node;
+        if (hitNode.name == "Mushroom")
+        {
+            updateBox = hitDrawable.worldBoundingBox;
+            hitNode.Remove();
+            mushroomNodes.Erase(mushroomNodes.FindByRef(hitNode));
+        }
+        else
+        {
+            Node@ newNode = CreateMushroom(hitPos);
+            StaticModel@ newObject = newNode.GetComponent("StaticModel");
+            updateBox = newObject.worldBoundingBox;
+        }
+    }
+}
+
+Node@ CreateMushroom(const Vector3& pos)
+{
+    Node@ mushroomNode = scene_.CreateChild("Mushroom");
+    mushroomNode.position = pos;
+    mushroomNode.rotation = Quaternion(0.0f, Random(360.0f), 0.0f);
+    mushroomNode.SetScale(2.0f + Random(0.5f));
+    StaticModel@ mushroomObject = mushroomNode.CreateComponent("StaticModel");
+    mushroomObject.model = cache.GetResource("Model", "Models/Mushroom.mdl");
+    mushroomObject.material = cache.GetResource("Material", "Materials/Mushroom.xml");
+    mushroomObject.castShadows = true;
+    Obstacle@ obstacleObject = mushroomNode.CreateComponent("Obstacle");
+    obstacleObject.radius = 2.5f;
+    mushroomNodes.Push(mushroomNode);
+    
+    return mushroomNode;
+}
+
+Node@ SpawnJack(const Vector3& pos)
+{
+    Node@ jackNode = scene_.CreateChild("Jack");
+    jackNodes.Push(jackNode);
+    jackNode.position = pos;
+    AnimatedModel@ modelObject = jackNode.CreateComponent("AnimatedModel");
+    modelObject.model = cache.GetResource("Model", "Models/Jack.mdl");
+    modelObject.material = cache.GetResource("Material", "Materials/Jack.xml");
+    modelObject.castShadows = true;
+    CrowdAgent@ navAgent = jackNode.CreateComponent("CrowdAgent");
+    navAgent.enabled = false;
+    return jackNode;
+}
+
+bool Raycast(float maxDistance, Vector3& hitPos, Drawable@& hitDrawable)
+{
+    hitDrawable = null;
+
+    IntVector2 pos = ui.cursorPosition;
+    // Check the cursor is visible and there is no UI element in front of the cursor
+    if (!ui.cursor.visible || ui.GetElementAt(pos, true) !is null)
+        return false;
+
+    Camera@ camera = cameraNode.GetComponent("Camera");
+    Ray cameraRay = camera.GetScreenRay(float(pos.x) / graphics.width, float(pos.y) / graphics.height);
+    // Pick only geometry objects, not eg. zones or lights, only get the first (closest) hit
+    // Note the convenience accessor to scene's Octree component
+    RayQueryResult result = scene_.octree.RaycastSingle(cameraRay, RAY_TRIANGLE, maxDistance, DRAWABLE_GEOMETRY);
+    if (result.drawable !is null)
+    {
+        hitPos = result.position;
+        hitDrawable = result.drawable;
+        return true;
+    }
+
+    return false;
+}
+
+void HandleUpdate(StringHash eventType, VariantMap& eventData)
+{
+    // Take the frame time step, which is stored as a float
+    float timeStep = eventData["TimeStep"].GetFloat();
+
+    // Move the camera, scale movement with time step
+    MoveCamera(timeStep);
+    
+    // Make the CrowdAgents face the direction of their velocity
+    for (uint i = 0; i < jackNodes.length; ++i)
+    {
+        CrowdAgent@ agent = jackNodes[i].GetComponent("CrowdAgent");
+        jackNodes[i].worldDirection = agent.actualVelocity;
+    }
+    
+    if (input.keyPress[KEY_F5])
+    {
+        File saveFile(fileSystem.programDir + "Data/Scenes/CrowdDemo.xml", FILE_WRITE);
+        scene_.SaveXML(saveFile);
+    }
+    if (input.keyPress[KEY_F7])
+    {
+        File loadFile(fileSystem.programDir + "Data/Scenes/CrowdDemo.xml", FILE_READ);
+        scene_.LoadXML(loadFile);
+        // After loading we have to reacquire the character scene node, as it has been recreated
+        // Simply find by name as there's only one of them
+        jackNodes.Clear();
+        Array<Node@> jacks = scene_.GetChildrenWithComponent("CrowdAgent", true);
+        for (uint i = 0; i < jacks.length; ++i)
+        {
+            jackNodes.Push(jacks[i]);
+        }
+        mushroomNodes.Clear();
+        Array<Node@> mushrooms = scene_.GetChildrenWithComponent("Obstacle", true);
+        for (uint i = 0; i < mushrooms.length; ++i)
+        {
+            mushroomNodes.Push(mushrooms[i]);
+        }
+    }
+}
+
+void HandlePostRenderUpdate(StringHash eventType, VariantMap& eventData)
+{
+    // If draw debug mode is enabled, draw navigation mesh debug geometry
+    if (drawDebug)
+    {
+        DynamicNavigationMesh@ navMesh = scene_.GetComponent("DynamicNavigationMesh");
+        navMesh.DrawDebugGeometry(true);
+
+        for (uint i = 0; i < jackNodes.length; ++i)
+        {
+            CrowdAgent@ agent = jackNodes[i].GetComponent("CrowdAgent");
+            agent.DrawDebugGeometry(true);
+        }
+        
+        for (uint i = 0; i < mushroomNodes.length; ++i)
+        {
+            Obstacle@ obstacle = mushroomNodes[i].GetComponent("Obstacle");
+            obstacle.DrawDebugGeometry(true);
+        }
+    }
+}
+
+// Create XML patch instructions for screen joystick layout specific to this sample app
+String patchInstructions =
+        "<patch>" +
+        "    <add sel=\"/element\">" +
+        "        <element type=\"Button\">" +
+        "            <attribute name=\"Name\" value=\"Button3\" />" +
+        "            <attribute name=\"Position\" value=\"-120 -120\" />" +
+        "            <attribute name=\"Size\" value=\"96 96\" />" +
+        "            <attribute name=\"Horiz Alignment\" value=\"Right\" />" +
+        "            <attribute name=\"Vert Alignment\" value=\"Bottom\" />" +
+        "            <attribute name=\"Texture\" value=\"Texture2D;Textures/TouchInput.png\" />" +
+        "            <attribute name=\"Image Rect\" value=\"96 0 192 96\" />" +
+        "            <attribute name=\"Hover Image Offset\" value=\"0 0\" />" +
+        "            <attribute name=\"Pressed Image Offset\" value=\"0 0\" />" +
+        "            <element type=\"Text\">" +
+        "                <attribute name=\"Name\" value=\"Label\" />" +
+        "                <attribute name=\"Horiz Alignment\" value=\"Center\" />" +
+        "                <attribute name=\"Vert Alignment\" value=\"Center\" />" +
+        "                <attribute name=\"Color\" value=\"0 0 0 1\" />" +
+        "                <attribute name=\"Text\" value=\"Spawn A jack\" />" +
+        "            </element>" +
+        "            <element type=\"Text\">" +
+        "                <attribute name=\"Name\" value=\"KeyBinding\" />" +
+        "                <attribute name=\"Text\" value=\"LSHIFT\" />" +
+        "            </element>" +
+        "            <element type=\"Text\">" +
+        "                <attribute name=\"Name\" value=\"MouseButtonBinding\" />" +
+        "                <attribute name=\"Text\" value=\"LEFT\" />" +
+        "            </element>" +
+        "        </element>" +
+        "        <element type=\"Button\">" +
+        "            <attribute name=\"Name\" value=\"Button4\" />" +
+        "            <attribute name=\"Position\" value=\"-120 -12\" />" +
+        "            <attribute name=\"Size\" value=\"96 96\" />" +
+        "            <attribute name=\"Horiz Alignment\" value=\"Right\" />" +
+        "            <attribute name=\"Vert Alignment\" value=\"Bottom\" />" +
+        "            <attribute name=\"Texture\" value=\"Texture2D;Textures/TouchInput.png\" />" +
+        "            <attribute name=\"Image Rect\" value=\"96 0 192 96\" />" +
+        "            <attribute name=\"Hover Image Offset\" value=\"0 0\" />" +
+        "            <attribute name=\"Pressed Image Offset\" value=\"0 0\" />" +
+        "            <element type=\"Text\">" +
+        "                <attribute name=\"Name\" value=\"Label\" />" +
+        "                <attribute name=\"Horiz Alignment\" value=\"Center\" />" +
+        "                <attribute name=\"Vert Alignment\" value=\"Center\" />" +
+        "                <attribute name=\"Color\" value=\"0 0 0 1\" />" +
+        "                <attribute name=\"Text\" value=\"Obstacles\" />" +
+        "            </element>" +
+        "            <element type=\"Text\">" +
+        "                <attribute name=\"Name\" value=\"MouseButtonBinding\" />" +
+        "                <attribute name=\"Text\" value=\"MIDDLE\" />" +
+        "            </element>" +
+        "        </element>" +
+        "    </add>" +
+        "    <remove sel=\"/element/element[./attribute[@name='Name' and @value='Button0']]/attribute[@name='Is Visible']\" />" +
+        "    <replace sel=\"/element/element[./attribute[@name='Name' and @value='Button0']]/element[./attribute[@name='Name' and @value='Label']]/attribute[@name='Text']/@value\">Set</replace>" +
+        "    <add sel=\"/element/element[./attribute[@name='Name' and @value='Button0']]\">" +
+        "        <element type=\"Text\">" +
+        "            <attribute name=\"Name\" value=\"MouseButtonBinding\" />" +
+        "            <attribute name=\"Text\" value=\"LEFT\" />" +
+        "        </element>" +
+        "    </add>" +
+        "    <remove sel=\"/element/element[./attribute[@name='Name' and @value='Button1']]/attribute[@name='Is Visible']\" />" +
+        "    <replace sel=\"/element/element[./attribute[@name='Name' and @value='Button1']]/element[./attribute[@name='Name' and @value='Label']]/attribute[@name='Text']/@value\">Debug</replace>" +
+        "    <add sel=\"/element/element[./attribute[@name='Name' and @value='Button1']]\">" +
+        "        <element type=\"Text\">" +
+        "            <attribute name=\"Name\" value=\"KeyBinding\" />" +
+        "            <attribute name=\"Text\" value=\"SPACE\" />" +
+        "        </element>" +
+        "    </add>" +
+        "</patch>";

+ 6 - 2
bin/Data/Scripts/Editor/EditorScene.as

@@ -998,8 +998,12 @@ bool SceneRebuildNavigation()
     Array<Component@>@ navMeshes = editorScene.GetComponents("NavigationMesh", true);
     if (navMeshes.empty)
     {
-        MessageBox("No NavigationMesh components in the scene, nothing to rebuild.");
-        return false;
+        @navMeshes = editorScene.GetComponents("DynamicNavigationMesh", true);
+        if (navMeshes.empty)
+        {
+            MessageBox("No NavigationMesh components in the scene, nothing to rebuild.");
+            return false;
+        }
     }
 
     bool success = true;

BIN
bin/Data/Textures/Editor/EditorIcons.png


+ 24 - 0
bin/Data/UI/EditorIcons.xml

@@ -151,10 +151,34 @@
         <attribute name="Texture" value="Texture2D;Textures/Editor/EditorIcons.png" />
         <attribute name="Image Rect" value="64 16 78 30" />
     </element>
+    <element type="DynamicNavigationMesh">
+        <attribute name="Texture" value="Texture2D;Textures/Editor/EditorIcons.png" />
+        <attribute name="Image Rect" value="64 16 78 30" />
+    </element>
     <element type="Navigable">
         <attribute name="Texture" value="Texture2D;Textures/Editor/EditorIcons.png" />
         <attribute name="Image Rect" value="64 16 78 30" />
     </element>
+    <element type="NavArea">
+        <attribute name="Texture" value="Texture2D;Textures/Editor/EditorIcons.png" />
+        <attribute name="Image Rect" value="240 0 254 14" />
+    </element>
+    <element type="DetourCrowdManager">
+        <attribute name="Texture" value="Texture2D;Textures/Editor/EditorIcons.png" />
+        <attribute name="Image Rect" value="128 16 142 30" />
+    </element>
+    <element type="CrowdAgent">
+        <attribute name="Texture" value="Texture2D;Textures/Editor/EditorIcons.png" />
+        <attribute name="Image Rect" value="144 16 158 30" />
+    </element>
+    <element type="Obstacle">
+        <attribute name="Texture" value="Texture2D;Textures/Editor/EditorIcons.png" />
+        <attribute name="Image Rect" value="160 16 174 30" />
+    </element>
+    <element type="NavigationMesh">
+        <attribute name="Texture" value="Texture2D;Textures/Editor/EditorIcons.png" />
+        <attribute name="Image Rect" value="64 16 78 30" />
+    </element>
     <element type="Text3D">
         <attribute name="Texture" value="Texture2D;Textures/Editor/EditorIcons.png" />
         <attribute name="Image Rect" value="80 16 94 30" />