Browse Source

Merge pull request #107 from AtomicGameEngine/JME-WORK-UPDATES

Updates for navigation meshes, bullet physics, texture getting for render targets, fix incorrect viewport size for fullscreen filters
JoshEngebretson 10 years ago
parent
commit
bba0172f1b
30 changed files with 591 additions and 235 deletions
  1. 10 3
      Source/Atomic/Atomic2D/AnimationSet2D.cpp
  2. 4 3
      Source/Atomic/Graphics/Direct3D11/D3D11Graphics.cpp
  3. 8 0
      Source/Atomic/Graphics/Direct3D11/D3D11Texture.cpp
  4. 2 0
      Source/Atomic/Graphics/Direct3D11/D3D11Texture.h
  5. 4 3
      Source/Atomic/Graphics/Direct3D9/D3D9Graphics.cpp
  6. 9 1
      Source/Atomic/Graphics/Direct3D9/D3D9Texture.cpp
  7. 2 0
      Source/Atomic/Graphics/Direct3D9/D3D9Texture.h
  8. 41 6
      Source/Atomic/Graphics/Direct3D9/D3D9Texture2D.cpp
  9. 40 5
      Source/Atomic/Graphics/Direct3D9/D3D9TextureCube.cpp
  10. 6 5
      Source/Atomic/Graphics/OpenGL/OGLGraphics.cpp
  11. 8 0
      Source/Atomic/Graphics/OpenGL/OGLTexture.cpp
  12. 2 0
      Source/Atomic/Graphics/OpenGL/OGLTexture.h
  13. 8 0
      Source/Atomic/Graphics/RenderPath.cpp
  14. 7 4
      Source/Atomic/Graphics/RenderPath.h
  15. 9 6
      Source/Atomic/Graphics/View.cpp
  16. 113 123
      Source/Atomic/Navigation/CrowdAgent.cpp
  17. 19 12
      Source/Atomic/Navigation/CrowdAgent.h
  18. 66 16
      Source/Atomic/Navigation/DetourCrowdManager.cpp
  19. 14 6
      Source/Atomic/Navigation/DetourCrowdManager.h
  20. 84 16
      Source/Atomic/Navigation/DynamicNavigationMesh.cpp
  21. 14 0
      Source/Atomic/Navigation/DynamicNavigationMesh.h
  22. 1 0
      Source/Atomic/Navigation/NavigationEvents.h
  23. 41 4
      Source/Atomic/Navigation/NavigationMesh.cpp
  24. 18 3
      Source/Atomic/Navigation/NavigationMesh.h
  25. 5 0
      Source/Atomic/Navigation/Obstacle.cpp
  26. 9 7
      Source/Atomic/Navigation/OffMeshConnection.cpp
  27. 6 6
      Source/Atomic/Navigation/OffMeshConnection.h
  28. 18 2
      Source/Atomic/Physics/CollisionShape.cpp
  29. 3 0
      Source/ThirdParty/DetourTileCache/include/DetourTileCache.h
  30. 20 4
      Source/Tools/AssetImporter/AssetImporter.cpp

+ 10 - 3
Source/Atomic/Atomic2D/AnimationSet2D.cpp

@@ -1,5 +1,5 @@
 //
-// Copyright (c) 2008-2014 the Urho3D project.
+// 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
@@ -331,6 +331,8 @@ bool AnimationSet2D::LoadSpriterAnimation(const XMLElement& animationElem)
     if (animationElem.HasAttribute("looping"))
         looped = animationElem.GetBool("looping");
 
+    float highestKeyTime = 0.0f;
+
     // Load timelines
     Vector<SpriterTimeline2D> timelines;
     for (XMLElement timelineElem = animationElem.GetChild("timeline"); timelineElem; timelineElem = timelineElem.GetNext("timeline"))
@@ -346,6 +348,7 @@ bool AnimationSet2D::LoadSpriterAnimation(const XMLElement& animationElem)
         {
             SpriterTimelineKey2D key;
             key.time_ = keyElem.GetFloat("time") * 0.001f;
+            highestKeyTime = Max(highestKeyTime, key.time_);
             if (keyElem.HasAttribute("spin"))
                 key.spin_ = keyElem.GetInt("spin");
 
@@ -436,6 +439,10 @@ bool AnimationSet2D::LoadSpriterAnimation(const XMLElement& animationElem)
 
     // Create animation
     SharedPtr<Animation2D> animation(new Animation2D(this));
+    // Crop animation length if longer than the last keyframe, prevents sprites vanishing in clamp mode, or occasional flashes
+    // when looped
+    if (length > highestKeyTime)
+        length = highestKeyTime;
     animation->SetName(name);
     animation->SetLength(length);
     animation->SetLooped(looped);
@@ -462,7 +469,7 @@ bool AnimationSet2D::LoadSpriterAnimation(const XMLElement& animationElem)
 
             keyFrame.time_ = timelineKey.time_;
 
-            // Set diabled
+            // Set disabled
             keyFrame.enabled_ = false;
             keyFrame.parent_ = timeline.parent_;
             keyFrame.transform_ = Transform2D(timelineKey.position_, timelineKey.angle_, timelineKey.scale_);
@@ -472,7 +479,7 @@ bool AnimationSet2D::LoadSpriterAnimation(const XMLElement& animationElem)
             {
                 keyFrame.sprite_ = timelineKey.sprite_;
                 keyFrame.alpha_ = timelineKey.alpha_;
-                keyFrame.useHotSpot_ = timelineKey.useHotSpot_;                
+                keyFrame.useHotSpot_ = timelineKey.useHotSpot_;
                 if (timelineKey.useHotSpot_)
                     keyFrame.hotSpot_ = timelineKey.hotSpot_;
             }

+ 4 - 3
Source/Atomic/Graphics/Direct3D11/D3D11Graphics.cpp

@@ -58,9 +58,10 @@
 #pragma warning(disable:4355)
 #endif
 
-// On Intel / NVIDIA setups prefer the NVIDIA GPU
+// Prefer the high-performance GPU on switchable GPU systems
 extern "C" {
-    __declspec(dllexport) DWORD NvOptimusEnablement = 0x00000001;
+    __declspec(dllexport) DWORD NvOptimusEnablement = 1;
+    __declspec(dllexport) int AmdPowerXpressRequestHighPerformance = 1;
 }
 
 namespace Atomic
@@ -2194,7 +2195,7 @@ bool Graphics::OpenWindow(int width, int height, bool resizable, bool borderless
 
     if (!impl_->window_)
     {
-        LOGERROR("Could not create window");
+        LOGERRORF("Could not create window, root cause: '%s'", SDL_GetError());
         return false;
     }
 

+ 8 - 0
Source/Atomic/Graphics/Direct3D11/D3D11Texture.cpp

@@ -248,6 +248,14 @@ unsigned Texture::GetRowDataSize(int width) const
     }
 }
 
+unsigned Texture::GetComponents() const
+{
+    if (!width_ || IsCompressed())
+        return 0;
+    else
+        return GetRowDataSize(width_) / width_;
+}
+
 void Texture::SetParameters(XMLFile* file)
 {
     if (!file)

+ 2 - 0
Source/Atomic/Graphics/Direct3D11/D3D11Texture.h

@@ -102,6 +102,8 @@ public:
     unsigned GetDataSize(int width, int height, int depth) const;
     /// Return data size in bytes for a pixel or block row.
     unsigned GetRowDataSize(int width) const;
+    /// Return number of image components required to receive pixel data from GetData(), or 0 for compressed images.
+    unsigned GetComponents() const;
     /// Return whether the parameters are dirty.
     bool GetParametersDirty() const { return parametersDirty_ || !sampler_; }
 

+ 4 - 3
Source/Atomic/Graphics/Direct3D9/D3D9Graphics.cpp

@@ -56,9 +56,10 @@
 #pragma warning(disable:4355)
 #endif
 
-// On Intel / NVIDIA setups prefer the NVIDIA GPU
+// Prefer the high-performance GPU on switchable GPU systems
 extern "C" {
-    __declspec(dllexport) DWORD NvOptimusEnablement = 0x00000001;
+    __declspec(dllexport) DWORD NvOptimusEnablement = 1;
+    __declspec(dllexport) int AmdPowerXpressRequestHighPerformance = 1;
 }
 
 // Fix missing define in MinGW headers
@@ -2389,7 +2390,7 @@ bool Graphics::OpenWindow(int width, int height, bool resizable, bool borderless
 
     if (!impl_->window_)
     {
-        LOGERROR("Could not create window");
+        LOGERRORF("Could not create window, root cause: '%s'", SDL_GetError());
         return false;
     }
 

+ 9 - 1
Source/Atomic/Graphics/Direct3D9/D3D9Texture.cpp

@@ -1,5 +1,5 @@
 //
-// Copyright (c) 2008-2014 the Urho3D project.
+// 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
@@ -234,6 +234,14 @@ unsigned Texture::GetRowDataSize(int width) const
     }
 }
 
+unsigned Texture::GetComponents() const
+{
+    if (!width_ || IsCompressed())
+        return 0;
+    else
+        return GetRowDataSize(width_) / width_;
+}
+
 void Texture::SetParameters(XMLFile* file)
 {
     if (!file)

+ 2 - 0
Source/Atomic/Graphics/Direct3D9/D3D9Texture.h

@@ -101,6 +101,8 @@ public:
     unsigned GetDataSize(int width, int height, int depth) const;
     /// Return data size in bytes for a pixel or block row.
     unsigned GetRowDataSize(int width) const;
+    /// Return number of image components required to receive pixel data from GetData(), or 0 for compressed images.
+    unsigned GetComponents() const;
 
     /// Set additional parameters from an XML file.
     void SetParameters(XMLFile* xml);

+ 41 - 6
Source/Atomic/Graphics/Direct3D9/D3D9Texture2D.cpp

@@ -1,5 +1,5 @@
 //
-// Copyright (c) 2008-2014 the Urho3D project.
+// 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
@@ -464,12 +464,40 @@ bool Texture2D::GetData(unsigned level, void* dest) const
     d3dRect.right = levelWidth;
     d3dRect.bottom = levelHeight;
     
-    if (FAILED(((IDirect3DTexture9*)object_)->LockRect(level, &d3dLockedRect, &d3dRect, D3DLOCK_READONLY)))
+    IDirect3DSurface9* offscreenSurface = 0;
+    // Need to use a offscreen surface & GetRenderTargetData() for rendertargets
+    if (renderSurface_)
     {
-        LOGERROR("Could not lock texture");
-        return false;
+        if (level != 0)
+        {
+            LOGERROR("Can only get mip level 0 data from a rendertarget");
+            return false;
+        }
+
+        IDirect3DDevice9* device = graphics_->GetImpl()->GetDevice();
+        device->CreateOffscreenPlainSurface(width_, height_, (D3DFORMAT)format_, D3DPOOL_SYSTEMMEM, &offscreenSurface, 0);
+        if (!offscreenSurface)
+        {
+            LOGERROR("Could not create surface for getting rendertarget data");
+            return false;
+        }
+        device->GetRenderTargetData((IDirect3DSurface9*)renderSurface_->GetSurface(), offscreenSurface);
+        if (FAILED(offscreenSurface->LockRect(&d3dLockedRect, &d3dRect, D3DLOCK_READONLY)))
+        {
+            LOGERROR("Could not lock surface for getting rendertarget data");
+            offscreenSurface->Release();
+            return false;
+        }
     }
-    
+    else
+    {
+        if (FAILED(((IDirect3DTexture9*)object_)->LockRect(level, &d3dLockedRect, &d3dRect, D3DLOCK_READONLY)))
+        {
+            LOGERROR("Could not lock texture");
+            return false;
+        }
+    }
+
     int height = levelHeight;
     if (IsCompressed())
         height = (height + 3) >> 2;
@@ -517,7 +545,14 @@ bool Texture2D::GetData(unsigned level, void* dest) const
         break;
     }
     
-    ((IDirect3DTexture9*)object_)->UnlockRect(level);
+    if (offscreenSurface)
+    {
+        offscreenSurface->UnlockRect();
+        offscreenSurface->Release();
+    }
+    else
+        ((IDirect3DTexture9*)object_)->UnlockRect(level);
+
     return true;
 }
 

+ 40 - 5
Source/Atomic/Graphics/Direct3D9/D3D9TextureCube.cpp

@@ -1,5 +1,5 @@
 //
-// Copyright (c) 2008-2014 the Urho3D project.
+// 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
@@ -669,10 +669,38 @@ bool TextureCube::GetData(CubeMapFace face, unsigned level, void* dest) const
     d3dRect.right = levelWidth;
     d3dRect.bottom = levelHeight;
     
-    if (FAILED(((IDirect3DCubeTexture9*)object_)->LockRect((D3DCUBEMAP_FACES)face, level, &d3dLockedRect, &d3dRect, D3DLOCK_READONLY)))
+    IDirect3DSurface9* offscreenSurface = 0;
+    // Need to use a offscreen surface & GetRenderTargetData() for rendertargets
+    if (renderSurfaces_[face])
     {
-        LOGERROR("Could not lock texture");
-        return false;
+        if (level != 0)
+        {
+            LOGERROR("Can only get mip level 0 data from a rendertarget");
+            return false;
+        }
+
+        IDirect3DDevice9* device = graphics_->GetImpl()->GetDevice();
+        device->CreateOffscreenPlainSurface(width_, height_, (D3DFORMAT)format_, D3DPOOL_SYSTEMMEM, &offscreenSurface, 0);
+        if (!offscreenSurface)
+        {
+            LOGERROR("Could not create surface for getting rendertarget data");
+            return false;
+        }
+        device->GetRenderTargetData((IDirect3DSurface9*)renderSurfaces_[face]->GetSurface(), offscreenSurface);
+        if (FAILED(offscreenSurface->LockRect(&d3dLockedRect, &d3dRect, D3DLOCK_READONLY)))
+        {
+            LOGERROR("Could not lock surface for getting rendertarget data");
+            offscreenSurface->Release();
+            return false;
+        }
+    }
+    else
+    {
+        if (FAILED(((IDirect3DCubeTexture9*)object_)->LockRect((D3DCUBEMAP_FACES)face, level, &d3dLockedRect, &d3dRect, D3DLOCK_READONLY)))
+        {
+            LOGERROR("Could not lock texture");
+            return false;
+        }
     }
     
     int height = levelHeight;
@@ -722,7 +750,14 @@ bool TextureCube::GetData(CubeMapFace face, unsigned level, void* dest) const
         break;
     }
     
-    ((IDirect3DCubeTexture9*)object_)->UnlockRect((D3DCUBEMAP_FACES)face, level);
+    if (offscreenSurface)
+    {
+        offscreenSurface->UnlockRect();
+        offscreenSurface->Release();
+    }
+    else
+        ((IDirect3DCubeTexture9*)object_)->UnlockRect((D3DCUBEMAP_FACES)face, level);
+    
     return true;
 }
 

+ 6 - 5
Source/Atomic/Graphics/OpenGL/OGLGraphics.cpp

@@ -58,10 +58,11 @@
 #endif
 
 #ifdef WIN32
-// On Intel / NVIDIA setups prefer the NVIDIA GPU
+// Prefer the high-performance GPU on switchable GPU systems
 #include <windows.h>
 extern "C" {
-    __declspec(dllexport) DWORD NvOptimusEnablement = 0x00000001;
+    __declspec(dllexport) DWORD NvOptimusEnablement = 1;
+    __declspec(dllexport) int AmdPowerXpressRequestHighPerformance = 1;
 }
 #endif
 
@@ -497,7 +498,7 @@ bool Graphics::SetMode(int width, int height, bool fullscreen, bool borderless,
                 }
                 else
                 {
-                    LOGERROR("Could not open window");
+                    LOGERRORF("Could not create window, root cause: '%s'", SDL_GetError());
                     return false;
                 }
             }
@@ -2438,7 +2439,7 @@ void Graphics::Restore()
         
         if (!impl_->context_)
         {
-            LOGERROR("Could not create OpenGL context");
+            LOGERRORF("Could not create OpenGL context, root cause '%s'", SDL_GetError());
             return;
         }
 
@@ -2450,7 +2451,7 @@ void Graphics::Restore()
         GLenum err = glewInit();
         if (GLEW_OK != err)
         {
-            LOGERROR("Could not initialize OpenGL extensions");
+            LOGERRORF("Could not initialize OpenGL extensions, root cause: '%s'", glewGetErrorString(err));
             return;
         }
 

+ 8 - 0
Source/Atomic/Graphics/OpenGL/OGLTexture.cpp

@@ -367,6 +367,14 @@ unsigned Texture::GetRowDataSize(int width) const
     }
 }
 
+unsigned Texture::GetComponents() const
+{
+    if (!width_ || IsCompressed())
+        return 0;
+    else
+        return GetRowDataSize(width_) / width_;
+}
+
 unsigned Texture::GetExternalFormat(unsigned format)
 {
     #ifndef GL_ES_VERSION_2_0

+ 2 - 0
Source/Atomic/Graphics/OpenGL/OGLTexture.h

@@ -109,6 +109,8 @@ public:
     unsigned GetDataSize(int width, int height, int depth) const;
     /// Return data size in bytes for a pixel or block row.
     unsigned GetRowDataSize(int width) const;
+    /// Return number of image components required to receive pixel data from GetData(), or 0 for compressed images.
+    unsigned GetComponents() const;
     /// Return the non-internal texture format corresponding to an OpenGL internal format.
     static unsigned GetExternalFormat(unsigned format);
     /// Return the data type corresponding to an OpenGL internal format.

+ 8 - 0
Source/Atomic/Graphics/RenderPath.cpp

@@ -52,6 +52,8 @@ static const char* sortModeNames[] =
     0
 };
 
+extern const char* blendModeNames[];
+
 TextureUnit ParseTextureUnitName(String name);
 
 void RenderTargetInfo::Load(const XMLElement& element)
@@ -157,6 +159,12 @@ void RenderPathCommand::Load(const XMLElement& element)
         
         if (type_ == CMD_QUAD)
         {
+            if (element.HasAttribute("blend"))
+            {
+                String blend = element.GetAttributeLower("blend");
+                blendMode_ = ((BlendMode)GetStringListIndex(blend.CString(), blendModeNames, BLEND_REPLACE));
+            }
+
             XMLElement parameterElem = element.GetChild("parameter");
             while (parameterElem)
             {

+ 7 - 4
Source/Atomic/Graphics/RenderPath.h

@@ -107,6 +107,7 @@ struct RenderPathCommand
     /// Construct.
     RenderPathCommand() :
         clearFlags_(0),
+        blendMode_(BLEND_REPLACE),
         enabled_(true),
         useFogColor_(false),
         markToStencil_(false),
@@ -175,14 +176,16 @@ struct RenderPathCommand
     Vector<Pair<String, CubeMapFace> > outputs_;
     /// Depth-stencil output name.
     String depthStencilName_;
-    /// Clear flags.
+    /// Clear flags. Affects clear command only.
     unsigned clearFlags_;
-    /// Clear color.
+    /// Clear color. Affects clear command only.
     Color clearColor_;
-    /// Clear depth.
+    /// Clear depth. Affects clear command only.
     float clearDepth_;
-    /// Clear stencil value.
+    /// Clear stencil value. Affects clear command only.
     unsigned clearStencil_;
+    /// Blend mode. Affects quad command only.
+    BlendMode blendMode_;
     /// Enabled flag.
     bool enabled_;
     /// Use fog color for clearing.

+ 9 - 6
Source/Atomic/Graphics/View.cpp

@@ -46,12 +46,11 @@
 #include "../Graphics/TextureCube.h"
 #include "../Graphics/VertexBuffer.h"
 #include "../Graphics/View.h"
+#include "../UI/UI.h"
 #include "../Core/WorkQueue.h"
 
 #include "../DebugNew.h"
 
-#include "../UI/UI.h"
-
 namespace Atomic
 {
 
@@ -1622,11 +1621,15 @@ void View::SetRenderTargets(RenderPathCommand& command)
     unsigned index = 0;
     bool useColorWrite = true;
     bool useCustomDepth = false;
+    bool useViewportOutput = false;
 
     while (index < command.outputs_.Size())
     {
         if (!command.outputs_[index].first_.Compare("viewport", false))
+        {
             graphics_->SetRenderTarget(index, currentRenderTarget_);
+            useViewportOutput = true;
+        }
         else
         {
             Texture* texture = FindNamedTexture(command.outputs_[index].first_, true, false);
@@ -1671,10 +1674,10 @@ void View::SetRenderTargets(RenderPathCommand& command)
         }
     }
 
-    // When rendering to the final destination rendertarget, use the actual viewport. Otherwise texture rendertargets will be
-    // viewport-sized, so they should use their full size as the viewport
+    // When rendering to the final destination rendertarget, use the actual viewport. Otherwise texture rendertargets should use 
+    // their full size as the viewport
     IntVector2 rtSizeNow = graphics_->GetRenderTargetDimensions();
-    IntRect viewport = (currentRenderTarget_ == renderTarget_) ? viewRect_ : IntRect(0, 0, rtSizeNow.x_,
+    IntRect viewport = (useViewportOutput && currentRenderTarget_ == renderTarget_) ? viewRect_ : IntRect(0, 0, rtSizeNow.x_,
         rtSizeNow.y_);
     
     if (!useCustomDepth)
@@ -1773,7 +1776,7 @@ void View::RenderQuad(RenderPathCommand& command)
         graphics_->SetShaderParameter(offsetsName, Vector2(pixelUVOffset.x_ / width, pixelUVOffset.y_ / height));
     }
     
-    graphics_->SetBlendMode(BLEND_REPLACE);
+    graphics_->SetBlendMode(command.blendMode_);
     graphics_->SetDepthTest(CMP_ALWAYS);
     graphics_->SetDepthWrite(false);
     graphics_->SetFillMode(FILL_SOLID);

+ 113 - 123
Source/Atomic/Navigation/CrowdAgent.cpp

@@ -45,8 +45,8 @@ namespace Atomic
 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 float DEFAULT_AGENT_MAX_SPEED = 0.f;
+static const float DEFAULT_AGENT_MAX_ACCEL = 0.f;
 static const NavigationQuality DEFAULT_AGENT_AVOIDANCE_QUALITY = NAVIGATIONQUALITY_HIGH;
 static const NavigationPushiness DEFAULT_AGENT_NAVIGATION_PUSHINESS = PUSHINESS_MEDIUM;
 
@@ -86,6 +86,7 @@ CrowdAgent::CrowdAgent(Context* context) :
 
 CrowdAgent::~CrowdAgent()
 {
+    RemoveAgentFromCrowd();
 }
 
 void CrowdAgent::RegisterObject(Context* context)
@@ -96,6 +97,7 @@ void CrowdAgent::RegisterObject(Context* context)
     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("Target Position", GetTargetPosition, SetMoveTarget, Vector3, Vector3::ZERO, 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);
@@ -117,6 +119,8 @@ void CrowdAgent::OnNodeSet(Node* node)
 
         node->AddListener(this);
     }
+    else
+        RemoveAgentFromCrowd();
 }
 
 void CrowdAgent::OnSetEnabled()
@@ -147,17 +151,22 @@ void CrowdAgent::DrawDebugGeometry(DebugRenderer* debug, bool depthTest)
         const Vector3 pos = GetPosition();
         const Vector3 vel = GetActualVelocity();
         const Vector3 desiredVel = GetDesiredVelocity();
-        const Vector3 agentHeightVec(0, height_ * 0.5f, 0);
+        const Vector3 agentHeightVec(0, height_, 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);
+        debug->AddLine(pos + 0.5f * agentHeightVec, pos + vel + 0.5f * agentHeightVec, Color::GREEN, depthTest);
+        debug->AddLine(pos + 0.25f * agentHeightVec, pos + desiredVel + 0.25f * agentHeightVec, Color::RED, depthTest);
+        debug->AddCylinder(pos, radius_, height_, HasArrived() ? Color::GREEN : Color::WHITE, depthTest);
     }
 }
 
+const dtCrowdAgent* CrowdAgent::GetDetourCrowdAgent() const
+{
+    return crowdManager_ && inCrowd_ ? crowdManager_->GetCrowdAgent(agentCrowdId_) : 0;
+}
+
 void CrowdAgent::AddAgentToCrowd()
 {
-    if (!crowdManager_ || !crowdManager_->crowd_)
+    if (!crowdManager_ || !crowdManager_->crowd_ || !node_)
         return;
 
     PROFILE(AddAgentToCrowd);
@@ -172,8 +181,6 @@ void CrowdAgent::AddAgentToCrowd()
             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_);
         previousAgentState_ = GetAgentState();
@@ -195,6 +202,9 @@ void CrowdAgent::AddAgentToCrowd()
             previousAgentState_ = GetAgentState();
             previousTargetState_ = GetTargetState();
         }
+
+        // Save the initial position to prevent CrowdAgentReposition event being triggered unnecessarily
+        previousPosition_ = GetPosition();
     }
 }
 
@@ -221,42 +231,45 @@ void CrowdAgent::SetNavigationFilterType(unsigned filterType)
     }
 }
 
-bool CrowdAgent::SetMoveTarget(const Vector3& position)
+void CrowdAgent::SetMoveTarget(const Vector3& position)
 {
-    if (crowdManager_ && !inCrowd_)
-        AddAgentToCrowd();
-    if (crowdManager_ && inCrowd_)
-    {
+    if (crowdManager_) {
+        if (!inCrowd_)
+            AddAgentToCrowd();
         targetPosition_ = position;
         if (crowdManager_->SetAgentTarget(this, position, targetRef_))
-        {
             MarkNetworkUpdate();
-            return true;
-        }
     }
-    return false;
 }
 
-bool CrowdAgent::SetMoveVelocity(const Vector3& velocity)
+void CrowdAgent::ResetMoveTarget()
 {
-    if (crowdManager_ && inCrowd_)
+    const dtCrowdAgent* agent = GetDetourCrowdAgent();
+    if (agent && agent->active)
     {
-        const dtCrowdAgent* agent = crowdManager_->GetCrowdAgent(agentCrowdId_);
-        if (agent && agent->active)
-        {
-            crowdManager_->GetCrowd()->requestMoveVelocity(agentCrowdId_, velocity.Data());
-            MarkNetworkUpdate();
-        }
+        targetPosition_ = Vector3::ZERO;
+        crowdManager_->GetCrowd()->resetMoveTarget(agentCrowdId_);
+        MarkNetworkUpdate();
+    }
+}
+
+void CrowdAgent::SetMoveVelocity(const Vector3& velocity)
+{
+    const dtCrowdAgent* agent = GetDetourCrowdAgent();
+    if (agent && agent->active)
+    {
+        crowdManager_->GetCrowd()->requestMoveVelocity(agentCrowdId_, velocity.Data());
+        MarkNetworkUpdate();
     }
-    return false;
 }
 
 void CrowdAgent::SetMaxSpeed(float speed)
 {
-    maxSpeed_ = speed;
-    if(crowdManager_ && inCrowd_)
+    const dtCrowdAgent* agent = GetDetourCrowdAgent();
+    if (agent)
     {
-        dtCrowdAgentParams params = crowdManager_->GetCrowdAgent(agentCrowdId_)->params;
+        maxSpeed_ = speed;
+        dtCrowdAgentParams params = agent->params;
         params.maxSpeed = speed;
         crowdManager_->GetCrowd()->updateAgentParameters(agentCrowdId_, &params);
         MarkNetworkUpdate();
@@ -265,10 +278,11 @@ void CrowdAgent::SetMaxSpeed(float speed)
 
 void CrowdAgent::SetMaxAccel(float accel)
 {
-    maxAccel_ = accel;
-    if(crowdManager_ && inCrowd_)
+    const dtCrowdAgent* agent = GetDetourCrowdAgent();
+    if (agent)
     {
-        dtCrowdAgentParams params = crowdManager_->GetCrowdAgent(agentCrowdId_)->params;
+        maxAccel_ = accel;
+        dtCrowdAgentParams params = agent->params;
         params.maxAcceleration = accel;
         crowdManager_->GetCrowd()->updateAgentParameters(agentCrowdId_, &params);
         MarkNetworkUpdate();
@@ -277,10 +291,11 @@ void CrowdAgent::SetMaxAccel(float accel)
 
 void CrowdAgent::SetRadius(float radius)
 {
-    radius_ = radius;
-    if (crowdManager_ && inCrowd_)
+    const dtCrowdAgent* agent = GetDetourCrowdAgent();
+    if (agent)
     {
-        dtCrowdAgentParams params = crowdManager_->GetCrowdAgent(agentCrowdId_)->params;
+        radius_ = radius;
+        dtCrowdAgentParams params = agent->params;
         params.radius = radius;
         crowdManager_->GetCrowd()->updateAgentParameters(agentCrowdId_, &params);
         MarkNetworkUpdate();
@@ -289,10 +304,11 @@ void CrowdAgent::SetRadius(float radius)
 
 void CrowdAgent::SetHeight(float height)
 {
-    height_ = height;
-    if (crowdManager_ && inCrowd_)
+    const dtCrowdAgent* agent = GetDetourCrowdAgent();
+    if (agent)
     {
-        dtCrowdAgentParams params = crowdManager_->GetCrowdAgent(agentCrowdId_)->params;
+        height_ = height;
+        dtCrowdAgentParams params = agent->params;
         params.height = height;
         crowdManager_->GetCrowd()->updateAgentParameters(agentCrowdId_, &params);
         MarkNetworkUpdate();
@@ -301,9 +317,10 @@ void CrowdAgent::SetHeight(float height)
 
 void CrowdAgent::SetNavigationQuality(NavigationQuality val)
 {
-    navQuality_ = val;
-    if(crowdManager_ && inCrowd_)
+    const dtCrowdAgent* agent = GetDetourCrowdAgent();
+    if (agent)
     {
+        navQuality_ = val;
         crowdManager_->UpdateAgentNavigationQuality(this, navQuality_);
         MarkNetworkUpdate();
     }
@@ -311,9 +328,10 @@ void CrowdAgent::SetNavigationQuality(NavigationQuality val)
 
 void CrowdAgent::SetNavigationPushiness(NavigationPushiness val)
 {
-    navPushiness_ = val;
-    if(crowdManager_ && inCrowd_)
+    const dtCrowdAgent* agent = GetDetourCrowdAgent();
+    if (agent)
     {
+        navPushiness_ = val;
         crowdManager_->UpdateAgentPushiness(this, navPushiness_);
         MarkNetworkUpdate();
     }
@@ -321,75 +339,41 @@ void CrowdAgent::SetNavigationPushiness(NavigationPushiness val)
 
 Vector3 CrowdAgent::GetPosition() const
 {
-    if (crowdManager_ && inCrowd_)
-    {
-        const dtCrowdAgent* agent = crowdManager_->GetCrowdAgent(agentCrowdId_);
-        if (agent && agent->active)
-            return Vector3(agent->npos);
-    }
-    return node_->GetPosition();
+    const dtCrowdAgent* agent = GetDetourCrowdAgent();
+    return agent && agent->active ? Vector3(agent->npos) : 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;
+    const dtCrowdAgent* agent = GetDetourCrowdAgent();
+    return agent && agent->active ? Vector3(agent->dvel) : 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 dtCrowdAgent* agent = GetDetourCrowdAgent();
+    return agent && agent->active ? Vector3(agent->vel) : Vector3::ZERO;
 }
 
 Atomic::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;
+    const dtCrowdAgent* agent = GetDetourCrowdAgent();
+    return agent && agent->active ? (CrowdAgentState)agent->state : CROWD_AGENT_INVALID;
 }
 
 Atomic::CrowdTargetState CrowdAgent::GetTargetState() const
 {
-    if (crowdManager_ && inCrowd_)
-    {
-        const dtCrowdAgent* agent = crowdManager_->GetCrowdAgent(agentCrowdId_);
-        if (!agent || !agent->active)
-            return CROWD_AGENT_TARGET_NONE;
+    const dtCrowdAgent* agent = GetDetourCrowdAgent();
+    return agent && agent->active ? (CrowdTargetState)agent->targetState : 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?
-                if (agent->cornerFlags[agent->ncorners - 1] & DT_STRAIGHTPATH_END)
-                {
-                    // 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;
+bool CrowdAgent::HasArrived() const
+{
+    // Is the agent at or near the end of its path?
+    const dtCrowdAgent* agent = GetDetourCrowdAgent();
+    return agent && agent->active && (!agent->ncorners ||
+        (agent->cornerFlags[agent->ncorners - 1] & DT_STRAIGHTPATH_END &&
+            Equals(dtVdist2D(agent->npos, &agent->cornerVerts[(agent->ncorners - 1) * 3]), 0.f)));
 }
 
 void CrowdAgent::SetUpdateNodePosition(bool unodepos)
@@ -398,23 +382,29 @@ void CrowdAgent::SetUpdateNodePosition(bool unodepos)
     MarkNetworkUpdate();
 }
 
-void CrowdAgent::OnCrowdAgentReposition(const Vector3& newPos, const Vector3& newDirection)
+void CrowdAgent::OnCrowdAgentReposition(const Vector3& newPos, const Vector3& newVel)
 {
     if (node_)
     {
         // Notify parent node of the reposition
-        VariantMap& map = GetContext()->GetEventDataMap();
-        map[CrowdAgentReposition::P_NODE] = GetNode();
-        map[CrowdAgentReposition::P_CROWD_AGENT] = this;
-        map[CrowdAgentReposition::P_POSITION] = newPos;
-        map[CrowdAgentReposition::P_VELOCITY] = GetActualVelocity();
-        SendEvent(E_CROWD_AGENT_REPOSITION, map);
-        
-        if (updateNodePosition_)
+        if (newPos != previousPosition_)
         {
-            ignoreTransformChanges_ = true;
-            node_->SetPosition(newPos);
-            ignoreTransformChanges_ = false;
+            previousPosition_ = newPos;
+
+            VariantMap& map = GetContext()->GetEventDataMap();
+            map[CrowdAgentReposition::P_NODE] = GetNode();
+            map[CrowdAgentReposition::P_CROWD_AGENT] = this;
+            map[CrowdAgentReposition::P_POSITION] = newPos;
+            map[CrowdAgentReposition::P_VELOCITY] = newVel;
+            map[CrowdAgentReposition::P_ARRIVED] = HasArrived();
+            SendEvent(E_CROWD_AGENT_REPOSITION, map);
+
+            if (updateNodePosition_)
+            {
+                ignoreTransformChanges_ = true;
+                node_->SetPosition(newPos);
+                ignoreTransformChanges_ = false;
+            }
         }
 
         // Send a notification event if we've reached the destination
@@ -428,7 +418,7 @@ void CrowdAgent::OnCrowdAgentReposition(const Vector3& newPos, const Vector3& ne
             map[CrowdAgentStateChanged::P_CROWD_TARGET_STATE] = newTargetState;
             map[CrowdAgentStateChanged::P_CROWD_AGENT_STATE] = newAgentState;
             map[CrowdAgentStateChanged::P_POSITION] = newPos;
-            map[CrowdAgentStateChanged::P_VELOCITY] = GetActualVelocity();
+            map[CrowdAgentStateChanged::P_VELOCITY] = newVel;
             SendEvent(E_CROWD_AGENT_STATE_CHANGED, map);
 
             // Send a failure event if either state is a failed status
@@ -440,7 +430,7 @@ void CrowdAgent::OnCrowdAgentReposition(const Vector3& newPos, const Vector3& ne
                 map[CrowdAgentFailure::P_CROWD_TARGET_STATE] = newTargetState;
                 map[CrowdAgentFailure::P_CROWD_AGENT_STATE] = newAgentState;
                 map[CrowdAgentFailure::P_POSITION] = newPos;
-                map[CrowdAgentFailure::P_VELOCITY] = GetActualVelocity();
+                map[CrowdAgentFailure::P_VELOCITY] = newVel;
                 SendEvent(E_CROWD_AGENT_FAILURE, map);
             }
 
@@ -453,11 +443,10 @@ void CrowdAgent::OnCrowdAgentReposition(const Vector3& newPos, const Vector3& ne
 
 PODVector<unsigned char> CrowdAgent::GetAgentDataAttr() const
 {
-    if (!inCrowd_ || !crowdManager_ || !IsEnabled())
+    const dtCrowdAgent* agent = GetDetourCrowdAgent();
+    if (!agent)
         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));
@@ -467,12 +456,14 @@ PODVector<unsigned char> CrowdAgent::GetAgentDataAttr() const
 
 void CrowdAgent::SetAgentDataAttr(const PODVector<unsigned char>& value)
 {
-    if (value.Empty() || !inCrowd_ || !crowdManager_ || !IsEnabled())
+    if (value.Empty())
+        return;
+
+    dtCrowdAgent* agent = const_cast<dtCrowdAgent*>(GetDetourCrowdAgent());
+    if (!agent)
         return;
 
     MemoryBuffer buffer(value);
-    dtCrowd* crowd = crowdManager_->GetCrowd();
-    dtCrowdAgent* agent = crowd->getEditableAgent(agentCrowdId_);
 
     // Path corridor is tricky
     char corridorData[sizeof(dtPathCorridor)];
@@ -491,19 +482,18 @@ void CrowdAgent::SetAgentDataAttr(const PODVector<unsigned char>& value)
 
 void CrowdAgent::OnMarkedDirty(Node* node)
 {
-    if (inCrowd_ && crowdManager_ && !ignoreTransformChanges_ && IsEnabledEffective())
+    if (!ignoreTransformChanges_ && IsEnabledEffective())
     {
-        dtCrowdAgent* agt = crowdManager_->GetCrowd()->getEditableAgent(agentCrowdId_);
-        if (agt)
+        dtCrowdAgent* agent = const_cast<dtCrowdAgent*>(GetDetourCrowdAgent());
+        if (agent)
         {
-            memcpy(agt->npos, node->GetWorldPosition().Data(), sizeof(float) * 3);
+            memcpy(agent->npos, node->GetWorldPosition().Data(), sizeof(float) * 3);
 
             // If the node has been externally altered, provide the opportunity for DetourCrowd to reevaluate the crowd agent
-            if (agt->state == CROWD_AGENT_INVALID)
-                agt->state = CROWD_AGENT_READY;
+            if (agent->state == CROWD_AGENT_INVALID)
+                agent->state = CROWD_AGENT_READY;
         }
     }
 }
 
-
 }

+ 19 - 12
Source/Atomic/Navigation/CrowdAgent.h

@@ -34,17 +34,16 @@ enum CrowdTargetState
     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
+    CROWD_AGENT_TARGET_WAITINGFORPATH,
+    CROWD_AGENT_TARGET_VELOCITY
 };
 
 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.
+    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.
@@ -71,9 +70,11 @@ public:
     /// Set the navigation filter type the agent will use.
     void SetNavigationFilterType(unsigned filterTypeID);
     /// Submit a new move request for this agent.
-    bool SetMoveTarget(const Vector3& position);
+    void SetMoveTarget(const Vector3& position);
+    /// Reset any request for the specified agent.
+    void ResetMoveTarget();
     /// Submit a new move velocity request for this agent.
-    bool SetMoveVelocity(const Vector3& velocity);
+    void SetMoveVelocity(const Vector3& velocity);
     /// Update the node position. When set to false, the node position should be updated by other means (e.g. using Physics) in response to the E_CROWD_AGENT_REPOSITION event.
     void SetUpdateNodePosition(bool unodepos);
     /// Set the agent's max acceleration.
@@ -110,7 +111,7 @@ public:
     /// Get the agent's max velocity.
     float GetMaxSpeed() const { return maxSpeed_; }
     /// Get the agent's max acceleration.
-    float GetMaxAccel()	const { return maxAccel_; }
+    float GetMaxAccel() const { return maxAccel_; }
     /// Get the agent's radius.
     float GetRadius() const { return radius_; }
     /// Get the agent's height.
@@ -119,6 +120,8 @@ public:
     NavigationQuality GetNavigationQuality() const {return navQuality_; }
     /// Get the agent's navigation pushiness.
     NavigationPushiness GetNavigationPushiness() const {return navPushiness_; }
+    /// Return true when the agent has arrived at its target.
+    bool HasArrived() const;
 
     /// Get serialized data of internal state.
     PODVector<unsigned char> GetAgentDataAttr() const;
@@ -127,11 +130,13 @@ public:
 
 protected:
     /// Update the nodes position if updateNodePosition is set. Is called in DetourCrowdManager::Update().
-    virtual void OnCrowdAgentReposition(const Vector3& newPos, const Vector3& newDirection);
+    virtual void OnCrowdAgentReposition(const Vector3& newPos, const Vector3& newVel);
     /// Handle node being assigned.
     virtual void OnNodeSet(Node* node);
     /// \todo Handle node transform being dirtied.
     virtual void OnMarkedDirty(Node* node);
+    /// Get internal Detour crowd agent.
+    const dtCrowdAgent* GetDetourCrowdAgent() const;
 private:
     /// Create or re-add.
     void AddAgentToCrowd();
@@ -144,9 +149,9 @@ private:
     /// DetourCrowd reference to this agent.
     int agentCrowdId_;
     /// Reference to poly closest to requested target position.
-    unsigned int targetRef_;         
+    unsigned int targetRef_;
     /// Actual target position, closest to that requested.
-    Vector3 targetPosition_;   
+    Vector3 targetPosition_;
     /// Flag indicating the node's position should be updated by Detour crowd manager.
     bool updateNodePosition_;
     /// Agent's max acceleration.
@@ -163,6 +168,8 @@ private:
     NavigationQuality navQuality_;
     /// Agent's Navigation Pushiness.
     NavigationPushiness navPushiness_;
+    /// Agent's previous position used to check for position changes.
+    Vector3 previousPosition_;
     /// Agent's previous target state used to check for state changes.
     CrowdTargetState previousTargetState_;
     /// Agent's previous agent state used to check for state changes.

+ 66 - 16
Source/Atomic/Navigation/DetourCrowdManager.cpp

@@ -46,7 +46,7 @@
 
 namespace Atomic
 {
-    
+
 extern const char* NAVIGATION_CATEGORY;
 
 static const unsigned DEFAULT_MAX_AGENTS = 512;
@@ -72,7 +72,7 @@ DetourCrowdManager::~DetourCrowdManager()
 void DetourCrowdManager::RegisterObject(Context* context)
 {
     context->RegisterFactory<DetourCrowdManager>(NAVIGATION_CATEGORY);
-    
+
     ACCESSOR_ATTRIBUTE("Max Agents", GetMaxAgents, SetMaxAgents, unsigned, DEFAULT_MAX_AGENTS, AM_DEFAULT);
 }
 
@@ -117,9 +117,44 @@ void DetourCrowdManager::SetMaxAgents(unsigned agentCt)
     MarkNetworkUpdate();
 }
 
-NavigationMesh* DetourCrowdManager::GetNavigationMesh()
+void DetourCrowdManager::SetCrowdTarget(const Vector3& position, int startId, int endId)
+{
+    startId = Max(0, startId);
+    endId = Clamp(endId, startId, agents_.Size() - 1);
+    Vector3 moveTarget(position);
+    for (int i = startId; i <= endId; ++i)
+    {
+        // Skip agent that does not have acceleration
+        if (agents_[i]->GetMaxAccel() > 0.f)
+        {
+            agents_[i]->SetMoveTarget(moveTarget);
+            // FIXME: Should reimplement this using event callback, i.e. it should be application-specific to decide what is the desired crowd formation when they reach the target
+            if (navigationMesh_)
+                moveTarget = navigationMesh_->FindNearestPoint(position + Vector3(Random(-4.5f, 4.5f), 0.0f, Random(-4.5f, 4.5f)), Vector3(1.0f, 1.0f, 1.0f));
+        }
+    }
+}
+
+void DetourCrowdManager::ResetCrowdTarget(int startId, int endId)
+{
+    startId = Max(0, startId);
+    endId = Clamp(endId, startId, agents_.Size() - 1);
+    for (int i = startId; i <= endId; ++i)
+    {
+        if (agents_[i]->GetMaxAccel() > 0.f)
+            agents_[i]->ResetMoveTarget();
+    }
+}
+
+void DetourCrowdManager::SetCrowdVelocity(const Vector3& velocity, int startId, int endId)
 {
-    return navigationMesh_.Get();
+    startId = Max(0, startId);
+    endId = Clamp(endId, startId, agents_.Size() - 1);
+    for (int i = startId; i <= endId; ++i)
+    {
+        if (agents_[i]->GetMaxAccel() > 0.f)
+            agents_[i]->SetMoveVelocity(velocity);
+    }
 }
 
 float DetourCrowdManager::GetAreaCost(unsigned filterID, unsigned areaID) const
@@ -149,9 +184,17 @@ void DetourCrowdManager::DrawDebugGeometry(DebugRenderer* debug, bool depthTest)
             if (!ag->active)
                 continue;
 
+            // Draw CrowdAgent shape (from its radius & height)
+            CrowdAgent* crowdAgent = static_cast<CrowdAgent*>(ag->params.userData);
+            crowdAgent->DrawDebugGeometry(debug, depthTest);
+
+            // Draw move target if any
+            if (crowdAgent->GetTargetState() == CROWD_AGENT_TARGET_NONE)
+                continue;
+
             Color color(0.6f, 0.2f, 0.2f, 1.0f);
 
-            // Render line to target:
+            // Draw line to target
             Vector3 pos1(ag->npos[0], ag->npos[1], ag->npos[2]);
             Vector3 pos2;
             for (int i = 0; i < ag->ncorners; ++i)
@@ -167,12 +210,23 @@ void DetourCrowdManager::DrawDebugGeometry(DebugRenderer* debug, bool depthTest)
             pos2.z_ = ag->targetPos[2];
             debug->AddLine(pos1, pos2, color, depthTest);
 
-            // Target circle
+            // Draw target circle
             debug->AddSphere(Sphere(pos2, 0.5f), color, depthTest);
         }
     }
 }
 
+void DetourCrowdManager::DrawDebugGeometry(bool depthTest)
+{
+    Scene* scene = GetScene();
+    if (scene)
+    {
+        DebugRenderer* debug = scene->GetComponent<DebugRenderer>();
+        if (debug)
+            DrawDebugGeometry(debug, depthTest);
+    }
+}
+
 bool DetourCrowdManager::CreateCrowd()
 {
     if (!navigationMesh_ || !navigationMesh_->navMesh_)
@@ -231,6 +285,7 @@ int DetourCrowdManager::AddAgent(CrowdAgent* agent, const Vector3& pos)
     if (!crowd_ || navigationMesh_.Expired())
         return -1;
     dtCrowdAgentParams params;
+    params.userData = agent;
     if (agent->radius_ <= 0.0f)
         agent->radius_ = navigationMesh_->GetAgentRadius();
     params.radius = agent->radius_;
@@ -258,7 +313,7 @@ int DetourCrowdManager::AddAgent(CrowdAgent* agent, const Vector3& pos)
         crowd_->getFilter(agent->filterType_),
         &polyRef,
         nearestPos);
-    
+
     const int agentID = crowd_->addAgent(nearestPos, &params);
     if (agentID != -1)
         agents_.Push(agent);
@@ -403,12 +458,12 @@ void DetourCrowdManager::Update(float delta)
         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++)
@@ -416,7 +471,7 @@ void DetourCrowdManager::Update(float delta)
             dtCrowdAgent* agent = agentBuffer_[i];
             if (agent)
             {
-                CrowdAgent* crowdAgent = static_cast<CrowdAgent*>(agent->params.userData);    
+                CrowdAgent* crowdAgent = static_cast<CrowdAgent*>(agent->params.userData);
                 if (crowdAgent)
                     crowdAgent->OnCrowdAgentReposition(Vector3(agent->npos), Vector3(agent->vel));
             }
@@ -429,11 +484,6 @@ const dtCrowdAgent* DetourCrowdManager::GetCrowdAgent(int agent)
     return crowd_ ? crowd_->getAgent(agent) : 0;
 }
 
-dtCrowd* DetourCrowdManager::GetCrowd()
-{
-    return crowd_;
-}
-
 void DetourCrowdManager::HandleSceneSubsystemUpdate(StringHash eventType, VariantMap& eventData)
 {
     using namespace SceneSubsystemUpdate;
@@ -472,7 +522,7 @@ void DetourCrowdManager::OnNodeSet(Node* node)
     {
         SubscribeToEvent(node, E_SCENESUBSYSTEMUPDATE, HANDLER(DetourCrowdManager, HandleSceneSubsystemUpdate));
         SubscribeToEvent(node, E_NAVIGATION_MESH_REBUILT, HANDLER(DetourCrowdManager, HandleNavMeshFullRebuild));
-        
+
         NavigationMesh* mesh = GetScene()->GetComponent<NavigationMesh>();
         if (!mesh)
             mesh = GetScene()->GetComponent<DynamicNavigationMesh>();

+ 14 - 6
Source/Atomic/Navigation/DetourCrowdManager.h

@@ -43,7 +43,7 @@ enum NavigationQuality
 
 enum NavigationPushiness
 {
-    PUSHINESS_LOW,
+    PUSHINESS_LOW = 0,
     PUSHINESS_MEDIUM,
     PUSHINESS_HIGH
 };
@@ -54,7 +54,7 @@ class ATOMIC_API DetourCrowdManager : public Component
 {
     OBJECT(DetourCrowdManager);
     friend class CrowdAgent;
-              
+
 public:
     /// Construct.
     DetourCrowdManager(Context* context);
@@ -69,9 +69,15 @@ public:
     void SetAreaCost(unsigned filterTypeID, unsigned areaID, float weight);
     /// Set the maximum number of agents.
     void SetMaxAgents(unsigned agentCt);
+    /// Set the crowd move target. The move target is applied to all crowd agents within the id range, excluding crowd agent which does not have acceleration.
+    void SetCrowdTarget(const Vector3& position, int startId = 0, int endId = M_MAX_INT);
+    /// Reset the crowd move target to all crowd agents within the id range, excluding crowd agent which does not have acceleration.
+    void ResetCrowdTarget(int startId = 0, int endId = M_MAX_INT);
+    /// Set the crowd move velocity. The move velocity is applied to all crowd agents within the id range, excluding crowd agent which does not have acceleration.
+    void SetCrowdVelocity(const Vector3& velocity, int startId = 0, int endId = M_MAX_INT);
 
     /// Get the Navigation mesh assigned to the crowd.
-    NavigationMesh* GetNavigationMesh();
+    NavigationMesh* GetNavigationMesh() const { return navigationMesh_; }
     /// Get the cost of an area-type for the specified navigation filter type.
     float GetAreaCost(unsigned filterTypeID, unsigned areaID) const;
     /// Get the maximum number of agents.
@@ -81,8 +87,10 @@ public:
 
     /// Draw the agents' pathing debug data.
     virtual void DrawDebugGeometry(DebugRenderer* debug, bool depthTest);
+    /// Add debug geometry to the debug renderer.
+    void DrawDebugGeometry(bool depthTest);
     /// Get the currently included agents.
-    PODVector<CrowdAgent*> GetActiveAgents() const { return agents_; }
+    const PODVector<CrowdAgent*>& GetActiveAgents() const { return agents_; }
     /// Create detour crowd component for the specified navigation mesh.
     bool CreateCrowd();
 
@@ -113,7 +121,7 @@ protected:
     /// Get the detour crowd agent.
     const dtCrowdAgent* GetCrowdAgent(int agent);
     /// Get the internal detour crowd component.
-    dtCrowd* GetCrowd();
+    dtCrowd* GetCrowd() const { return crowd_; }
 
 private:
     /// Handle the scene subsystem update event.
@@ -126,7 +134,7 @@ private:
     /// NavigationMesh for which the crowd was created.
     WeakPtr<NavigationMesh> navigationMesh_;
     /// Max agents for the crowd.
-    unsigned maxAgents_;    
+    unsigned maxAgents_;
     /// Internal debug information.
     dtCrowdAgentDebugInfo* agentDebug_;
     /// Container for fetching agents from DetourCrowd during update.

+ 84 - 16
Source/Atomic/Navigation/DynamicNavigationMesh.cpp

@@ -24,15 +24,17 @@
 
 #include "../Math/BoundingBox.h"
 #include "../Core/Context.h"
+#include "../Navigation/CrowdAgent.h"
 #include "../Graphics/DebugRenderer.h"
 #include "../IO/Log.h"
 #include "../IO/MemoryBuffer.h"
+#include "../Navigation/NavArea.h"
 #include "../Navigation/NavBuildData.h"
 #include "../Navigation/NavigationEvents.h"
 #include "../Scene/Node.h"
+#include "../Navigation/Obstacle.h"
 #include "../Navigation/OffMeshConnection.h"
 #include "../Core/Profiler.h"
-#include "../Navigation/Obstacle.h"
 #include "../Scene/Scene.h"
 #include "../Scene/SceneEvents.h"
 
@@ -53,7 +55,7 @@
 
 namespace Atomic
 {
-    
+
 extern const char* NAVIGATION_CATEGORY;
 
 static const int DEFAULT_MAX_OBSTACLES = 1024;
@@ -113,7 +115,7 @@ struct MeshProcess : public dtTileCacheMeshProcess
         BoundingBox bounds;
         rcVcopy(&bounds.min_.x_, params->bmin);
         rcVcopy(&bounds.max_.x_, params->bmin);
-        
+
         // collect off-mesh connections
         PODVector<OffMeshConnection*> offMeshConnections = owner_->CollectOffMeshConnections(bounds);
 
@@ -177,7 +179,7 @@ struct LinearAllocator : public dtTileCacheAlloc
 
     void resize(const int cap)
     {
-        if (buffer) 
+        if (buffer)
             dtFree(buffer);
         buffer = (unsigned char*)dtAlloc(cap, DT_ALLOC_PERM);
         capacity = cap;
@@ -209,10 +211,11 @@ struct LinearAllocator : public dtTileCacheAlloc
 DynamicNavigationMesh::DynamicNavigationMesh(Context* context) :
     NavigationMesh(context),
     tileCache_(0),
-    maxObstacles_(1024)
+    maxObstacles_(1024),
+    drawObstacles_(false)
 {
     //64 is the largest tile-size that DetourTileCache will tolerate without silently failing
-    tileSize_ = 64; 
+    tileSize_ = 64;
     partitionType_ = NAVMESH_PARTITION_MONOTONE;
     allocator_ = new LinearAllocator(32000); //32kb to start
     compressor_ = new TileCompressor();
@@ -235,7 +238,8 @@ void DynamicNavigationMesh::RegisterObject(Context* context)
     context->RegisterFactory<DynamicNavigationMesh>(NAVIGATION_CATEGORY);
 
     COPY_BASE_ATTRIBUTES(NavigationMesh);
-    ATTRIBUTE("Max Obstacles", unsigned, maxObstacles_, DEFAULT_MAX_OBSTACLES, AM_DEFAULT);
+    ACCESSOR_ATTRIBUTE("Max Obstacles", GetMaxObstacles, SetMaxObstacles, unsigned, DEFAULT_MAX_OBSTACLES, AM_DEFAULT);
+    ACCESSOR_ATTRIBUTE("Draw Obstacles", GetDrawObstacles, SetDrawObstacles, bool, false, AM_DEFAULT);
 }
 
 bool DynamicNavigationMesh::Build()
@@ -361,7 +365,7 @@ bool DynamicNavigationMesh::Build()
             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_);
@@ -475,7 +479,7 @@ void DynamicNavigationMesh::DrawDebugGeometry(DebugRenderer* debug, bool depthTe
             // Get the layers from the tile-cache
             const dtMeshTile* tiles[TILECACHE_MAXLAYERS];
             int tileCount = navMesh->getTilesAt(x, z, tiles, TILECACHE_MAXLAYERS);
-            for (int i = 0; i < tileCount; ++i) 
+            for (int i = 0; i < tileCount; ++i)
             {
                 const dtMeshTile* tile = tiles[i];
                 if (!tile)
@@ -497,12 +501,66 @@ void DynamicNavigationMesh::DrawDebugGeometry(DebugRenderer* debug, bool depthTe
             }
         }
     }
+
+    Scene* scene = GetScene();
+    if (scene)
+    {
+        // Draw Obstacle components
+        if (drawObstacles_)
+        {
+            PODVector<Node*> obstacles;
+            scene->GetChildrenWithComponent<Obstacle>(obstacles, true);
+            for (unsigned i = 0; i < obstacles.Size(); ++i)
+            {
+                Obstacle* obstacle = obstacles[i]->GetComponent<Obstacle>();
+                if (obstacle && obstacle->IsEnabledEffective())
+                    obstacle->DrawDebugGeometry(debug, depthTest);
+            }
+        }
+
+        // Draw OffMeshConnection components
+        if (drawOffMeshConnections_)
+        {
+            PODVector<Node*> connections;
+            scene->GetChildrenWithComponent<OffMeshConnection>(connections, true);
+            for (unsigned i = 0; i < connections.Size(); ++i)
+            {
+                OffMeshConnection* connection = connections[i]->GetComponent<OffMeshConnection>();
+                if (connection && connection->IsEnabledEffective())
+                    connection->DrawDebugGeometry(debug, depthTest);
+            }
+        }
+
+        // Draw NavArea components
+        if (drawNavAreas_)
+        {
+            PODVector<Node*> areas;
+            scene->GetChildrenWithComponent<NavArea>(areas, true);
+            for (unsigned i = 0; i < areas.Size(); ++i)
+            {
+                NavArea* area = areas[i]->GetComponent<NavArea>();
+                if (area && area->IsEnabledEffective())
+                    area->DrawDebugGeometry(debug, depthTest);
+            }
+        }
+    }
+}
+
+void DynamicNavigationMesh::DrawDebugGeometry(bool depthTest)
+{
+    Scene* scene = GetScene();
+    if (scene)
+    {
+        DebugRenderer* debug = scene->GetComponent<DebugRenderer>();
+        if (debug)
+            DrawDebugGeometry(debug, depthTest);
+    }
 }
 
 void DynamicNavigationMesh::SetNavigationDataAttr(const PODVector<unsigned char>& value)
 {
     ReleaseNavigationMesh();
-        
+
     if (value.Empty())
         return;
 
@@ -684,7 +742,7 @@ int DynamicNavigationMesh::BuildTile(Vector<NavigationGeometryInfo>& geometryLis
     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_);
 
@@ -732,7 +790,7 @@ int DynamicNavigationMesh::BuildTile(Vector<NavigationGeometryInfo>& geometryLis
             return 0;
         }
     }
-    
+
     build.heightFieldLayers_ = rcAllocHeightfieldLayerSet();
     if (!build.heightFieldLayers_)
     {
@@ -838,6 +896,12 @@ void DynamicNavigationMesh::AddObstacle(Obstacle* obstacle, bool silent)
         Vector3 obsPos = obstacle->GetNode()->GetWorldPosition();
         rcVcopy(pos, &obsPos.x_);
         dtObstacleRef refHolder;
+
+        // Because dtTileCache doesn't process obstacle requests while updating tiles 
+        // it's necessary update until sufficient request space is available
+        while (tileCache_->isObstacleQueueFull())
+            tileCache_->update(1, navMesh_);
+
         if (dtStatusFailed(tileCache_->addObstacle(pos, obstacle->GetRadius(), obstacle->GetHeight(), &refHolder)))
         {
             LOGERROR("Failed to add obstacle");
@@ -845,8 +909,7 @@ void DynamicNavigationMesh::AddObstacle(Obstacle* obstacle, bool silent)
         }
         obstacle->obstacleId_ = refHolder;
         assert(refHolder > 0);
-        tileCache_->update(1, navMesh_);
-        
+
         if (!silent)
         {
             using namespace NavigationObstacleAdded;
@@ -874,15 +937,20 @@ void DynamicNavigationMesh::RemoveObstacle(Obstacle* obstacle, bool silent)
 {
     if (tileCache_ && obstacle->obstacleId_ > 0)
     {
+        // Because dtTileCache doesn't process obstacle requests while updating tiles 
+        // it's necessary update until sufficient request space is available
+        while (tileCache_->isObstacleQueueFull())
+            tileCache_->update(1, navMesh_);
+
         if (dtStatusFailed(tileCache_->removeObstacle(obstacle->obstacleId_)))
         {
             LOGERROR("Failed to remove obstacle");
             return;
         }
+        obstacle->obstacleId_ = 0;
         // 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();
@@ -890,7 +958,7 @@ void DynamicNavigationMesh::RemoveObstacle(Obstacle* obstacle, bool silent)
             eventData[P_POSITION] = obstacle->GetNode()->GetWorldPosition();
             eventData[P_RADIUS] = obstacle->GetRadius();
             eventData[P_HEIGHT] = obstacle->GetHeight();
-            SendEvent(E_NAVIGATION_OBSTACLE_ADDED, eventData);
+            SendEvent(E_NAVIGATION_OBSTACLE_REMOVED, eventData);
         }
     }
 }

+ 14 - 0
Source/Atomic/Navigation/DynamicNavigationMesh.h

@@ -59,12 +59,24 @@ public:
     virtual bool Build(const BoundingBox& boundingBox);
     /// Visualize the component as debug geometry.
     virtual void DrawDebugGeometry(DebugRenderer* debug, bool depthTest);
+    /// Add debug geometry to the debug renderer.
+    void DrawDebugGeometry(bool depthTest);
 
     /// Set navigation data attribute.
     virtual void SetNavigationDataAttr(const PODVector<unsigned char>& value);
     /// Return navigation data attribute.
     virtual PODVector<unsigned char> GetNavigationDataAttr() const;
 
+    /// Set the maximum number of obstacles allowed.
+    void SetMaxObstacles(unsigned maxObstacles) { maxObstacles_ = maxObstacles; }
+    /// Return the maximum number of obstacles allowed.
+    unsigned GetMaxObstacles() const { return maxObstacles_; }
+
+    /// Draw debug geometry for Obstacles.
+    void SetDrawObstacles(bool enable) { drawObstacles_ = enable; }
+    /// Return whether to draw Obstacles.
+    bool GetDrawObstacles() const { return drawObstacles_; }
+
 protected:
     struct TileCacheData;
 
@@ -101,6 +113,8 @@ private:
     dtTileCacheMeshProcess* meshProcessor_;
     /// Maximum number of obstacle objects allowed.
     unsigned maxObstacles_;
+    /// Debug draw Obstacles.
+    bool drawObstacles_;
 };
 
 }

+ 1 - 0
Source/Atomic/Navigation/NavigationEvents.h

@@ -50,6 +50,7 @@ EVENT(E_CROWD_AGENT_REPOSITION, CrowdAgentReposition)
     PARAM(P_CROWD_AGENT, CrowdAgent); // CrowdAgent pointer
     PARAM(P_POSITION, Position); // Vector3
     PARAM(P_VELOCITY, Velocity); // Vector3
+    PARAM(P_ARRIVED, Arrived); // bool
 }
 
 /// Crowd agent's internal state has become invalidated.

+ 41 - 4
Source/Atomic/Navigation/NavigationMesh.cpp

@@ -1,5 +1,5 @@
 //
-// Copyright (c) 2008-2014 the Urho3D project.
+// 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
@@ -123,7 +123,9 @@ NavigationMesh::NavigationMesh(Context* context) :
     numTilesX_(0),
     numTilesZ_(0),
     partitionType_(NAVMESH_PARTITION_WATERSHED),
-    keepInterResults_(false)
+    keepInterResults_(false),
+    drawOffMeshConnections_(false),
+    drawNavAreas_(false)
 {
 }
 
@@ -158,6 +160,8 @@ void NavigationMesh::RegisterObject(Context* context)
     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);
+    ACCESSOR_ATTRIBUTE("Draw OffMeshConnections", GetDrawOffMeshConnections, SetDrawOffMeshConnections, bool, false, AM_DEFAULT);
+    ACCESSOR_ATTRIBUTE("Draw NavAreas", GetDrawNavAreas, SetDrawNavAreas, bool, false, AM_DEFAULT);
 }
 
 void NavigationMesh::DrawDebugGeometry(DebugRenderer* debug, bool depthTest)
@@ -195,6 +199,36 @@ void NavigationMesh::DrawDebugGeometry(DebugRenderer* debug, bool depthTest)
             }
         }
     }
+
+    Scene* scene = GetScene();
+    if (scene)
+    {
+        // Draw OffMeshConnection components
+        if (drawOffMeshConnections_)
+        {
+            PODVector<Node*> connections;
+            scene->GetChildrenWithComponent<OffMeshConnection>(connections, true);
+            for (unsigned i = 0; i < connections.Size(); ++i)
+            {
+                OffMeshConnection* connection = connections[i]->GetComponent<OffMeshConnection>();
+                if (connection && connection->IsEnabledEffective())
+                    connection->DrawDebugGeometry(debug, depthTest);
+            }
+        }
+
+        // Draw NavArea components
+        if (drawNavAreas_)
+        {
+            PODVector<Node*> areas;
+            scene->GetChildrenWithComponent<NavArea>(areas, true);
+            for (unsigned i = 0; i < areas.Size(); ++i)
+            {
+                NavArea* area = areas[i]->GetComponent<NavArea>();
+                if (area && area->IsEnabledEffective())
+                    area->DrawDebugGeometry(debug, depthTest);
+            }
+        }
+    }
 }
 
 void NavigationMesh::SetMeshName(const String& newName)
@@ -499,7 +533,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);
@@ -803,6 +837,9 @@ void NavigationMesh::CollectGeometries(Vector<NavigationGeometryInfo>& geometryL
     // Make sure nodes are not included twice
     if (processedNodes.Contains(node))
         return;
+    // Exclude obstacles from consideration
+    if (node->HasComponent<Obstacle>())
+        return;
     processedNodes.Insert(node);
 
     Matrix3x4 inverse = node_->GetWorldTransform().Inverse();
@@ -1118,7 +1155,7 @@ 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_);
-    
+
     rcFilterWalkableLowHeightSpans(build.ctx_, cfg.walkableHeight, *build.heightField_);
     rcFilterLedgeSpans(build.ctx_, cfg.walkableHeight, cfg.walkableClimb, *build.heightField_);
 

+ 18 - 3
Source/Atomic/Navigation/NavigationMesh.h

@@ -1,5 +1,5 @@
 //
-// Copyright (c) 2008-2014 the Urho3D project.
+// 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
@@ -45,8 +45,8 @@ namespace Atomic
 
 enum NavmeshPartitionType
 {
-    NAVMESH_PARTITION_WATERSHED,
-    NAVMESH_PARTITION_MONOTONE,
+    NAVMESH_PARTITION_WATERSHED = 0,
+    NAVMESH_PARTITION_MONOTONE
 };
 
 class Geometry;
@@ -188,6 +188,16 @@ public:
     /// Return navigation data attribute.
     virtual PODVector<unsigned char> GetNavigationDataAttr() const;
 
+    /// Draw debug geometry for OffMeshConnection components.
+    void SetDrawOffMeshConnections(bool enable) { drawOffMeshConnections_ = enable; }
+    /// Return whether to draw OffMeshConnection components.
+    bool GetDrawOffMeshConnections() const { return drawOffMeshConnections_; }
+
+    /// Draw debug geometry for NavArea components.
+    void SetDrawNavAreas(bool enable) { drawNavAreas_ = enable; }
+    /// Return whether to draw NavArea components.
+    bool GetDrawNavAreas() const { return drawNavAreas_; }
+
 protected:
     /// Collect geometry from under Navigable components.
     void CollectGeometries(Vector<NavigationGeometryInfo>& geometryList);
@@ -255,6 +265,11 @@ protected:
     bool keepInterResults_;
     /// Internal build resources for creating the navmesh.
     HashMap<Pair<int, int>, NavBuildData*> builds_;
+
+    /// Debug draw OffMeshConnection components.
+    bool drawOffMeshConnections_;
+    /// Debug draw NavArea components.
+    bool drawNavAreas_;
 };
 
 /// Register Navigation library objects.

+ 5 - 0
Source/Atomic/Navigation/Obstacle.cpp

@@ -99,6 +99,11 @@ void Obstacle::OnNodeSet(Node* node)
         if (ownerMesh_)
             ownerMesh_->AddObstacle(this);
     }
+    else
+    {
+        if (obstacleId_ > 0 && ownerMesh_)
+            ownerMesh_->RemoveObstacle(this);
+    }
 }
 
 void Obstacle::DrawDebugGeometry(DebugRenderer* debug, bool depthTest)

+ 9 - 7
Source/Atomic/Navigation/OffMeshConnection.cpp

@@ -1,5 +1,5 @@
 //
-// Copyright (c) 2008-2014 the Urho3D project.
+// 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
@@ -35,14 +35,16 @@ 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;
+static const unsigned DEFAULT_AREA = 1;
 
 OffMeshConnection::OffMeshConnection(Context* context) :
     Component(context),
     endPointID_(0),
     radius_(DEFAULT_RADIUS),
     bidirectional_(true),
-    endPointDirty_(false)
+    endPointDirty_(false),
+    mask_(DEFAULT_MASK_FLAG),
+    areaId_(DEFAULT_AREA)
 {
 }
 
@@ -53,7 +55,7 @@ OffMeshConnection::~OffMeshConnection()
 void OffMeshConnection::RegisterObject(Context* context)
 {
     context->RegisterFactory<OffMeshConnection>(NAVIGATION_CATEGORY);
-    
+
     ACCESSOR_ATTRIBUTE("Is Enabled", IsEnabled, SetEnabled, bool, true, AM_DEFAULT);
     ATTRIBUTE("Endpoint NodeID", int, endPointID_, 0, AM_DEFAULT | AM_NODEID);
     ATTRIBUTE("Radius", float, radius_, DEFAULT_RADIUS, AM_DEFAULT);
@@ -65,7 +67,7 @@ void OffMeshConnection::RegisterObject(Context* context)
 void OffMeshConnection::OnSetAttribute(const AttributeInfo& attr, const Variant& src)
 {
     Serializable::OnSetAttribute(attr, src);
-    
+
     if (attr.offset_ == offsetof(OffMeshConnection, endPointID_))
         endPointDirty_ = true;
 }
@@ -84,10 +86,10 @@ void OffMeshConnection::DrawDebugGeometry(DebugRenderer* debug, bool depthTest)
 {
     if (!node_ || !endPoint_)
         return;
-    
+
     Vector3 start = node_->GetWorldPosition();
     Vector3 end = endPoint_->GetWorldPosition();
-    
+
     debug->AddSphere(Sphere(start, radius_), Color::WHITE, depthTest);
     debug->AddSphere(Sphere(end, radius_), Color::WHITE, depthTest);
     debug->AddLine(start, end, Color::WHITE, depthTest);

+ 6 - 6
Source/Atomic/Navigation/OffMeshConnection.h

@@ -1,5 +1,5 @@
 //
-// Copyright (c) 2008-2014 the Urho3D project.
+// 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
@@ -31,7 +31,7 @@ namespace Atomic
 class ATOMIC_API OffMeshConnection : public Component
 {
     OBJECT(OffMeshConnection);
-    
+
 public:
     /// Construct.
     OffMeshConnection(Context* context);
@@ -39,14 +39,14 @@ public:
     virtual ~OffMeshConnection();
     /// Register object factory.
     static void RegisterObject(Context* context);
-    
+
     /// Handle attribute write access.
     virtual void OnSetAttribute(const AttributeInfo& attr, const Variant& src);
     /// Apply attribute changes that can not be applied immediately. Called after scene load or a network update.
     virtual void ApplyAttributes();
     /// Visualize the component as debug geometry.
     virtual void DrawDebugGeometry(DebugRenderer* debug, bool depthTest);
-    
+
     /// Set endpoint node.
     void SetEndPoint(Node* node);
     /// Set radius.
@@ -57,7 +57,7 @@ public:
     void SetMask(unsigned newMask);
     /// Sets the assigned area Id for the connection
     void SetAreaID(unsigned newAreaID);
-    
+
     /// Return endpoint node.
     Node* GetEndPoint() const;
     /// Return radius.
@@ -68,7 +68,7 @@ public:
     unsigned GetMask() const { return mask_; }
     /// Return the user assigned area ID
     unsigned GetAreaID() const { return areaId_; }
-    
+
 private:
     /// Endpoint node.
     WeakPtr<Node> endPoint_;

+ 18 - 2
Source/Atomic/Physics/CollisionShape.cpp

@@ -59,6 +59,7 @@ namespace Atomic
 {
 
 static const float DEFAULT_COLLISION_MARGIN = 0.04f;
+static const unsigned QUANTIZE_MAX_TRIANGLES = 1000000;
 
 static const btVector3 WHITE(1.0f, 1.0f, 1.0f);
 static const btVector3 GREEN(0.0f, 1.0f, 0.0f);
@@ -85,6 +86,7 @@ public:
     TriangleMeshInterface(Model* model, unsigned lodLevel) : btTriangleIndexVertexArray()
     {
         unsigned numGeometries = model->GetNumGeometries();
+        unsigned totalTriangles = 0;
 
         for (unsigned i = 0; i < numGeometries; ++i)
         {
@@ -125,13 +127,20 @@ public:
             meshIndex.m_indexType = (indexSize == sizeof(unsigned short)) ? PHY_SHORT : PHY_INTEGER;
             meshIndex.m_vertexType = PHY_FLOAT;
             m_indexedMeshes.push_back(meshIndex);
+            
+            totalTriangles += meshIndex.m_numTriangles;
         }
+
+        // Bullet will not work properly with quantized AABB compression, if the triangle count is too large. Use a conservative
+        // threshold value
+        useQuantize_ = totalTriangles <= QUANTIZE_MAX_TRIANGLES;
     }
 
     TriangleMeshInterface(CustomGeometry* custom) : btTriangleIndexVertexArray()
     {
         const Vector<PODVector<CustomGeometryVertex> >& srcVertices = custom->GetVertices();
         unsigned totalVertexCount = 0;
+        unsigned totalTriangles = 0;
 
         for (unsigned i = 0; i < srcVertices.Size(); ++i)
             totalVertexCount += srcVertices[i].Size();
@@ -167,9 +176,16 @@ public:
             meshIndex.m_indexType = PHY_INTEGER;
             meshIndex.m_vertexType = PHY_FLOAT;
             m_indexedMeshes.push_back(meshIndex);
+
+            totalTriangles += meshIndex.m_numTriangles;
         }
+
+        useQuantize_ = totalTriangles <= QUANTIZE_MAX_TRIANGLES;
     }
 
+    /// OK to use quantization flag.
+    bool useQuantize_;
+
 private:
     /// Shared vertex/index data used in the collision
     Vector<SharedArrayPtr<unsigned char> > dataArrays_;
@@ -181,7 +197,7 @@ TriangleMeshData::TriangleMeshData(Model* model, unsigned lodLevel) :
     infoMap_(0)
 {
     meshInterface_ = new TriangleMeshInterface(model, lodLevel);
-    shape_ = new btBvhTriangleMeshShape(meshInterface_, true, true);
+    shape_ = new btBvhTriangleMeshShape(meshInterface_, meshInterface_->useQuantize_, true);
 
     infoMap_ = new btTriangleInfoMap();
     btGenerateInternalEdgeInfo(shape_, infoMap_);
@@ -193,7 +209,7 @@ TriangleMeshData::TriangleMeshData(CustomGeometry* custom) :
     infoMap_(0)
 {
     meshInterface_ = new TriangleMeshInterface(custom);
-    shape_ = new btBvhTriangleMeshShape(meshInterface_, true, true);
+    shape_ = new btBvhTriangleMeshShape(meshInterface_, meshInterface_->useQuantize_, true);
 
     infoMap_ = new btTriangleInfoMap();
     btGenerateInternalEdgeInfo(shape_, infoMap_);

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

@@ -2,6 +2,7 @@
 #define DETOURTILECACHE_H
 
 #include <Detour/include/DetourStatus.h>
+// Modified by Lasse Oorni for Urho3D
 
 typedef unsigned int dtObstacleRef;
 
@@ -119,6 +120,8 @@ public:
 	
 	void getObstacleBounds(const struct dtTileCacheObstacle* ob, float* bmin, float* bmax) const;
 	
+    // Urho3D: added function to know when we have too many obstacle requests without update
+    bool isObstacleQueueFull() const { return m_nreqs >= MAX_REQUESTS; }
 
 	/// Encodes a tile id.
 	inline dtCompressedTileRef encodeTileId(unsigned int salt, unsigned int it) const

+ 20 - 4
Source/Tools/AssetImporter/AssetImporter.cpp

@@ -1072,18 +1072,34 @@ void BuildAndSaveAnimations(OutModel* model)
                     continue;
                 }
             }
-            
+
+            // To export single frame animation, check if first key frame is identical to bone transformation
+            aiVector3D bonePos, boneScale;
+            aiQuaternion boneRot;
+            boneNode->mTransformation.Decompose(boneScale, boneRot, bonePos);
+
+            bool posEqual = true;
+            bool scaleEqual = true;
+            bool rotEqual = true;
+
+            if (channel->mNumPositionKeys > 0 && !ToVector3(bonePos).Equals(ToVector3(channel->mPositionKeys[0].mValue)))
+                posEqual = false;
+            if (channel->mNumScalingKeys > 0 && !ToVector3(boneScale).Equals(ToVector3(channel->mScalingKeys[0].mValue)))
+                scaleEqual = false;
+            if (channel->mNumRotationKeys > 0 && !ToQuaternion(boneRot).Equals(ToQuaternion(channel->mRotationKeys[0].mValue)))
+                rotEqual = false;
+
             AnimationTrack track;
             track.name_ = channelName;
             track.nameHash_ = channelName;
             
             // Check which channels are used
             track.channelMask_ = 0;
-            if (channel->mNumPositionKeys > 1)
+            if (channel->mNumPositionKeys > 1 || !posEqual)
                 track.channelMask_ |= CHANNEL_POSITION;
-            if (channel->mNumRotationKeys > 1)
+            if (channel->mNumRotationKeys > 1 || !rotEqual)
                 track.channelMask_ |= CHANNEL_ROTATION;
-            if (channel->mNumScalingKeys > 1)
+            if (channel->mNumScalingKeys > 1 || !scaleEqual)
                 track.channelMask_ |= CHANNEL_SCALE;
             // Check for redundant identity scale in all keyframes and remove in that case
             if (track.channelMask_ & CHANNEL_SCALE)