Josh Engebretson 10 years ago
parent
commit
5b7a672d26
100 changed files with 3516 additions and 1577 deletions
  1. 2 2
      Resources/CoreData/RenderPaths/ForwardDepth.xml
  2. 11 9
      Resources/CoreData/Shaders/HLSL/Basic.hlsl
  3. 3 3
      Resources/CoreData/Shaders/HLSL/Depth.hlsl
  4. 9 6
      Resources/CoreData/Shaders/HLSL/Samplers.hlsl
  5. 3 0
      Resources/CoreData/Techniques/BasicVColUnlitAlpha.xml
  6. 20 4
      Source/Atomic/Atomic3D/AnimatedModel.cpp
  7. 2 0
      Source/Atomic/Atomic3D/AnimatedModel.h
  8. 29 3
      Source/Atomic/Atomic3D/AnimationController.cpp
  9. 8 1
      Source/Atomic/Atomic3D/AnimationController.h
  10. 4 0
      Source/Atomic/Atomic3D/DecalSet.cpp
  11. 84 8
      Source/Atomic/Atomic3D/Terrain.cpp
  12. 19 1
      Source/Atomic/Atomic3D/Terrain.h
  13. 11 22
      Source/Atomic/Atomic3D/TerrainPatch.cpp
  14. 5 12
      Source/Atomic/Atomic3D/TerrainPatch.h
  15. 4 0
      Source/Atomic/Audio/Audio.cpp
  16. 12 8
      Source/Atomic/Container/Str.cpp
  17. 3 3
      Source/Atomic/Container/Str.h
  18. 19 0
      Source/Atomic/Container/Vector.h
  19. 1 1
      Source/Atomic/Core/Attribute.h
  20. 3 5
      Source/Atomic/Core/ProcessUtils.cpp
  21. 93 0
      Source/Atomic/Core/Spline.cpp
  22. 31 20
      Source/Atomic/Core/Spline.h
  23. 73 18
      Source/Atomic/Core/Variant.cpp
  24. 120 23
      Source/Atomic/Core/Variant.h
  25. 5 1
      Source/Atomic/Core/WorkQueue.cpp
  26. 4 0
      Source/Atomic/Core/WorkQueue.h
  27. 99 0
      Source/Atomic/Database/Database.cpp
  28. 74 0
      Source/Atomic/Database/Database.h
  29. 44 0
      Source/Atomic/Database/DatabaseEvents.h
  30. 31 0
      Source/Atomic/Database/DbConnection.h
  31. 31 0
      Source/Atomic/Database/DbResult.h
  32. 158 0
      Source/Atomic/Database/ODBC/ODBCConnection.cpp
  33. 68 0
      Source/Atomic/Database/ODBC/ODBCConnection.h
  34. 73 0
      Source/Atomic/Database/ODBC/ODBCResult.h
  35. 162 0
      Source/Atomic/Database/SQLite/SQLiteConnection.cpp
  36. 65 0
      Source/Atomic/Database/SQLite/SQLiteConnection.h
  37. 68 0
      Source/Atomic/Database/SQLite/SQLiteResult.h
  38. 0 3
      Source/Atomic/Engine/Application.cpp
  39. 9 0
      Source/Atomic/Engine/Engine.cpp
  40. 17 5
      Source/Atomic/Graphics/Batch.cpp
  41. 18 12
      Source/Atomic/Graphics/Batch.h
  42. 4 0
      Source/Atomic/Graphics/Drawable.cpp
  43. 15 0
      Source/Atomic/Graphics/Material.cpp
  44. 9 0
      Source/Atomic/Graphics/Material.h
  45. 2 1
      Source/Atomic/Graphics/OcclusionBuffer.cpp
  46. 3 2
      Source/Atomic/Graphics/Octree.cpp
  47. 5 5
      Source/Atomic/Graphics/OpenGL/OGLGraphics.cpp
  48. 1 1
      Source/Atomic/Graphics/OpenGL/OGLGraphics.h
  49. 68 54
      Source/Atomic/Graphics/OpenGL/OGLTextureCube.cpp
  50. 4 0
      Source/Atomic/Graphics/RenderPath.cpp
  51. 4 0
      Source/Atomic/Graphics/Renderer.cpp
  52. 1 1
      Source/Atomic/Graphics/Renderer.h
  53. 1 1
      Source/Atomic/Graphics/Shader.cpp
  54. 37 61
      Source/Atomic/Graphics/View.cpp
  55. 4 3
      Source/Atomic/Graphics/View.h
  56. 12 1
      Source/Atomic/IO/Deserializer.cpp
  57. 2 0
      Source/Atomic/IO/Deserializer.h
  58. 3 2
      Source/Atomic/IO/File.cpp
  59. 9 0
      Source/Atomic/IO/File.h
  60. 74 79
      Source/Atomic/IO/FileSystem.cpp
  61. 2 2
      Source/Atomic/IO/PackageFile.cpp
  62. 13 1
      Source/Atomic/IO/Serializer.cpp
  63. 2 0
      Source/Atomic/IO/Serializer.h
  64. 2 2
      Source/Atomic/Input/Input.cpp
  65. 7 2
      Source/Atomic/Math/AreaAllocator.cpp
  66. 1 1
      Source/Atomic/Math/AreaAllocator.h
  67. 2 0
      Source/Atomic/Math/BoundingBox.cpp
  68. 2 0
      Source/Atomic/Math/Color.cpp
  69. 13 0
      Source/Atomic/Math/Color.h
  70. 2 0
      Source/Atomic/Math/Frustum.cpp
  71. 48 2
      Source/Atomic/Math/MathDefs.h
  72. 2 0
      Source/Atomic/Math/Plane.cpp
  73. 9 0
      Source/Atomic/Math/Plane.h
  74. 6 0
      Source/Atomic/Math/Polyhedron.cpp
  75. 7 0
      Source/Atomic/Math/Polyhedron.h
  76. 14 5
      Source/Atomic/Math/Quaternion.cpp
  77. 2 0
      Source/Atomic/Math/Random.cpp
  78. 2 0
      Source/Atomic/Math/Ray.cpp
  79. 2 0
      Source/Atomic/Math/Rect.cpp
  80. 2 0
      Source/Atomic/Math/Sphere.cpp
  81. 8 0
      Source/Atomic/Math/Vector2.h
  82. 311 198
      Source/Atomic/Navigation/CrowdAgent.cpp
  83. 110 57
      Source/Atomic/Navigation/CrowdAgent.h
  84. 690 0
      Source/Atomic/Navigation/CrowdManager.cpp
  85. 198 0
      Source/Atomic/Navigation/CrowdManager.h
  86. 0 546
      Source/Atomic/Navigation/DetourCrowdManager.cpp
  87. 0 146
      Source/Atomic/Navigation/DetourCrowdManager.h
  88. 34 38
      Source/Atomic/Navigation/DynamicNavigationMesh.cpp
  89. 3 0
      Source/Atomic/Navigation/DynamicNavigationMesh.h
  90. 43 44
      Source/Atomic/Navigation/NavArea.cpp
  91. 33 31
      Source/Atomic/Navigation/NavArea.h
  92. 13 23
      Source/Atomic/Navigation/NavBuildData.cpp
  93. 6 6
      Source/Atomic/Navigation/NavBuildData.h
  94. 1 0
      Source/Atomic/Navigation/Navigable.h
  95. 12 1
      Source/Atomic/Navigation/NavigationEvents.h
  96. 99 66
      Source/Atomic/Navigation/NavigationMesh.cpp
  97. 52 22
      Source/Atomic/Navigation/NavigationMesh.h
  98. 2 3
      Source/Atomic/Navigation/Obstacle.cpp
  99. 4 0
      Source/Atomic/Navigation/Obstacle.h
  100. 4 0
      Source/Atomic/Navigation/OffMeshConnection.h

+ 2 - 2
Resources/CoreData/RenderPaths/ForwardDepth.xml

@@ -1,8 +1,8 @@
 <renderpath>
     <rendertarget name="depth" sizedivisor="1 1" format="lineardepth" />
-    <command type="clear" color="1 1 1 1" depth="1.0" output="depth" />
+    <command type="clear" color="1 1 1 1" depth="1.0" stencil="0" output="depth" />
     <command type="scenepass" pass="depth" output="depth" />
-    <command type="clear" color="fog" depth="1.0" stencil="0" />
+    <command type="clear" color="fog" />
     <command type="scenepass" pass="base" vertexlights="true" metadata="base" />
     <command type="forwardlights" pass="light" />
     <command type="scenepass" pass="postopaque" />

+ 11 - 9
Resources/CoreData/Shaders/HLSL/Basic.hlsl

@@ -3,6 +3,12 @@
 #include "Transform.hlsl"
 
 void VS(float4 iPos : POSITION,
+    #ifdef DIFFMAP
+        float2 iTexCoord : TEXCOORD0,
+    #endif
+    #ifdef VERTEXCOLOR
+        float4 iColor : COLOR0,
+    #endif
     #ifdef SKINNED
         float4 iBlendWeights : BLENDWEIGHT,
         int4 iBlendIndices : BLENDINDICES,
@@ -14,15 +20,11 @@ void VS(float4 iPos : POSITION,
         float2 iSize : TEXCOORD1,
     #endif
     #ifdef DIFFMAP
-        float2 iTexCoord : TEXCOORD0,
+        out float2 oTexCoord : TEXCOORD0,
     #endif
     #ifdef VERTEXCOLOR
-        float4 iColor : COLOR0,
         out float4 oColor : COLOR0,
     #endif
-    #ifdef DIFFMAP
-        out float2 oTexCoord : TEXCOORD0,
-    #endif
     #if defined(D3D11) && defined(CLIPPLANE)
         out float oClip : SV_CLIPDISTANCE0,
     #endif
@@ -45,15 +47,15 @@ void VS(float4 iPos : POSITION,
 }
 
 void PS(
-    #ifdef VERTEXCOLOR
-        float4 iColor : COLOR0,
-    #endif
     #if defined(DIFFMAP) || defined(ALPHAMAP)
         float2 iTexCoord : TEXCOORD0,
     #endif
+    #ifdef VERTEXCOLOR
+        float4 iColor : COLOR0,
+    #endif
     #if defined(D3D11) && defined(CLIPPLANE)
         float iClip : SV_CLIPDISTANCE0,
-    #endif    
+    #endif
     out float4 oColor : OUTCOLOR0)
 {
     float4 diffColor = cMatDiffColor;

+ 3 - 3
Resources/CoreData/Shaders/HLSL/Depth.hlsl

@@ -12,7 +12,7 @@ void VS(float4 iPos : POSITION,
     #endif
     float2 iTexCoord : TEXCOORD0,
     out float3 oTexCoord : TEXCOORD0,
-    out float4 oPos : POSITION)
+    out float4 oPos : OUTPOSITION)
 {
     float4x3 modelMatrix = iModelMatrix;
     float3 worldPos = GetWorldPos(modelMatrix);
@@ -22,10 +22,10 @@ void VS(float4 iPos : POSITION,
 
 void PS(
     float3 iTexCoord : TEXCOORD0,
-    out float4 oColor : COLOR0)
+    out float4 oColor : OUTCOLOR0)
 {
     #ifdef ALPHAMASK
-        float alpha = tex2D(sDiffMap, iTexCoord.xy).a;
+        float alpha = Sample2D(sDiffMap, iTexCoord.xy).a;
         if (alpha < 0.5)
             discard;
     #endif

+ 9 - 6
Resources/CoreData/Shaders/HLSL/Samplers.hlsl

@@ -1,3 +1,12 @@
+#ifdef D3D11
+// Make sampling macros also available for VS on D3D11
+#define Sample2D(tex, uv) t##tex.Sample(s##tex, uv)
+#define Sample2DProj(tex, uv) t##tex.Sample(s##tex, uv.xy / uv.w)
+#define Sample2DLod0(tex, uv) t##tex.SampleLevel(s##tex, uv, 0.0)
+#define SampleCube(tex, uv) t##tex.Sample(s##tex, uv)
+#define SampleShadow(tex, uv) t##tex.SampleCmpLevelZero(s##tex, uv.xy, uv.z)
+#endif
+
 #ifdef COMPILEPS
 
 #ifndef D3D11
@@ -76,12 +85,6 @@ SamplerState sLightBuffer : register(s14);
 SamplerState sZoneCubeMap : register(s15);
 SamplerState sZoneVolumeMap : register(s15);
 
-#define Sample2D(tex, uv) t##tex.Sample(s##tex, uv)
-#define Sample2DProj(tex, uv) t##tex.Sample(s##tex, uv.xy / uv.w)
-#define Sample2DLod0(tex, uv) t##tex.SampleLevel(s##tex, uv, 0.0)
-#define SampleCube(tex, uv) t##tex.Sample(s##tex, uv)
-#define SampleShadow(tex, uv) t##tex.SampleCmpLevelZero(s##tex, uv.xy, uv.z)
-
 #endif
 
 float3 DecodeNormal(float4 normalInput)

+ 3 - 0
Resources/CoreData/Techniques/BasicVColUnlitAlpha.xml

@@ -0,0 +1,3 @@
+<technique vs="Basic" ps="Basic" vsdefines="DIFFMAP VERTEXCOLOR" psdefines="DIFFMAP VERTEXCOLOR">
+    <pass name="alpha" depthwrite="false" blend="alpha" />
+</technique>

+ 20 - 4
Source/Atomic/Atomic3D/AnimatedModel.cpp

@@ -73,7 +73,8 @@ AnimatedModel::AnimatedModel(Context* context) :
     boneBoundingBoxDirty_(true),
     isMaster_(true),
     loading_(false),
-    assignBonesPending_(false)
+    assignBonesPending_(false),
+    forceAnimationUpdate_(false)
 {
 }
 
@@ -211,9 +212,17 @@ void AnimatedModel::Update(const FrameInfo& frame)
     // If headless, retain the current animation distance (should be 0)
     if (frame.camera_ && abs((int)frame.frameNumber_ - (int)viewFrameNumber_) > 1)
     {
-        // First check for no update at all when invisible
+        // First check for no update at all when invisible. In that case reset LOD timer to ensure update
+        // next time the model is in view
         if (!updateInvisible_)
+        {
+            if (animationDirty_)
+            {
+                animationLodTimer_ = -1.0f;
+                forceAnimationUpdate_ = true;
+            }
             return;
+        }
         float distance = frame.camera_->GetDistance(node_->GetWorldPosition());
         // If distance is greater than draw distance, no need to update at all
         if (drawDistance_ > 0.0f && distance > drawDistance_)
@@ -268,6 +277,13 @@ void AnimatedModel::UpdateBatches(const FrameInfo& frame)
 
 void AnimatedModel::UpdateGeometry(const FrameInfo& frame)
 {
+    // Late update in case the model came into view and animation was dirtied in the meanwhile
+    if (forceAnimationUpdate_)
+    {
+        UpdateAnimation(frame);
+        forceAnimationUpdate_ = false;
+    }
+
     if (morphsDirty_)
         UpdateMorphs();
 
@@ -277,7 +293,7 @@ void AnimatedModel::UpdateGeometry(const FrameInfo& frame)
 
 UpdateGeometryType AnimatedModel::GetUpdateGeometryType()
 {
-    if (morphsDirty_)
+    if (morphsDirty_ || forceAnimationUpdate_)
         return UPDATE_MAIN_THREAD;
     else if (skinningDirty_)
         return UPDATE_WORKER_THREAD;
@@ -1150,7 +1166,7 @@ void AnimatedModel::UpdateAnimation(const FrameInfo& frame)
     // If using animation LOD, accumulate time and see if it is time to update
     if (animationLodBias_ > 0.0f && animationLodDistance_ > 0.0f)
     {
-        // Check for first time update
+        // Perform the first update always regardless of LOD timer
         if (animationLodTimer_ >= 0.0f)
         {
             animationLodTimer_ += animationLodBias_ * frame.timeStep_ * ANIMATION_LOD_BASESCALE;

+ 2 - 0
Source/Atomic/Atomic3D/AnimatedModel.h

@@ -255,6 +255,8 @@ private:
     bool assignBonesPending_;
     /// Whether bone creation is enabled, globally
     static bool boneCreationEnabled_;
+    /// Force animation update after becoming visible flag.
+    bool forceAnimationUpdate_;
 };
 
 }

+ 29 - 3
Source/Atomic/Atomic3D/AnimationController.cpp

@@ -44,6 +44,7 @@ static const unsigned char CTRL_STARTBONE = 0x2;
 static const unsigned char CTRL_AUTOFADE = 0x4;
 static const unsigned char CTRL_SETTIME = 0x08;
 static const unsigned char CTRL_SETWEIGHT = 0x10;
+static const unsigned char CTRL_REMOVEONCOMPLETION = 0x20;
 static const float EXTRA_ANIM_FADEOUT_TIME = 0.1f;
 static const float COMMAND_STAY_TIME = 0.25f;
 static const unsigned MAX_NODE_ANIMATION_STATES = 256;
@@ -70,7 +71,6 @@ void AnimationController::RegisterObject(Context* context)
         Variant::emptyBuffer, AM_NET | AM_LATESTDATA | AM_NOEDIT);
     MIXED_ACCESSOR_ATTRIBUTE("Node Animation States", GetNodeAnimationStatesAttr, SetNodeAnimationStatesAttr, VariantVector,
         Variant::emptyVariantVector, AM_FILE | AM_NOEDIT);
-
 }
 
 void AnimationController::OnSetEnabled()
@@ -128,7 +128,7 @@ void AnimationController::Update(float timeStep)
             }
 
             // Remove if weight zero and target weight zero
-            if (state->GetWeight() == 0.0f && (targetWeight == 0.0f || fadeTime == 0.0f))
+            if (state->GetWeight() == 0.0f && (targetWeight == 0.0f || fadeTime == 0.0f) && i->removeOnCompletion_)
                 remove = true;
         }
 
@@ -176,7 +176,7 @@ bool AnimationController::Play(const String& name, unsigned char layer, bool loo
         }
 
         if (!newAnimation)
-            GetSubsystem<ResourceCache>()->GetResource<Animation>(name);
+            newAnimation = GetSubsystem<ResourceCache>()->GetResource<Animation>(name);
 
         state = AddAnimationState(newAnimation);
         if (!state)
@@ -377,6 +377,19 @@ bool AnimationController::SetWeight(const String& name, float weight)
     return true;
 }
 
+bool AnimationController::SetRemoveOnCompletion(const String& name, bool removeOnCompletion)
+{
+    unsigned index;
+    AnimationState* state;
+    FindAnimation(name, index, state);
+    if (index == M_MAX_UNSIGNED || !state)
+        return false;
+
+    animations_[index].removeOnCompletion_ = removeOnCompletion;
+    MarkNetworkUpdate();
+    return true;
+}
+
 bool AnimationController::SetLooped(const String& name, bool enable)
 {
     AnimationState* state = GetAnimationState(name);
@@ -517,6 +530,14 @@ float AnimationController::GetAutoFade(const String& name) const
     return index != M_MAX_UNSIGNED ? animations_[index].autoFadeTime_ : 0.0f;
 }
 
+bool AnimationController::GetRemoveOnCompletion(const String& name) const
+{
+    unsigned index;
+    AnimationState* state;
+    FindAnimation(name, index, state);
+    return index != M_MAX_UNSIGNED ? animations_[index].removeOnCompletion_ : false;
+}
+
 AnimationState* AnimationController::GetAnimationState(const String& name) const
 {
     return GetAnimationState(StringHash(name));
@@ -619,6 +640,9 @@ void AnimationController::SetNetAnimationsAttr(const PODVector<unsigned char>& v
             animations_[index].autoFadeTime_ = (float)buf.ReadUByte() / 64.0f; // 6 bits of decimal precision, max. 4 seconds fade
         else
             animations_[index].autoFadeTime_ = 0.0f;
+        
+        animations_[index].removeOnCompletion_ = (ctrl & CTRL_REMOVEONCOMPLETION) != 0;
+        
         if (ctrl & CTRL_SETTIME)
         {
             unsigned char setTimeRev = buf.ReadUByte();
@@ -731,6 +755,8 @@ const PODVector<unsigned char>& AnimationController::GetNetAnimationsAttr() cons
             ctrl |= CTRL_STARTBONE;
         if (i->autoFadeTime_ > 0.0f)
             ctrl |= CTRL_AUTOFADE;
+        if (i->removeOnCompletion_)
+            ctrl |= CTRL_REMOVEONCOMPLETION;
         if (i->setTimeTtl_ > 0.0f)
             ctrl |= CTRL_SETTIME;
         if (i->setWeightTtl_ > 0.0f)

+ 8 - 1
Source/Atomic/Atomic3D/AnimationController.h

@@ -47,7 +47,8 @@ struct AnimationControl
         setTime_(0),
         setWeight_(0),
         setTimeRev_(0),
-        setWeightRev_(0)
+        setWeightRev_(0),
+        removeOnCompletion_(true)
     {
     }
 
@@ -75,6 +76,8 @@ struct AnimationControl
     unsigned char setTimeRev_;
     /// Set weight command revision.
     unsigned char setWeightRev_;
+    /// Sets whether this should automatically be removed when it finishes playing.
+    bool removeOnCompletion_;
 };
 
 /// %Component that drives an AnimatedModel's animations.
@@ -124,6 +127,8 @@ public:
     bool SetSpeed(const String& name, float speed);
     /// Set animation autofade at end (non-looped animations only.) Zero time disables. Return true on success.
     bool SetAutoFade(const String& name, float fadeOutTime);
+    /// Set whether an animation auto-removes on completion.
+    bool SetRemoveOnCompletion(const String& name, bool removeOnCompletion);
 
     /// Return whether an animation is active. Note that non-looping animations that are being clamped at the end also return true.
     bool IsPlaying(const String& name) const;
@@ -155,6 +160,8 @@ public:
     float GetFadeTime(const String& name) const;
     /// Return animation autofade time.
     float GetAutoFade(const String& name) const;
+    /// Return whether animation auto-removes on completion, or false if no such animation.
+    bool GetRemoveOnCompletion(const String& name) const;
     /// Find an animation state by animation name.
     AnimationState* GetAnimationState(const String& name) const;
     /// Find an animation state by animation name hash

+ 4 - 0
Source/Atomic/Atomic3D/DecalSet.cpp

@@ -42,6 +42,10 @@
 
 #include "../DebugNew.h"
 
+#ifdef _MSC_VER
+#pragma warning(disable:6293)
+#endif
+
 namespace Atomic
 {
 

+ 84 - 8
Source/Atomic/Atomic3D/Terrain.cpp

@@ -89,6 +89,8 @@ Terrain::Terrain(Context* context) :
     patchSize_(DEFAULT_PATCH_SIZE),
     lastPatchSize_(0),
     numLodLevels_(1),
+    maxLodLevels_(MAX_LOD_LEVELS),
+    occlusionLodLevel_(M_MAX_UNSIGNED),
     smoothing_(false),
     visible_(true),
     castShadows_(false),
@@ -122,6 +124,7 @@ void Terrain::RegisterObject(Context* context)
         AM_DEFAULT);
     ATTRIBUTE("Vertex Spacing", Vector3, spacing_, DEFAULT_SPACING, AM_DEFAULT);
     ACCESSOR_ATTRIBUTE("Patch Size", GetPatchSize, SetPatchSizeAttr, int, DEFAULT_PATCH_SIZE, AM_DEFAULT);
+    ACCESSOR_ATTRIBUTE("Max LOD Levels", GetMaxLodLevels, SetMaxLodLevelsAttr, unsigned, MAX_LOD_LEVELS, AM_DEFAULT);
     ATTRIBUTE("Smooth Height Map", bool, smoothing_, false, AM_DEFAULT);
     ACCESSOR_ATTRIBUTE("Is Occluder", IsOccluder, SetOccluder, bool, false, AM_DEFAULT);
     ACCESSOR_ATTRIBUTE("Can Be Occluded", IsOccludee, SetOccludee, bool, true, AM_DEFAULT);
@@ -134,6 +137,7 @@ void Terrain::RegisterObject(Context* context)
     ACCESSOR_ATTRIBUTE("Light Mask", GetLightMask, SetLightMask, unsigned, DEFAULT_LIGHTMASK, AM_DEFAULT);
     ACCESSOR_ATTRIBUTE("Shadow Mask", GetShadowMask, SetShadowMask, unsigned, DEFAULT_SHADOWMASK, AM_DEFAULT);
     ACCESSOR_ATTRIBUTE("Zone Mask", GetZoneMask, SetZoneMask, unsigned, DEFAULT_ZONEMASK, AM_DEFAULT);
+    ACCESSOR_ATTRIBUTE("Occlusion LOD level", GetOcclusionLodLevel, SetOcclusionLodLevelAttr, unsigned, M_MAX_UNSIGNED, AM_DEFAULT);
 }
 
 void Terrain::OnSetAttribute(const AttributeInfo& attr, const Variant& src)
@@ -187,6 +191,31 @@ void Terrain::SetSpacing(const Vector3& spacing)
     }
 }
 
+void Terrain::SetMaxLodLevels(unsigned levels)
+{
+    levels = Clamp((int)levels, 1, MAX_LOD_LEVELS);
+    if (levels != maxLodLevels_)
+    {
+        maxLodLevels_ = levels;
+        lastPatchSize_ = 0; // Force full recreate
+
+        CreateGeometry();
+        MarkNetworkUpdate();
+    }
+}
+
+void Terrain::SetOcclusionLodLevel(unsigned level)
+{
+    if (level != occlusionLodLevel_)
+    {
+        occlusionLodLevel_ = level;
+        lastPatchSize_ = 0; // Force full recreate
+
+        CreateGeometry();
+        MarkNetworkUpdate();
+    }
+}
+
 void Terrain::SetSmoothing(bool enable)
 {
     if (enable != smoothing_)
@@ -468,20 +497,28 @@ void Terrain::CreatePatchGeometry(TerrainPatch* patch)
     VertexBuffer* vertexBuffer = patch->GetVertexBuffer();
     Geometry* geometry = patch->GetGeometry();
     Geometry* maxLodGeometry = patch->GetMaxLodGeometry();
-    Geometry* minLodGeometry = patch->GetMinLodGeometry();
+    Geometry* occlusionGeometry = patch->GetOcclusionGeometry();
 
     if (vertexBuffer->GetVertexCount() != row * row)
         vertexBuffer->SetSize(row * row, MASK_POSITION | MASK_NORMAL | MASK_TEXCOORD1 | MASK_TANGENT);
 
     SharedArrayPtr<unsigned char> cpuVertexData(new unsigned char[row * row * sizeof(Vector3)]);
+    SharedArrayPtr<unsigned char> occlusionCpuVertexData(new unsigned char[row * row * sizeof(Vector3)]);
 
     float* vertexData = (float*)vertexBuffer->Lock(0, vertexBuffer->GetVertexCount());
     float* positionData = (float*)cpuVertexData.Get();
+    float* occlusionData = (float*)occlusionCpuVertexData.Get();
     BoundingBox box;
 
+    unsigned occlusionLevel = occlusionLodLevel_;
+    if (occlusionLevel > numLodLevels_ - 1)
+        occlusionLevel = numLodLevels_ - 1;
+
     if (vertexData)
     {
         const IntVector2& coords = patch->GetCoordinates();
+        int lodExpand = (1 << (occlusionLevel)) - 1;
+        int halfLodExpand = (1 << (occlusionLevel)) / 2;
 
         for (int z = 0; z <= patchSize_; ++z)
         {
@@ -501,6 +538,25 @@ void Terrain::CreatePatchGeometry(TerrainPatch* patch)
 
                 box.Merge(position);
 
+                // For vertices that are part of the occlusion LOD, calculate the minimum height in the neighborhood
+                // to prevent false positive occlusion due to inaccuracy between occlusion LOD & visible LOD
+                float minHeight = position.y_;
+                if (halfLodExpand > 0 && (x & lodExpand) == 0 && (z & lodExpand) == 0)
+                {
+                    int minX = Max(xPos - halfLodExpand, 0);
+                    int maxX = Min(xPos + halfLodExpand, numVertices_.x_ - 1);
+                    int minZ = Max(zPos - halfLodExpand, 0);
+                    int maxZ = Min(zPos + halfLodExpand, numVertices_.y_ - 1);
+                    for (int nZ = minZ; nZ <= maxZ; ++nZ)
+                    {
+                        for (int nX = minX; nX <= maxX; ++nX)
+                            minHeight = Min(minHeight, GetRawHeight(nX, nZ));
+                    }
+                }
+                *occlusionData++ = position.x_;
+                *occlusionData++ = minHeight;
+                *occlusionData++ = position.z_;
+
                 // Normal
                 Vector3 normal = GetRawNormal(xPos, zPos);
                 *vertexData++ = normal.x_;
@@ -529,7 +585,7 @@ void Terrain::CreatePatchGeometry(TerrainPatch* patch)
 
     if (drawRanges_.Size())
     {
-        unsigned lastDrawRange = drawRanges_.Size() - 1;
+        unsigned occlusionDrawRange = occlusionLevel << 4;
 
         geometry->SetIndexBuffer(indexBuffer_);
         geometry->SetDrawRange(TRIANGLE_LIST, drawRanges_[0].first_, drawRanges_[0].second_, false);
@@ -537,13 +593,11 @@ void Terrain::CreatePatchGeometry(TerrainPatch* patch)
         maxLodGeometry->SetIndexBuffer(indexBuffer_);
         maxLodGeometry->SetDrawRange(TRIANGLE_LIST, drawRanges_[0].first_, drawRanges_[0].second_, false);
         maxLodGeometry->SetRawVertexData(cpuVertexData, sizeof(Vector3), MASK_POSITION);
-        minLodGeometry->SetIndexBuffer(indexBuffer_);
-        minLodGeometry->SetDrawRange(TRIANGLE_LIST, drawRanges_[lastDrawRange].first_, drawRanges_[lastDrawRange].second_, false);
-        minLodGeometry->SetRawVertexData(cpuVertexData, sizeof(Vector3), MASK_POSITION);
+        occlusionGeometry->SetIndexBuffer(indexBuffer_);
+        occlusionGeometry->SetDrawRange(TRIANGLE_LIST, drawRanges_[occlusionDrawRange].first_, drawRanges_[occlusionDrawRange].second_, false);
+        occlusionGeometry->SetRawVertexData(occlusionCpuVertexData, sizeof(Vector3), MASK_POSITION);
     }
 
-    // Offset the occlusion geometry by vertex spacing to reduce possibility of over-aggressive occlusion
-    patch->SetOcclusionOffset(-0.5f * (spacing_.x_ + spacing_.z_));
     patch->ResetLod();
 }
 
@@ -600,6 +654,28 @@ void Terrain::SetPatchSizeAttr(int value)
     }
 }
 
+void Terrain::SetMaxLodLevelsAttr(unsigned value)
+{
+    value = Clamp((int)value, 1, MAX_LOD_LEVELS);
+
+    if (value != maxLodLevels_)
+    {
+        maxLodLevels_ = value;
+        lastPatchSize_ = 0; // Force full recreate
+        recreateTerrain_ = true;
+    }
+}
+
+void Terrain::SetOcclusionLodLevelAttr(unsigned value)
+{
+    if (value != occlusionLodLevel_)
+    {
+        occlusionLodLevel_ = value;
+        lastPatchSize_ = 0; // Force full recreate
+        recreateTerrain_ = true;
+    }
+}
+
 ResourceRef Terrain::GetMaterialAttr() const
 {
     return GetResourceRef(material_, Material::GetTypeStatic());
@@ -624,7 +700,7 @@ void Terrain::CreateGeometry()
     // Determine number of LOD levels
     unsigned lodSize = (unsigned)patchSize_;
     numLodLevels_ = 1;
-    while (lodSize > MIN_PATCH_SIZE && numLodLevels_ < MAX_LOD_LEVELS)
+    while (lodSize > MIN_PATCH_SIZE && numLodLevels_ < maxLodLevels_)
     {
         lodSize >>= 1;
         ++numLodLevels_;

+ 19 - 1
Source/Atomic/Atomic3D/Terrain.h

@@ -57,6 +57,10 @@ public:
     void SetPatchSize(int size);
     /// Set vertex (XZ) and height (Y) spacing.
     void SetSpacing(const Vector3& spacing);
+    /// Set maximum number of LOD levels for terrain patches. This can be between 1-4.
+    void SetMaxLodLevels(unsigned levels);
+    /// Set LOD level used for terrain patch occlusion. By default (M_MAX_UNSIGNED) the coarsest. Since the LOD level used needs to be fixed, using finer LOD levels may result in false positive occlusion in cases where the actual rendered geometry is coarser, so use with caution.
+    void SetOcclusionLodLevel(unsigned level);
     /// Set smoothing of heightmap.
     void SetSmoothing(bool enable);
     /// Set heightmap image. Dimensions should be a power of two + 1. Uses 8-bit grayscale, or optionally red as MSB and green as LSB for 16-bit accuracy. Return true if successful.
@@ -81,7 +85,7 @@ public:
     void SetMaxLights(unsigned num);
     /// Set shadowcaster flag for patches.
     void SetCastShadows(bool enable);
-    /// Set occlusion flag for patches. Occlusion uses the coarsest LOD and may potentially be too aggressive, so use with caution.
+    /// Set occlusion flag for patches. Occlusion uses the coarsest LOD by default.
     void SetOccluder(bool enable);
     /// Set occludee flag for patches.
     void SetOccludee(bool enable);
@@ -100,6 +104,12 @@ public:
     /// Return heightmap size in patches.
     const IntVector2& GetNumPatches() const { return numPatches_; }
 
+    /// Return maximum number of LOD levels for terrain patches. This can be between 1-4.
+    unsigned GetMaxLodLevels() const { return maxLodLevels_; }
+
+    /// Return LOD level used for occlusion.
+    unsigned GetOcclusionLodLevel() const { return occlusionLodLevel_; }
+
     /// Return whether smoothing is in use.
     bool GetSmoothing() const { return smoothing_; }
 
@@ -167,6 +177,10 @@ public:
     void SetMaterialAttr(const ResourceRef& value);
     /// Set patch size attribute.
     void SetPatchSizeAttr(int value);
+    /// Set max LOD levels attribute.
+    void SetMaxLodLevelsAttr(unsigned value);
+    /// Set occlusion LOD level attribute.
+    void SetOcclusionLodLevelAttr(unsigned value);
     /// Return heightmap attribute.
     ResourceRef GetHeightMapAttr() const;
     /// Return material attribute.
@@ -228,6 +242,10 @@ private:
     int lastPatchSize_;
     /// Number of terrain LOD levels.
     unsigned numLodLevels_;
+    /// Maximum number of LOD levels.
+    unsigned maxLodLevels_;
+    /// LOD level used for occlusion.
+    unsigned occlusionLodLevel_;
     /// Smoothing enable flag.
     bool smoothing_;
     /// Visible flag.

+ 11 - 22
Source/Atomic/Atomic3D/TerrainPatch.cpp

@@ -24,6 +24,7 @@
 
 #include "../Core/Context.h"
 #include "../Graphics/Camera.h"
+#include "../Graphics/DebugRenderer.h"
 #include "../Graphics/Geometry.h"
 #include "../Graphics/IndexBuffer.h"
 #include "../Graphics/Material.h"
@@ -48,15 +49,14 @@ TerrainPatch::TerrainPatch(Context* context) :
     Drawable(context, DRAWABLE_GEOMETRY),
     geometry_(new Geometry(context)),
     maxLodGeometry_(new Geometry(context)),
-    minLodGeometry_(new Geometry(context)),
+    occlusionGeometry_(new Geometry(context)),
     vertexBuffer_(new VertexBuffer(context)),
     coordinates_(IntVector2::ZERO),
-    lodLevel_(0),
-    occlusionOffset_(0.0f)
+    lodLevel_(0)
 {
     geometry_->SetVertexBuffer(0, vertexBuffer_, MASK_POSITION | MASK_NORMAL | MASK_TEXCOORD1 | MASK_TANGENT);
     maxLodGeometry_->SetVertexBuffer(0, vertexBuffer_, MASK_POSITION | MASK_NORMAL | MASK_TEXCOORD1 | MASK_TANGENT);
-    minLodGeometry_->SetVertexBuffer(0, vertexBuffer_, MASK_POSITION | MASK_NORMAL | MASK_TEXCOORD1 | MASK_TANGENT);
+    occlusionGeometry_->SetVertexBuffer(0, vertexBuffer_, MASK_POSITION | MASK_NORMAL | MASK_TEXCOORD1 | MASK_TANGENT);
 
     batches_.Resize(1);
     batches_[0].geometry_ = geometry_;
@@ -176,7 +176,7 @@ unsigned TerrainPatch::GetNumOccluderTriangles()
     if (mat && !mat->GetOcclusion())
         return 0;
     else
-        return minLodGeometry_->GetIndexCount() / 3;
+        return occlusionGeometry_->GetIndexCount() / 3;
 }
 
 bool TerrainPatch::DrawOcclusion(OcclusionBuffer* buffer)
@@ -198,27 +198,21 @@ bool TerrainPatch::DrawOcclusion(OcclusionBuffer* buffer)
     unsigned indexSize;
     unsigned elementMask;
 
-    minLodGeometry_->GetRawData(vertexData, vertexSize, indexData, indexSize, elementMask);
+    occlusionGeometry_->GetRawData(vertexData, vertexSize, indexData, indexSize, elementMask);
     // Check for valid geometry data
     if (!vertexData || !indexData)
         return true;
 
-    const Matrix3x4& worldTransform = node_->GetWorldTransform();
-
-    Matrix3x4 occlusionTransform(worldTransform.Translation() + worldTransform * Vector4(0.0f, occlusionOffset_, 0.0f,
-        0.0f), worldTransform.Rotation(), worldTransform.Scale());
-
     // Draw and check for running out of triangles
-    return buffer->Draw(occlusionTransform, vertexData, vertexSize, indexData, indexSize, minLodGeometry_->GetIndexStart(),
-        minLodGeometry_->GetIndexCount());
+    return buffer->Draw(node_->GetWorldTransform(), vertexData, vertexSize, indexData, indexSize, occlusionGeometry_->GetIndexStart(),
+        occlusionGeometry_->GetIndexCount());
 }
 
 void TerrainPatch::DrawDebugGeometry(DebugRenderer* debug, bool depthTest)
 {
-    // Intentionally no action
+    // Intentionally no operation
 }
 
-
 void TerrainPatch::SetOwner(Terrain* terrain)
 {
     owner_ = terrain;
@@ -248,11 +242,6 @@ void TerrainPatch::SetCoordinates(const IntVector2& coordinates)
     coordinates_ = coordinates;
 }
 
-void TerrainPatch::SetOcclusionOffset(float offset)
-{
-    occlusionOffset_ = offset;
-}
-
 void TerrainPatch::ResetLod()
 {
     lodLevel_ = 0;
@@ -268,9 +257,9 @@ Geometry* TerrainPatch::GetMaxLodGeometry() const
     return maxLodGeometry_;
 }
 
-Geometry* TerrainPatch::GetMinLodGeometry() const
+Geometry* TerrainPatch::GetOcclusionGeometry() const
 {
-    return minLodGeometry_;
+    return occlusionGeometry_;
 }
 
 VertexBuffer* TerrainPatch::GetVertexBuffer() const

+ 5 - 12
Source/Atomic/Atomic3D/TerrainPatch.h

@@ -71,17 +71,15 @@ public:
     void SetBoundingBox(const BoundingBox& box);
     /// Set patch coordinates.
     void SetCoordinates(const IntVector2& coordinates);
-    /// Set vertical offset for occlusion geometry. Should be negative.
-    void SetOcclusionOffset(float offset);
     /// Reset to LOD level 0.
     void ResetLod();
 
     /// Return visible geometry.
     Geometry* GetGeometry() const;
-    /// Return max LOD geometry.
+    /// Return max LOD geometry. Used for decals.
     Geometry* GetMaxLodGeometry() const;
-    /// Return min LOD geometry.
-    Geometry* GetMinLodGeometry() const;
+    /// Return geometry used for occlusion.
+    Geometry* GetOcclusionGeometry() const;
     /// Return vertex buffer.
     VertexBuffer* GetVertexBuffer() const;
     /// Return owner terrain.
@@ -108,9 +106,6 @@ public:
     /// Return current LOD level.
     unsigned GetLodLevel() const { return lodLevel_; }
 
-    /// Return vertical offset for occlusion geometry..
-    float GetOcclusionOffset() const { return occlusionOffset_; }
-
 protected:
     /// Recalculate the world-space bounding box.
     virtual void OnWorldBoundingBoxUpdate();
@@ -123,8 +118,8 @@ private:
     SharedPtr<Geometry> geometry_;
     /// Geometry that is locked to the max LOD level. Used for decals.
     SharedPtr<Geometry> maxLodGeometry_;
-    /// Geometry that is locked to the minimum LOD level. Used for occlusion.
-    SharedPtr<Geometry> minLodGeometry_;
+    /// Geometry that is used for occlusion.
+    SharedPtr<Geometry> occlusionGeometry_;
     /// Vertex buffer.
     SharedPtr<VertexBuffer> vertexBuffer_;
     /// Parent terrain.
@@ -143,8 +138,6 @@ private:
     IntVector2 coordinates_;
     /// Current LOD level.
     unsigned lodLevel_;
-    /// Vertical offset for occlusion geometry.
-    float occlusionOffset_;
 };
 
 }

+ 4 - 0
Source/Atomic/Audio/Audio.cpp

@@ -36,6 +36,10 @@
 
 #include "../DebugNew.h"
 
+#ifdef _MSC_VER
+  #pragma warning(disable:6293)
+#endif
+
 namespace Atomic
 {
 

+ 12 - 8
Source/Atomic/Container/Str.cpp

@@ -28,6 +28,10 @@
 
 #include "../DebugNew.h"
 
+#ifdef _MSC_VER
+#pragma warning(disable:6293)
+#endif
+
 namespace Atomic
 {
 
@@ -559,7 +563,7 @@ Vector<String> String::Split(char separator) const
     return Split(CString(), separator);
 }
 
-void String::Join(const Vector<String>& subStrings, String glue)
+void String::Join(const Vector<String>& subStrings, const String& glue)
 {
     *this = Joined(subStrings, glue);
 }
@@ -1009,9 +1013,9 @@ unsigned String::DecodeUTF16(const wchar_t*& src)
 {
     if (src == 0)
         return 0;
-    
+
     unsigned short word1 = *src;
-    
+
     // Check if we are at a low surrogate
     word1 = *src++;
     if (word1 >= 0xdc00 && word1 < 0xe000)
@@ -1020,7 +1024,7 @@ unsigned String::DecodeUTF16(const wchar_t*& src)
             ++src;
         return '?';
     }
-    
+
     if (word1 < 0xd800 || word1 >= 0xe00)
         return word1;
     else
@@ -1085,7 +1089,7 @@ Vector<String> String::Split(const char* str, char separator)
     return ret;
 }
 
-String String::Joined(const Vector<String>& subStrings, String glue)
+String String::Joined(const Vector<String>& subStrings, const String& glue)
 {
     if (subStrings.Empty())
         return String();
@@ -1261,7 +1265,7 @@ WString::WString(const String& str) :
 #ifdef WIN32
     unsigned neededSize = 0;
     wchar_t temp[3];
-    
+
     unsigned byteOffset = 0;
     while (byteOffset < str.Length())
     {
@@ -1269,9 +1273,9 @@ WString::WString(const String& str) :
         String::EncodeUTF16(dest, str.NextUTF8Char(byteOffset));
         neededSize += dest - temp;
     }
-    
+
     Resize(neededSize);
-    
+
     byteOffset = 0;
     wchar_t* dest = buffer_;
     while (byteOffset < str.Length())

+ 3 - 3
Source/Atomic/Container/Str.h

@@ -390,7 +390,7 @@ public:
     /// Return substrings split by a separator char.
     Vector<String> Split(char separator) const;
     /// Join substrings with a 'glue' string.
-    void Join(const Vector<String>& subStrings, String glue);
+    void Join(const Vector<String>& subStrings, const String& glue);
     /// Return index to the first occurrence of a string, or NPOS if not found.
     unsigned Find(const String& str, unsigned startPos = 0, bool caseSensitive = true) const;
     /// Return index to the first occurrence of a character, or NPOS if not found.
@@ -421,7 +421,7 @@ public:
     /// Return comparison result with a C string.
     int Compare(const char* str, bool caseSensitive = true) const;
 
-    /// Return whether contains a specific occurence of a string.
+    /// Return whether contains a specific occurrence of a string.
     bool Contains(const String& str, bool caseSensitive = true) const { return Find(str, 0, caseSensitive) != NPOS; }
 
     /// Return whether contains a specific character.
@@ -465,7 +465,7 @@ public:
     /// Return substrings split by a separator char.
     static Vector<String> Split(const char* str, char separator);
     /// Return a string by joining substrings with a 'glue' string.
-    static String Joined(const Vector<String>& subStrings, String glue);
+    static String Joined(const Vector<String>& subStrings, const String& glue);
     /// Encode Unicode character to UTF8. Pointer will be incremented.
     static void EncodeUTF8(char*& dest, unsigned unicodeChar);
     /// Decode Unicode character from UTF8. Pointer will be incremented.

+ 19 - 0
Source/Atomic/Container/Vector.h

@@ -28,6 +28,11 @@
 #include <cstring>
 #include <new>
 
+#ifdef _MSC_VER
+#pragma warning(push)
+#pragma warning(disable:6293)
+#endif
+
 namespace Atomic
 {
 
@@ -170,7 +175,17 @@ public:
     }
 
     /// Add an element at the end.
+#ifndef COVERITY_SCAN_MODEL
     void Push(const T& value) { Resize(size_ + 1, &value); }
+#else
+    // FIXME: Attempt had been made to use this model in the Coverity-Scan model file without any success
+    // Probably because the model had generated a different mangled name than the one used by static analyzer
+    void Push(const T& value)
+    {
+        T array[] = {value};
+        Resize(size_ + 1, array);
+    }
+#endif
 
     /// Add another vector at the end.
     void Push(const Vector<T>& vector) { Resize(size_ + vector.size_, vector.Buffer()); }
@@ -948,3 +963,7 @@ template <class T> typename Atomic::PODVector<T>::Iterator begin(Atomic::PODVect
 template <class T> typename Atomic::PODVector<T>::Iterator end(Atomic::PODVector<T>& v) { return v.End(); }
 
 }
+
+#ifdef _MSC_VER
+#pragma warning(pop)
+#endif

+ 1 - 1
Source/Atomic/Core/Attribute.h

@@ -34,7 +34,7 @@ static const unsigned AM_EDIT = 0x0;
 static const unsigned AM_FILE = 0x1;
 /// Attribute used for network replication.
 static const unsigned AM_NET = 0x2;
-/// Attribute used for both file serialization and network replication (default.)
+/// Attribute used for both file serialization and network replication (default).
 static const unsigned AM_DEFAULT = 0x3;
 /// Attribute should use latest data grouping instead of delta update in network replication.
 static const unsigned AM_LATESTDATA = 0x4;

+ 3 - 5
Source/Atomic/Core/ProcessUtils.cpp

@@ -74,6 +74,8 @@ inline void SetFPUState(unsigned control)
 
 #endif
 
+#include <SDL/include/SDL.h>
+
 #include "../DebugNew.h"
 
 namespace Atomic
@@ -123,11 +125,7 @@ void InitFPU()
 
 void ErrorDialog(const String& title, const String& message)
 {
-#ifdef WIN32
-    MessageBoxW(0, WString(message).CString(), WString(title).CString(), 0);
-#else
-    PrintLine(message, true);
-#endif
+    SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_ERROR, title.CString(), message.CString(), 0);
 }
 
 void ErrorExit(const String& message, int exitCode)

+ 93 - 0
Source/Atomic/Core/Spline.cpp

@@ -31,6 +31,9 @@ namespace Atomic
 const char* interpolationModeNames[] =
 {
     "Bezier",
+    "Catmull-Rom",
+    "Linear",
+    "Catmull-Rom Full",
     0
 };
 
@@ -70,6 +73,33 @@ Variant Spline::GetPoint(float f) const
     {
     case BEZIER_CURVE:
         return BezierInterpolation(knots_, f);
+    case CATMULL_ROM_CURVE:
+        return CatmullRomInterpolation(knots_, f);
+    case LINEAR_CURVE:
+        return LinearInterpolation(knots_, f);
+    case CATMULL_ROM_FULL_CURVE:
+        {
+            /// \todo Do not allocate a new vector each time
+            Vector<Variant> fullKnots;
+            if (knots_.Size() > 1)
+            {
+                // Non-cyclic case: duplicate start and end
+                if (knots_.Front() != knots_.Back())
+                {
+                    fullKnots.Push(knots_.Front());
+                    fullKnots.Push(knots_);
+                    fullKnots.Push(knots_.Back());
+                }
+                // Cyclic case: smooth the tangents
+                else
+                {
+                    fullKnots.Push(knots_[knots_.Size() - 2]);
+                    fullKnots.Push(knots_);
+                    fullKnots.Push(knots_[1]);
+                }
+            }
+            return CatmullRomInterpolation(fullKnots, f);
+        }
 
     default:
         LOGERROR("Unsupported interpolation mode");
@@ -135,6 +165,7 @@ Variant Spline::BezierInterpolation(const Vector<Variant>& knots, float t) const
     }
     else
     {
+        /// \todo Do not allocate a new vector each time
         Vector<Variant> interpolatedKnots;
         for (unsigned i = 1; i < knots.Size(); i++)
         {
@@ -156,6 +187,68 @@ Variant Spline::BezierInterpolation(const Vector<Variant>& knots, float t) const
     }
 }
 
+template <typename T> Variant CalculateCatmullRom(const T& p0, const T& p1, const T& p2, const T& p3, float t, float t2, float t3)
+{
+    return Variant(0.5f * ((2.0f * p1) + (-p0 + p2) * t +
+        (2.0f * p0 - 5.0f * p1 + 4.0f * p2 - p3) * t2 +
+        (-p0 + 3.0f * p1 - 3.0f * p2 + p3) * t3));
+}
+
+Variant Spline::CatmullRomInterpolation(const Vector<Variant>& knots, float t) const
+{
+    if (knots.Size() < 4)
+        return Variant::EMPTY;
+    else
+    {
+        if (t >= 1.f)
+            return knots[knots.Size() - 2];
+
+        int originIndex = static_cast<int>(t * (knots.Size() - 3));
+        t = fmodf(t * (knots.Size() - 3), 1.f);
+        float t2 = t * t;
+        float t3 = t2 * t;
+
+        switch (knots[originIndex].GetType())
+        {
+        case VAR_FLOAT:
+            return CalculateCatmullRom(knots[originIndex].GetFloat(), knots[originIndex + 1].GetFloat(),
+                knots[originIndex + 2].GetFloat(), knots[originIndex + 3].GetFloat(), t, t2, t3);
+        case VAR_VECTOR2:
+            return CalculateCatmullRom(knots[originIndex].GetVector2(), knots[originIndex + 1].GetVector2(),
+                knots[originIndex + 2].GetVector2(), knots[originIndex + 3].GetVector2(), t, t2, t3);
+        case VAR_VECTOR3:
+            return CalculateCatmullRom(knots[originIndex].GetVector3(), knots[originIndex + 1].GetVector3(),
+                knots[originIndex + 2].GetVector3(), knots[originIndex + 3].GetVector3(), t, t2, t3);
+        case VAR_VECTOR4:
+            return CalculateCatmullRom(knots[originIndex].GetVector4(), knots[originIndex + 1].GetVector4(),
+                knots[originIndex + 2].GetVector4(), knots[originIndex + 3].GetVector4(), t, t2, t3);
+        case VAR_COLOR:
+            return CalculateCatmullRom(knots[originIndex].GetColor(), knots[originIndex + 1].GetColor(),
+                knots[originIndex + 2].GetColor(), knots[originIndex + 3].GetColor(), t, t2, t3);
+        case VAR_DOUBLE:
+            return CalculateCatmullRom(knots[originIndex].GetDouble(), knots[originIndex + 1].GetDouble(),
+                knots[originIndex + 2].GetDouble(), knots[originIndex + 3].GetDouble(), t, t2, t3);
+        default:
+            return Variant::EMPTY;
+        }
+    }
+}
+
+Variant Spline::LinearInterpolation(const Vector<Variant>& knots, float t) const
+{
+    if (knots.Size() < 2)
+        return Variant::EMPTY;
+    else
+    {
+        if (t >= 1.f)
+            return knots.Back();
+
+        int originIndex = Clamp((int)(t * (knots.Size() - 1)), 0, (int)(knots.Size() - 2));
+        t = fmodf(t * (knots.Size() - 1), 1.f);
+        return LinearInterpolation(knots[originIndex], knots[originIndex + 1], t);
+    }
+}
+
 Variant Spline::LinearInterpolation(const Variant& lhs, const Variant& rhs, float t) const
 {
     switch (lhs.GetType())

+ 31 - 20
Source/Atomic/Core/Spline.h

@@ -33,7 +33,14 @@ namespace Atomic
 
 enum InterpolationMode
 {
-    BEZIER_CURVE = 0
+    /// Bezier interpolation.
+    BEZIER_CURVE = 0,
+    /// Catmull-Rom interpolation. The first and last knots control velocity and are not included on the path.
+    CATMULL_ROM_CURVE,
+    /// Linear interpolation.
+    LINEAR_CURVE,
+    /// Catmull-Rom full path interpolation. Start and end knots are duplicated or looped as necessary to move through the full path.
+    CATMULL_ROM_FULL_CURVE
 };
 
 /// Spline class to get a point on it based off the interpolation mode.
@@ -42,9 +49,9 @@ class ATOMIC_API Spline
 public:
     /// Default constructor.
     Spline();
-    /// Constructor setting InterpolationMode.
+    /// Constructor setting interpolation mode.
     Spline(InterpolationMode mode);
-    /// Constructor setting Knots and InterpolationMode.
+    /// Constructor setting knots and interpolation mode.
     Spline(const Vector<Variant>& knots, InterpolationMode mode = BEZIER_CURVE);
     /// Copy constructor.
     Spline(const Spline& rhs);
@@ -62,55 +69,59 @@ public:
         return (knots_ == rhs.knots_ && interpolationMode_ == rhs.interpolationMode_);
     }
 
-    /// Non Equality operator.
+    /// Inequality operator.
     bool operator !=(const Spline& rhs) const
     {
         return !(*this == rhs);
     }
 
-    /// Return the ImplementationMode.
+    /// Return the interpolation mode.
     InterpolationMode GetInterpolationMode() const { return interpolationMode_; }
 
-    /// Return the Knots of the Spline.
+    /// Return the knots of the spline.
     const VariantVector& GetKnots() const { return knots_; }
 
-    /// Return the Knot at the specific index.
+    /// Return the knot at the specific index.
     Variant GetKnot(unsigned index) const { return knots_[index]; }
 
-    /// Return the T of the point of the Spline at f from 0.f - 1.f.
+    /// Return the T of the point of the spline at f from 0.f - 1.f.
     Variant GetPoint(float f) const;
 
-    /// Set the InterpolationMode of the Spline.
+    /// Set the interpolation mode.
     void SetInterpolationMode(InterpolationMode interpolationMode) { interpolationMode_ = interpolationMode; }
 
-    /// Set the Knots of the Spline.
+    /// Set the knots of the spline.
     void SetKnots(const Vector<Variant>& knots) { knots_ = knots; }
 
-    /// Set the Knot value of an existing Knot.
+    /// Set the value of an existing knot.
     void SetKnot(const Variant& knot, unsigned index);
-    /// Add a Knot to the end of the Spline.
+    /// Add a knot to the end of the spline.
     void AddKnot(const Variant& knot);
-    /// Add a Knot to the Spline at a specific index.
+    /// Add a knot to the spline at a specific index.
     void AddKnot(const Variant& knot, unsigned index);
 
-    /// Remove the last Knot on the Spline.
+    /// Remove the last knot on the spline.
     void RemoveKnot() { knots_.Pop(); }
 
-    /// Remove the Knot at the specific index.
+    /// Remove the knot at the specific index.
     void RemoveKnot(unsigned index) { knots_.Erase(index); }
 
-    /// Clear the Spline.
+    /// Clear the spline.
     void Clear() { knots_.Clear(); }
 
 private:
-    /// Perform Bezier Interpolation on the Spline.
+    /// Perform Bezier interpolation on the spline.
     Variant BezierInterpolation(const Vector<Variant>& knots, float t) const;
-    /// LinearInterpolation between two Variants based on underlying type.
+    /// Perform Spline interpolation on the spline.
+    Variant CatmullRomInterpolation(const Vector<Variant>& knots, float t) const;
+    /// Perform linear interpolation on the spline.
+    Variant LinearInterpolation(const Vector<Variant>& knots, float t) const;
+    /// Linear interpolation between two Variants based on underlying type.
     Variant LinearInterpolation(const Variant& lhs, const Variant& rhs, float t) const;
 
-    /// InterpolationMode.
+    /// Interpolation mode.
     InterpolationMode interpolationMode_;
-    /// Knots on the Spline.
+    /// Knots on the spline.
     VariantVector knots_;
 };
 

+ 73 - 18
Source/Atomic/Core/Variant.cpp

@@ -23,6 +23,7 @@
 #include "../Precompiled.h"
 
 #include "../Core/StringUtils.h"
+#include "../IO/VectorBuffer.h"
 
 #include <cstring>
 
@@ -35,6 +36,7 @@ const ResourceRef Variant::emptyResourceRef;
 const ResourceRefList Variant::emptyResourceRefList;
 const VariantMap Variant::emptyVariantMap;
 const VariantVector Variant::emptyVariantVector;
+const StringVector Variant::emptyStringVector;
 
 static const char* typeNames[] =
 {
@@ -61,6 +63,7 @@ static const char* typeNames[] =
     "Matrix3x4",
     "Matrix4",
     "Double",
+    "StringVector",
     0
 };
 
@@ -90,6 +93,10 @@ Variant& Variant::operator =(const Variant& rhs)
         *(reinterpret_cast<VariantVector*>(&value_)) = *(reinterpret_cast<const VariantVector*>(&rhs.value_));
         break;
 
+    case VAR_STRINGVECTOR:
+        *(reinterpret_cast<StringVector*>(&value_)) = *(reinterpret_cast<const StringVector*>(&rhs.value_));
+        break;
+
     case VAR_VARIANTMAP:
         *(reinterpret_cast<VariantMap*>(&value_)) = *(reinterpret_cast<const VariantMap*>(&rhs.value_));
         break;
@@ -118,6 +125,13 @@ Variant& Variant::operator =(const Variant& rhs)
     return *this;
 }
 
+Variant& Variant::operator =(const VectorBuffer& rhs)
+{
+    SetType(VAR_BUFFER);
+    *(reinterpret_cast<PODVector<unsigned char>*>(&value_)) = rhs.GetBuffer();
+    return *this;
+}
+
 bool Variant::operator ==(const Variant& rhs) const
 {
     if (type_ == VAR_VOIDPTR || type_ == VAR_PTR)
@@ -164,6 +178,9 @@ bool Variant::operator ==(const Variant& rhs) const
     case VAR_VARIANTVECTOR:
         return *(reinterpret_cast<const VariantVector*>(&value_)) == *(reinterpret_cast<const VariantVector*>(&rhs.value_));
 
+    case VAR_STRINGVECTOR:
+        return *(reinterpret_cast<const StringVector*>(&value_)) == *(reinterpret_cast<const StringVector*>(&rhs.value_));
+
     case VAR_VARIANTMAP:
         return *(reinterpret_cast<const VariantMap*>(&value_)) == *(reinterpret_cast<const VariantMap*>(&rhs.value_));
 
@@ -190,6 +207,23 @@ bool Variant::operator ==(const Variant& rhs) const
     }
 }
 
+bool Variant::operator ==(const PODVector<unsigned char>& rhs) const
+{
+    // Use strncmp() instead of PODVector<unsigned char>::operator ==()
+    const PODVector<unsigned char>& buffer = *(reinterpret_cast<const PODVector<unsigned char>*>(&value_));
+    return type_ == VAR_BUFFER && buffer.Size() == rhs.Size() ?
+        strncmp(reinterpret_cast<const char*>(&buffer[0]), reinterpret_cast<const char*>(&rhs[0]), buffer.Size()) == 0 :
+        false;
+}
+
+bool Variant::operator ==(const VectorBuffer& rhs) const
+{
+    const PODVector<unsigned char>& buffer = *(reinterpret_cast<const PODVector<unsigned char>*>(&value_));
+    return type_ == VAR_BUFFER && buffer.Size() == rhs.GetSize() ?
+        strncmp(reinterpret_cast<const char*>(&buffer[0]), reinterpret_cast<const char*>(rhs.GetData()), buffer.Size()) == 0 :
+        false;
+}
+
 void Variant::FromString(const String& type, const String& value)
 {
     return FromString(GetTypeFromName(type), value.CString());
@@ -260,7 +294,7 @@ void Variant::FromString(VariantType type, const char* value)
 
     case VAR_RESOURCEREF:
     {
-        Vector<String> values = String::Split(value, ';');
+        StringVector values = String::Split(value, ';');
         if (values.Size() == 2)
         {
             SetType(VAR_RESOURCEREF);
@@ -273,7 +307,7 @@ void Variant::FromString(VariantType type, const char* value)
 
     case VAR_RESOURCEREFLIST:
     {
-        Vector<String> values = String::Split(value, ';');
+        StringVector values = String::Split(value, ';');
         if (values.Size() >= 1)
         {
             SetType(VAR_RESOURCEREFLIST);
@@ -310,7 +344,7 @@ void Variant::FromString(VariantType type, const char* value)
     case VAR_MATRIX4:
         *this = ToMatrix4(value);
         break;
-        
+
     case VAR_DOUBLE:
         *this = ToDouble(value);
         break;
@@ -332,6 +366,11 @@ void Variant::SetBuffer(const void* data, unsigned size)
         memcpy(&buffer[0], data, size);
 }
 
+const VectorBuffer Variant::GetVectorBuffer() const
+{
+    return VectorBuffer(type_ == VAR_BUFFER ? *reinterpret_cast<const PODVector<unsigned char>*>(&value_) : emptyBuffer);
+}
+
 String Variant::GetTypeName() const
 {
     return typeNames[type_];
@@ -369,12 +408,12 @@ String Variant::ToString() const
         return *(reinterpret_cast<const String*>(&value_));
 
     case VAR_BUFFER:
-    {
-        const PODVector<unsigned char>& buffer = *(reinterpret_cast<const PODVector<unsigned char>*>(&value_));
-        String ret;
-        BufferToString(ret, buffer.Begin().ptr_, buffer.Size());
-        return ret;
-    }
+        {
+            const PODVector<unsigned char>& buffer = *(reinterpret_cast<const PODVector<unsigned char>*>(&value_));
+            String ret;
+            BufferToString(ret, buffer.Begin().ptr_, buffer.Size());
+            return ret;
+        }
 
     case VAR_VOIDPTR:
     case VAR_PTR:
@@ -387,12 +426,6 @@ String Variant::ToString() const
     case VAR_INTVECTOR2:
         return (reinterpret_cast<const IntVector2*>(&value_))->ToString();
 
-    default:
-        // VAR_RESOURCEREF, VAR_RESOURCEREFLIST, VAR_VARIANTVECTOR, VAR_VARIANTMAP
-        // Reference string serialization requires typehash-to-name mapping from the context. Can not support here
-        // Also variant map or vector string serialization is not supported. XML or binary save should be used instead
-        return String::EMPTY;
-
     case VAR_MATRIX3:
         return (reinterpret_cast<const Matrix3*>(value_.ptr_))->ToString();
 
@@ -404,6 +437,12 @@ String Variant::ToString() const
 
     case VAR_DOUBLE:
         return String(*reinterpret_cast<const double*>(&value_));
+
+    default:
+        // VAR_RESOURCEREF, VAR_RESOURCEREFLIST, VAR_VARIANTVECTOR, VAR_STRINGVECTOR, VAR_VARIANTMAP
+        // Reference string serialization requires typehash-to-name mapping from the context. Can not support here
+        // Also variant map or vector string serialization is not supported. XML or binary save should be used instead
+        return String::EMPTY;
     }
 }
 
@@ -450,8 +489,8 @@ bool Variant::IsZero() const
 
     case VAR_RESOURCEREFLIST:
     {
-        const Vector<String>& names = reinterpret_cast<const ResourceRefList*>(&value_)->names_;
-        for (Vector<String>::ConstIterator i = names.Begin(); i != names.End(); ++i)
+        const StringVector& names = reinterpret_cast<const ResourceRefList*>(&value_)->names_;
+        for (StringVector::ConstIterator i = names.Begin(); i != names.End(); ++i)
         {
             if (!i->Empty())
                 return false;
@@ -462,6 +501,9 @@ bool Variant::IsZero() const
     case VAR_VARIANTVECTOR:
         return reinterpret_cast<const VariantVector*>(&value_)->Empty();
 
+    case VAR_STRINGVECTOR:
+        return reinterpret_cast<const StringVector*>(&value_)->Empty();
+
     case VAR_VARIANTMAP:
         return reinterpret_cast<const VariantMap*>(&value_)->Empty();
 
@@ -482,7 +524,7 @@ bool Variant::IsZero() const
 
     case VAR_MATRIX4:
         return *reinterpret_cast<const Matrix4*>(value_.ptr_) == Matrix4::IDENTITY;
-        
+
     case VAR_DOUBLE:
         return *reinterpret_cast<const double*>(&value_) == 0.0;
 
@@ -518,6 +560,10 @@ void Variant::SetType(VariantType newType)
         (reinterpret_cast<VariantVector*>(&value_))->~VariantVector();
         break;
 
+    case VAR_STRINGVECTOR:
+        (reinterpret_cast<StringVector*>(&value_))->~StringVector();
+        break;
+
     case VAR_VARIANTMAP:
         (reinterpret_cast<VariantMap*>(&value_))->~VariantMap();
         break;
@@ -566,6 +612,10 @@ void Variant::SetType(VariantType newType)
         new(reinterpret_cast<VariantVector*>(&value_)) VariantVector();
         break;
 
+    case VAR_STRINGVECTOR:
+        new(reinterpret_cast<StringVector*>(&value_)) StringVector();
+        break;
+
     case VAR_VARIANTMAP:
         new(reinterpret_cast<VariantMap*>(&value_)) VariantMap();
         break;
@@ -706,6 +756,11 @@ template <> VariantVector Variant::Get<VariantVector>() const
     return GetVariantVector();
 }
 
+template <> StringVector Variant::Get<StringVector >() const
+{
+    return GetStringVector();
+}
+
 template <> VariantMap Variant::Get<VariantMap>() const
 {
     return GetVariantMap();

+ 120 - 23
Source/Atomic/Core/Variant.h

@@ -59,10 +59,11 @@ enum VariantType
     VAR_MATRIX3X4,
     VAR_MATRIX4,
     VAR_DOUBLE,
+    VAR_STRINGVECTOR,
     MAX_VAR_TYPES
 };
 
-/// Union for the possible variant values. Also stores non-POD objects such as String which must not exceed 16 bytes in size.
+/// Union for the possible variant values. Also stores non-POD objects such as String and math objects (excluding Matrix) which must not exceed 16 bytes in size. Objects exceeding 16 bytes size are stored in the heap pointed by _ptr.
 struct VariantValue
 {
     union
@@ -95,6 +96,18 @@ struct VariantValue
     };
 };
 
+class Variant;
+class VectorBuffer;
+
+/// Vector of variants.
+typedef Vector<Variant> VariantVector;
+
+/// Vector of strings.
+typedef Vector<String> StringVector;
+
+/// Map of variants.
+typedef HashMap<StringHash, Variant> VariantMap;
+
 /// Typed resource reference.
 struct ATOMIC_API ResourceRef
 {
@@ -150,7 +163,7 @@ struct ATOMIC_API ResourceRefList
     }
 
     /// Construct with type and id list.
-    ResourceRefList(StringHash type, const Vector<String>& names) :
+    ResourceRefList(StringHash type, const StringVector& names) :
         type_(type),
         names_(names)
     {
@@ -159,7 +172,7 @@ struct ATOMIC_API ResourceRefList
     /// Object type.
     StringHash type_;
     /// List of object names.
-    Vector<String> names_;
+    StringVector names_;
 
     /// Test for equality with another reference list.
     bool operator ==(const ResourceRefList& rhs) const { return type_ == rhs.type_ && names_ == rhs.names_; }
@@ -168,14 +181,6 @@ struct ATOMIC_API ResourceRefList
     bool operator !=(const ResourceRefList& rhs) const { return type_ != rhs.type_ || names_ != rhs.names_; }
 };
 
-class Variant;
-
-/// Vector of variants.
-typedef Vector<Variant> VariantVector;
-
-/// Map of variants.
-typedef HashMap<StringHash, Variant> VariantMap;
-
 /// Variable that supports a fixed set of types.
 class ATOMIC_API Variant
 {
@@ -284,6 +289,13 @@ public:
         *this = value;
     }
 
+    /// Construct from a %VectorBuffer and store as a buffer.
+    Variant(const VectorBuffer& value) :
+        type_(VAR_NONE)
+    {
+        *this = value;
+    }
+
     /// Construct from a pointer.
     Variant(void* value) :
         type_(VAR_NONE)
@@ -319,6 +331,13 @@ public:
         *this = value;
     }
 
+    /// Construct from a string vector.
+    Variant(const StringVector& value) :
+        type_ (VAR_NONE)
+    {
+        *this = value;
+    }
+
     /// Construct from an integer rect.
     Variant(const IntRect& value) :
         type_(VAR_NONE)
@@ -523,6 +542,9 @@ public:
         return *this;
     }
 
+    /// Assign from a %VectorBuffer and store as a buffer.
+    Variant& operator =(const VectorBuffer& rhs);
+
     /// Assign from a void pointer.
     Variant& operator =(void* rhs)
     {
@@ -555,6 +577,14 @@ public:
         return *this;
     }
 
+    // Assign from a string vector.
+    Variant& operator =(const StringVector& rhs)
+    {
+        SetType(VAR_STRINGVECTOR);
+        *(reinterpret_cast<StringVector*>(&value_)) = rhs;
+        return *this;
+    }
+
     /// Assign from a variant map.
     Variant& operator =(const VariantMap& rhs)
     {
@@ -666,10 +696,9 @@ public:
     }
 
     /// Test for equality with a buffer. To return true, both the type and value must match.
-    bool operator ==(const PODVector<unsigned char>& rhs) const
-    {
-        return type_ == VAR_BUFFER ? *(reinterpret_cast<const PODVector<unsigned char>*>(&value_)) == rhs : false;
-    }
+    bool operator ==(const PODVector<unsigned char>& rhs) const;
+    /// Test for equality with a %VectorBuffer. To return true, both the type and value must match.
+    bool operator ==(const VectorBuffer& rhs) const;
 
     /// Test for equality with a void pointer. To return true, both the type and value must match, with the exception that a RefCounted pointer is also allowed.
     bool operator ==(void* rhs) const
@@ -700,6 +729,12 @@ public:
         return type_ == VAR_VARIANTVECTOR ? *(reinterpret_cast<const VariantVector*>(&value_)) == rhs : false;
     }
 
+    /// Test for equality with a string vector. To return true, both the type and value must match.
+    bool operator ==(const StringVector& rhs) const
+    {
+        return type_ == VAR_STRINGVECTOR ? *(reinterpret_cast<const StringVector*>(&value_)) == rhs : false;
+    }
+
     /// Test for equality with a variant map. To return true, both the type and value must match.
     bool operator ==(const VariantMap& rhs) const
     {
@@ -786,6 +821,9 @@ public:
     /// Test for inequality with a buffer.
     bool operator !=(const PODVector<unsigned char>& rhs) const { return !(*this == rhs); }
 
+    /// Test for inequality with a %VectorBuffer.
+    bool operator !=(const VectorBuffer& rhs) const { return !(*this == rhs); }
+
     /// Test for inequality with a pointer.
     bool operator !=(void* rhs) const { return !(*this == rhs); }
 
@@ -798,6 +836,9 @@ public:
     /// Test for inequality with a variant vector.
     bool operator !=(const VariantVector& rhs) const { return !(*this == rhs); }
 
+    /// Test for inequality with a string vector.
+    bool operator !=(const StringVector& rhs) const { return !(*this == rhs); }
+
     /// Test for inequality with a variant map.
     bool operator !=(const VariantMap& rhs) const { return !(*this == rhs); }
 
@@ -833,11 +874,31 @@ public:
     /// Set buffer type from a memory area.
     void SetBuffer(const void* data, unsigned size);
 
-    /// Return int or zero on type mismatch.
-    int GetInt() const { return type_ == VAR_INT ? value_.int_ : 0; }
+    /// Return int or zero on type mismatch. Floats and doubles are converted.
+    int GetInt() const
+    {
+        if (type_ == VAR_INT)
+            return value_.int_;
+        else if (type_ == VAR_FLOAT)
+            return (int)value_.float_;
+        else if (type_ == VAR_DOUBLE)
+            return (int)*reinterpret_cast<const double*>(&value_);
+        else
+            return 0;
+    }
 
-    /// Return unsigned int or zero on type mismatch.
-    unsigned GetUInt() const { return type_ == VAR_INT ? (unsigned)value_.int_ : 0; }
+    /// Return unsigned int or zero on type mismatch. Floats and doubles are converted.
+    unsigned GetUInt() const
+    {
+        if (type_ == VAR_INT)
+            return value_.int_;
+        else if (type_ == VAR_FLOAT)
+            return (unsigned)value_.float_;
+        else if (type_ == VAR_DOUBLE)
+            return (unsigned)*reinterpret_cast<const double*>(&value_);
+        else
+            return 0;
+    }
 
     /// Return StringHash or zero on type mismatch.
     StringHash GetStringHash() const { return StringHash(GetUInt()); }
@@ -845,11 +906,31 @@ public:
     /// Return bool or false on type mismatch.
     bool GetBool() const { return type_ == VAR_BOOL ? value_.bool_ : false; }
 
-    /// Return float or zero on type mismatch.
-    float GetFloat() const { return type_ == VAR_FLOAT ? value_.float_ : 0.0f; }
+    /// Return float or zero on type mismatch. Ints and doubles are converted.
+    float GetFloat() const
+    {
+        if (type_ == VAR_FLOAT)
+            return value_.float_;
+        else if (type_ == VAR_DOUBLE)
+            return (float)*reinterpret_cast<const double*>(&value_);
+        else if (type_ == VAR_INT)
+            return (float)value_.int_;
+        else
+            return 0.0f;
+    }
 
-    /// Return double or zero on type mismatch.
-    double GetDouble() const { return type_ == VAR_DOUBLE ? *reinterpret_cast<const double*>(&value_) : 0.0; }
+    /// Return double or zero on type mismatch. Ints and floats are converted.
+    double GetDouble() const
+    {
+        if (type_ == VAR_DOUBLE)
+            return *reinterpret_cast<const double*>(&value_);
+        else if (type_ == VAR_FLOAT)
+            return (double)value_.float_;
+        else if (type_ == VAR_INT)
+            return (double)value_.int_;
+        else
+            return 0.0;
+    }
 
     /// Return Vector2 or zero on type mismatch.
     const Vector2& GetVector2() const { return type_ == VAR_VECTOR2 ? *reinterpret_cast<const Vector2*>(&value_) : Vector2::ZERO; }
@@ -878,6 +959,9 @@ public:
         return type_ == VAR_BUFFER ? *reinterpret_cast<const PODVector<unsigned char>*>(&value_) : emptyBuffer;
     }
 
+    /// Return %VectorBuffer containing the buffer or empty on type mismatch.
+    const VectorBuffer GetVectorBuffer() const;
+
     /// Return void pointer or null on type mismatch. RefCounted pointer will be converted.
     void* GetVoidPtr() const
     {
@@ -907,6 +991,12 @@ public:
         return type_ == VAR_VARIANTVECTOR ? *reinterpret_cast<const VariantVector*>(&value_) : emptyVariantVector;
     }
 
+    /// Return a string vector or empty on type mismatch.
+    const StringVector& GetStringVector() const
+    {
+        return type_ == VAR_STRINGVECTOR ? *reinterpret_cast<const StringVector*>(&value_) : emptyStringVector;
+    }
+
     /// Return a variant map or empty on type mismatch.
     const VariantMap& GetVariantMap() const
     {
@@ -971,6 +1061,9 @@ public:
     /// Return a pointer to a modifiable variant vector or null on type mismatch.
     VariantVector* GetVariantVectorPtr() { return type_ == VAR_VARIANTVECTOR ? reinterpret_cast<VariantVector*>(&value_) : 0; }
 
+    /// Return a pointer to a modifiable string vector or null on type mismatch.
+    StringVector* GetStringVectorPtr() { return type_ == VAR_STRINGVECTOR ? reinterpret_cast<StringVector*>(&value_) : 0; }
+
     /// Return a pointer to a modifiable variant map or null on type mismatch.
     VariantMap* GetVariantMapPtr() { return type_ == VAR_VARIANTMAP ? reinterpret_cast<VariantMap*>(&value_) : 0; }
 
@@ -993,6 +1086,8 @@ public:
     static const VariantMap emptyVariantMap;
     /// Empty variant vector.
     static const VariantVector emptyVariantVector;
+    /// Empty string vector.
+    static const StringVector emptyStringVector;
 
 private:
     /// Set new type and allocate/deallocate memory as necessary.
@@ -1040,6 +1135,8 @@ template <> inline VariantType GetVariantType<ResourceRefList>() { return VAR_RE
 
 template <> inline VariantType GetVariantType<VariantVector>() { return VAR_VARIANTVECTOR; }
 
+template <> inline VariantType GetVariantType<StringVector >() { return VAR_STRINGVECTOR; }
+
 template <> inline VariantType GetVariantType<VariantMap>() { return VAR_VARIANTMAP; }
 
 template <> inline VariantType GetVariantType<IntRect>() { return VAR_INTRECT; }

+ 5 - 1
Source/Atomic/Core/WorkQueue.cpp

@@ -67,6 +67,7 @@ WorkQueue::WorkQueue(Context* context) :
     shutDown_(false),
     pausing_(false),
     paused_(false),
+    completing_(false),
     tolerance_(10),
     lastSize_(0),
     maxNonThreadedWorkMs_(5)
@@ -234,6 +235,8 @@ void WorkQueue::Resume()
 
 void WorkQueue::Complete(unsigned priority)
 {
+    completing_ = true;
+
     if (threads_.Size())
     {
         Resume();
@@ -279,6 +282,7 @@ void WorkQueue::Complete(unsigned priority)
     }
 
     PurgeCompleted(priority);
+    completing_ = false;
 }
 
 bool WorkQueue::IsCompleted(unsigned priority) const
@@ -370,7 +374,7 @@ void WorkQueue::ReturnToPool(SharedPtr<WorkItem>& item)
     // Check if this was a pooled item and set it to usable
     if (item->pooled_)
     {
-        // Reset the values to their defaults. This should 
+        // Reset the values to their defaults. This should
         // be safe to do here as the completed event has
         // already been handled and this is part of the
         // internal pool.

+ 4 - 0
Source/Atomic/Core/WorkQueue.h

@@ -114,6 +114,8 @@ public:
 
     /// Return whether all work with at least the specified priority is finished.
     bool IsCompleted(unsigned priority) const;
+    /// Return whether the queue is currently completing work in the main thread.
+    bool IsCompleting() const { return completing_; }
 
     /// Return the pool tolerance.
     int GetTolerance() const { return tolerance_; }
@@ -149,6 +151,8 @@ private:
     volatile bool pausing_;
     /// Paused flag. Indicates the queue mutex being locked to prevent worker threads using up CPU time.
     bool paused_;
+    /// Completing work in the main thread flag.
+    bool completing_;
     /// Tolerance for the shared pool before it begins to deallocate.
     int tolerance_;
     /// Last size of the shared pool.

+ 99 - 0
Source/Atomic/Database/Database.cpp

@@ -0,0 +1,99 @@
+//
+// 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/Profiler.h"
+#include "../Database/Database.h"
+
+namespace Urho3D
+{
+
+Database::Database(Context* context_) :
+    Object(context_),
+#ifdef ODBC_3_OR_LATER
+    poolSize_(0)
+#else
+    poolSize_(M_MAX_UNSIGNED)
+#endif
+{
+}
+
+DBAPI Database::GetAPI()
+{
+#ifdef URHO3D_DATABASE_ODBC
+    return DBAPI_ODBC;
+#else
+    return DBAPI_SQLITE;
+#endif
+}
+
+DbConnection* Database::Connect(const String& connectionString)
+{
+    PROFILE(DatabaseConnect);
+
+    SharedPtr<DbConnection> connection;
+    if (IsPooling())
+    {
+        Vector<SharedPtr<DbConnection> >& connectionsPool = connectionsPool_[connectionString];
+        while (!connectionsPool.Empty())
+        {
+            connection = connectionsPool.Back();
+            connectionsPool.Pop();
+            if (connection->IsConnected())
+                break;
+            connection = 0;
+        }
+    }
+    if (!connection)
+        connection = new DbConnection(context_, connectionString);
+    if (connection->IsConnected())
+    {
+        connections_.Push(connection);
+        return connection;
+    }
+    else
+        return 0;
+}
+
+void Database::Disconnect(DbConnection* connection)
+{
+    if (!connection)
+        return;
+
+    PROFILE(DatabaseDisconnect);
+
+    SharedPtr<DbConnection> dbConnection(connection);
+    connections_.Remove(dbConnection);
+
+    // Must finalize the connection before closing the connection or returning it to the pool
+    connection->Finalize();
+
+    if (IsPooling())
+    {
+        Vector<SharedPtr<DbConnection> >& connectionsPool = connectionsPool_[connection->GetConnectionString()];
+        if (connectionsPool.Size() < poolSize_)
+            connectionsPool.Push(dbConnection);
+    }
+}
+
+}

+ 74 - 0
Source/Atomic/Database/Database.h

@@ -0,0 +1,74 @@
+//
+// 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"
+#include "../Database/DbConnection.h"
+
+namespace Urho3D
+{
+
+/// Supported database API.
+enum DBAPI
+{
+    DBAPI_SQLITE = 0,
+    DBAPI_ODBC
+};
+
+class DbConnection;
+
+/// %Database subsystem. Manage database connections.
+class URHO3D_API Database : public Object
+{
+    OBJECT(Database);
+
+public:
+    /// Construct.
+    Database(Context* context_);
+    /// Return the underlying database API.
+    static DBAPI GetAPI();
+
+    /// Create new database connection. Return 0 if failed.
+    DbConnection* Connect(const String& connectionString);
+    /// Disconnect a database connection. The connection object pointer should not be used anymore after this.
+    void Disconnect(DbConnection* connection);
+
+    /// Return true when using internal database connection pool. The internal database pool is managed by the Database subsystem itself and should not be confused with ODBC connection pool option when ODBC is being used.
+    bool IsPooling() const { return (bool)poolSize_; }
+
+    /// Get internal database connection pool size.
+    unsigned GetPoolSize() const { return poolSize_; }
+
+    /// Set internal database connection pool size.
+    void SetPoolSize(unsigned poolSize) { poolSize_ = poolSize; }
+
+private:
+    /// %Database connection pool size. Default to 0 when using ODBC 3.0 or later as ODBC 3.0 driver manager could manage its own database connection pool.
+    unsigned poolSize_;
+    /// Active database connections.
+    Vector<SharedPtr<DbConnection> > connections_;
+    ///%Database connections pool.
+    HashMap<String, Vector<SharedPtr<DbConnection> > > connectionsPool_;
+};
+
+}

+ 44 - 0
Source/Atomic/Database/DatabaseEvents.h

@@ -0,0 +1,44 @@
+//
+// 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"
+#include "../Database/DbConnection.h"
+
+namespace Urho3D
+{
+
+/// %Database cursor. Event handler could set P_FILTER to true to filter out a row from resultset and P_ABORT to true to stop further cursor events.
+EVENT(E_DBCURSOR, DbCursor)
+{
+    PARAM(P_DBCONNECTION, DbConnection);    // DbConnection pointer
+    PARAM(P_RESULTIMPL, ResultImpl);        // Underlying result object pointer (cannot be used in scripting)
+    PARAM(P_SQL, SQL);                      // String
+    PARAM(P_NUMCOLS, NumCols);              // unsigned
+    PARAM(P_COLVALUES, ColValues);          // VariantVector
+    PARAM(P_COLHEADERS, ColHeaders);        // StringVector
+    PARAM(P_FILTER, Filter);                // bool [in]
+    PARAM(P_ABORT, Abort);                  // bool [in]
+}
+
+}

+ 31 - 0
Source/Atomic/Database/DbConnection.h

@@ -0,0 +1,31 @@
+//
+// 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
+
+#ifdef URHO3D_DATABASE_ODBC
+#include "ODBC/ODBCConnection.h"
+#elif URHO3D_DATABASE_SQLITE
+#include "SQLite/SQLiteConnection.h"
+#else
+#error "Database subsystem not enabled"
+#endif

+ 31 - 0
Source/Atomic/Database/DbResult.h

@@ -0,0 +1,31 @@
+//
+// 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
+
+#ifdef URHO3D_DATABASE_ODBC
+#include "ODBC/ODBCResult.h"
+#elif URHO3D_DATABASE_SQLITE
+#include "SQLite/SQLiteResult.h"
+#else
+#error "Database subsystem not enabled"
+#endif

+ 158 - 0
Source/Atomic/Database/ODBC/ODBCConnection.cpp

@@ -0,0 +1,158 @@
+//
+// 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 "../../Database/DatabaseEvents.h"
+#include "../../IO/Log.h"
+
+#include <sqlext.h>
+
+namespace Urho3D
+{
+
+DbConnection::DbConnection(Context* context, const String& connectionString) :
+    Object(context),
+    connectionString_(connectionString)
+{
+    try
+    {
+        connectionImpl_ = nanodbc::connection(connectionString.CString());
+    }
+    catch (std::runtime_error& e)
+    {
+        HandleRuntimeError("Could not connect", e.what());
+    }
+}
+
+DbConnection::~DbConnection()
+{
+    try
+    {
+        Finalize();
+        connectionImpl_.disconnect();
+    }
+    catch (std::runtime_error& e)
+    {
+        // This should not happen after finalizing the connection, log error in Release but assert in Debug
+        HandleRuntimeError("Could not disconnect", e.what());
+        assert(false);
+    }
+}
+
+void DbConnection::Finalize()
+{
+    // TODO
+}
+
+DbResult DbConnection::Execute(const String& sql, bool useCursorEvent)
+{
+    DbResult result;
+
+    try
+    {
+        result.resultImpl_ = nanodbc::execute(connectionImpl_, sql.Trimmed().CString());
+        unsigned numCols = (unsigned)result.resultImpl_.columns();
+        if (numCols)
+        {
+            result.columns_.Resize(numCols);
+            for (unsigned i = 0; i < numCols; ++i)
+                result.columns_[i] = result.resultImpl_.column_name((short)i).c_str();
+
+            bool filtered = false;
+            bool aborted = false;
+
+            while (result.resultImpl_.next())
+            {
+                VariantVector colValues(numCols);
+                for (unsigned i = 0; i < numCols; ++i)
+                {
+                    if (!result.resultImpl_.is_null((short)i))
+                    {
+                        // We can only bind primitive data type that our Variant class supports
+                        switch (result.resultImpl_.column_c_datatype((short)i))
+                        {
+                        case SQL_C_LONG:
+                            colValues[i] = result.resultImpl_.get<int>((short)i);
+                            if (result.resultImpl_.column_datatype((short)i) == SQL_BIT)
+                                colValues[i] = colValues[i] != 0;
+                            break;
+
+                        case SQL_C_FLOAT:
+                            colValues[i] = result.resultImpl_.get<float>((short)i);
+                            break;
+
+                        case SQL_C_DOUBLE:
+                            colValues[i] = result.resultImpl_.get<double>((short)i);
+                            break;
+
+                        default:
+                            // All other types are stored using their string representation in the Variant
+                            colValues[i] = result.resultImpl_.get<nanodbc::string_type>((short)i).c_str();
+                            break;
+                        }
+                    }
+                }
+
+                if (useCursorEvent)
+                {
+                    using namespace DbCursor;
+
+                    VariantMap& eventData = GetEventDataMap();
+                    eventData[P_DBCONNECTION] = this;
+                    eventData[P_RESULTIMPL] = &result.resultImpl_;
+                    eventData[P_SQL] = sql;
+                    eventData[P_NUMCOLS] = numCols;
+                    eventData[P_COLVALUES] = colValues;
+                    eventData[P_COLHEADERS] = result.columns_;
+                    eventData[P_FILTER] = false;
+                    eventData[P_ABORT] = false;
+
+                    SendEvent(E_DBCURSOR, eventData);
+
+                    filtered = eventData[P_FILTER].GetBool();
+                    aborted = eventData[P_ABORT].GetBool();
+                }
+
+                if (!filtered)
+                    result.rows_.Push(colValues);
+                if (aborted)
+                    break;
+            }
+        }
+        result.numAffectedRows_ = numCols ? -1 : result.resultImpl_.affected_rows();
+    }
+    catch (std::runtime_error& e)
+    {
+        HandleRuntimeError("Could not execute", e.what());
+    }
+
+    return result;
+}
+
+void DbConnection::HandleRuntimeError(const char* message, const char* cause)
+{
+    StringVector tokens = (String(cause) + "::").Split(':');      // Added "::" as sentinels against unexpected cause format
+    LOGERRORF("%s: nanodbc:%s:%s", message, tokens[1].CString(), tokens[2].CString());
+}
+
+}

+ 68 - 0
Source/Atomic/Database/ODBC/ODBCConnection.h

@@ -0,0 +1,68 @@
+//
+// 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"
+#include "../../Database/DbResult.h"
+
+#include <nanodbc/nanodbc.h>
+
+namespace Urho3D
+{
+
+/// %Database connection.
+class URHO3D_API DbConnection : public Object
+{
+    OBJECT(DbConnection);
+
+public:
+    /// Construct.
+    DbConnection(Context* context, const String& connectionString);
+    /// Destruct.
+    ~DbConnection();
+    /// Finalize all prepared statements, close all BLOB handles, and finish all sqlite3_backup objects
+    void Finalize();
+
+    /// Execute an SQL statements immediately. Send E_DBCURSOR event for each row in the resultset when useCursorEvent parameter is set to true.
+    DbResult Execute(const String& sql, bool useCursorEvent = false);
+
+    /// Return database connection string. The connection string for SQLite3 is using the URI format described in https://www.sqlite.org/uri.html, while the connection string for ODBC is using DSN format as per ODBC standard.
+    const String& GetConnectionString() const { return connectionString_; }
+
+    /// Return the underlying implementation connection object pointer. It is sqlite* when using SQLite3 or nanodbc::connection* when using ODBC.
+    const nanodbc::connection* GetConnectionImpl() const { return &connectionImpl_; }
+
+    /// Return true when the connection object is connected to the associated database.
+    bool IsConnected() const { return connectionImpl_.connected(); }
+
+private:
+    /// Internal helper method to handle runtime exception by logging it to stderr stream.
+    void HandleRuntimeError(const char* message, const char* cause);
+
+    /// The connection string for SQLite3 is using the URI format described in https://www.sqlite.org/uri.html, while the connection string for ODBC is using DSN format as per ODBC standard.
+    String connectionString_;
+    /// The underlying implementation connection object.
+    nanodbc::connection connectionImpl_;
+};
+
+}

+ 73 - 0
Source/Atomic/Database/ODBC/ODBCResult.h

@@ -0,0 +1,73 @@
+//
+// 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/Variant.h"
+
+#include <nanodbc/nanodbc.h>
+
+namespace Urho3D
+{
+
+/// %Database query result.
+class URHO3D_API DbResult
+{
+    friend class DbConnection;
+
+public:
+    // Default constructor constructs an empty result object.
+    DbResult() :
+        numAffectedRows_(-1)
+    {
+    }
+
+    /// Return number of columns in the resultset or 0 if there is no resultset.
+    unsigned GetNumColumns() const { return columns_.Size(); }
+
+    /// Return number of rows in the resultset or 0 if the number of rows is not available.
+    unsigned GetNumRows() const { return rows_.Size(); }
+
+    /// Return number of affected rows by the DML query or -1 if the number of affected rows is not available.
+    long GetNumAffectedRows() const { return numAffectedRows_; }
+
+    /// Return the underlying implementation result object.
+    const nanodbc::result& GetResultImpl() const { return resultImpl_; }
+
+    /// Return the column headers string collection.
+    const StringVector& GetColumns() const { return columns_; }
+
+    /// Return fetched rows collection. Filtered rows are not included in the collection.
+    const Vector<VariantVector>& GetRows() const { return rows_; }
+
+private:
+    /// The underlying implementation connection object.
+    nanodbc::result resultImpl_;
+    /// Column headers from the resultset.
+    StringVector columns_;
+    /// Fetched rows from the resultset.
+    Vector<VariantVector> rows_;
+    /// Number of affected rows by recent DML query.
+    long numAffectedRows_;
+};
+
+}

+ 162 - 0
Source/Atomic/Database/SQLite/SQLiteConnection.cpp

@@ -0,0 +1,162 @@
+//
+// 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 "../../Database/DatabaseEvents.h"
+#include "../../IO/Log.h"
+
+namespace Urho3D
+{
+
+DbConnection::DbConnection(Context* context, const String& connectionString) :
+    Object(context),
+    connectionString_(connectionString),
+    connectionImpl_(0)
+{
+    if (sqlite3_open(connectionString.CString(), &connectionImpl_) != SQLITE_OK)
+    {
+        LOGERRORF("Could not connect: %s", sqlite3_errmsg(connectionImpl_));
+        sqlite3_close(connectionImpl_);
+        connectionImpl_ = 0;
+    }
+}
+
+DbConnection::~DbConnection()
+{
+    Finalize();
+    if (sqlite3_close(connectionImpl_) != SQLITE_OK)
+    {
+        // This should not happen after finalizing the connection, log error in Release but assert in Debug
+        LOGERRORF("Could not disconnect: %s", sqlite3_errmsg(connectionImpl_));
+        assert(false);
+    }
+    connectionImpl_ = 0;
+}
+
+void DbConnection::Finalize()
+{
+    // TODO
+}
+
+DbResult DbConnection::Execute(const String& sql, bool useCursorEvent)
+{
+    DbResult result;
+    const char* zLeftover = 0;
+    sqlite3_stmt* pStmt = 0;
+    assert(connectionImpl_);
+    int rc = sqlite3_prepare_v2(connectionImpl_, sql.Trimmed().CString(), -1, &pStmt, &zLeftover);
+    if (rc != SQLITE_OK)
+    {
+        LOGERRORF("Could not execute: %s", sqlite3_errmsg(connectionImpl_));
+        assert(!pStmt);
+        return result;
+    }
+    if (*zLeftover)
+    {
+        LOGERROR("Could not execute: only one SQL statement is allowed");
+        sqlite3_finalize(pStmt);
+        return result;
+    }
+
+    unsigned numCols = (unsigned)sqlite3_column_count(pStmt);
+    result.columns_.Resize(numCols);
+    for (unsigned i = 0; i < numCols; ++i)
+        result.columns_[i] = sqlite3_column_name(pStmt, i);
+
+    bool filtered = false;
+    bool aborted = false;
+
+    while (1)
+    {
+        rc = sqlite3_step(pStmt);
+        if (rc == SQLITE_ROW)
+        {
+            VariantVector colValues(numCols);
+            for (unsigned i = 0; i < numCols; ++i)
+            {
+                int type = sqlite3_column_type(pStmt, i);
+                if (type != SQLITE_NULL)
+                {
+                    // We can only bind primitive data type that our Variant class supports
+                    switch (type)
+                    {
+                    case SQLITE_INTEGER:
+                        colValues[i] = sqlite3_column_int(pStmt, i);
+                        if (String(sqlite3_column_decltype(pStmt, i)).Compare("BOOLEAN", false) == 0)
+                            colValues[i] = colValues[i] != 0;
+                        break;
+
+                    case SQLITE_FLOAT:
+                        colValues[i] = sqlite3_column_double(pStmt, i);
+                        break;
+
+                    default:
+                        // All other types are stored using their string representation in the Variant
+                        colValues[i] = (const char*)sqlite3_column_text(pStmt, i);
+                        break;
+                    }
+                }
+            }
+
+            if (useCursorEvent)
+            {
+                using namespace DbCursor;
+
+                VariantMap& eventData = GetEventDataMap();
+                eventData[P_DBCONNECTION] = this;
+                eventData[P_RESULTIMPL] = pStmt;
+                eventData[P_SQL] = sql;
+                eventData[P_NUMCOLS] = numCols;
+                eventData[P_COLVALUES] = colValues;
+                eventData[P_COLHEADERS] = result.columns_;
+                eventData[P_FILTER] = false;
+                eventData[P_ABORT] = false;
+
+                SendEvent(E_DBCURSOR, eventData);
+
+                filtered = eventData[P_FILTER].GetBool();
+                aborted = eventData[P_ABORT].GetBool();
+            }
+
+            if (!filtered)
+                result.rows_.Push(colValues);
+            if (aborted)
+            {
+                sqlite3_finalize(pStmt);
+                break;
+            }
+        }
+        else if (rc != SQLITE_DONE)
+            LOGERRORF("Could not execute: %s", sqlite3_errmsg(connectionImpl_));
+        if (rc != SQLITE_ROW)
+        {
+            sqlite3_finalize(pStmt);
+            break;
+        }
+    }
+
+    result.numAffectedRows_ = numCols ? -1 : sqlite3_changes(connectionImpl_);
+    return result;
+}
+
+}

+ 65 - 0
Source/Atomic/Database/SQLite/SQLiteConnection.h

@@ -0,0 +1,65 @@
+//
+// 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"
+#include "../../Database/DbResult.h"
+
+#include <SQLite/sqlite3.h>
+
+namespace Urho3D
+{
+
+/// %Database connection.
+class URHO3D_API DbConnection : public Object
+{
+    OBJECT(DbConnection);
+
+public:
+    /// Construct.
+    DbConnection(Context* context, const String& connectionString);
+    /// Destruct.
+    ~DbConnection();
+    /// Finalize all prepared statements, close all BLOB handles, and finish all sqlite3_backup objects
+    void Finalize();
+
+    /// Execute an SQL statements immediately. Send E_DBCURSOR event for each row in the resultset when useCursorEvent parameter is set to true.
+    DbResult Execute(const String& sql, bool useCursorEvent = false);
+
+    /// Return database connection string. The connection string for SQLite3 is using the URI format described in https://www.sqlite.org/uri.html, while the connection string for ODBC is using DSN format as per ODBC standard.
+    const String& GetConnectionString() const { return connectionString_; }
+
+    /// Return the underlying implementation connection object pointer. It is sqlite* when using SQLite3 or nanodbc::connection* when using ODBC.
+    const sqlite3* GetConnectionImpl() const { return connectionImpl_; }
+
+    /// Return true when the connection object is connected to the associated database.
+    bool IsConnected() const { return connectionImpl_ != 0; }
+
+private:
+    /// The connection string for SQLite3 is using the URI format described in https://www.sqlite.org/uri.html, while the connection string for ODBC is using DSN format as per ODBC standard.
+    String connectionString_;
+    /// The underlying implementation connection object.
+    sqlite3* connectionImpl_;
+};
+
+}

+ 68 - 0
Source/Atomic/Database/SQLite/SQLiteResult.h

@@ -0,0 +1,68 @@
+//
+// 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/Variant.h"
+
+#include <SQLite/sqlite3.h>
+
+namespace Urho3D
+{
+
+/// %Database query result.
+class URHO3D_API DbResult
+{
+    friend class DbConnection;
+
+public:
+    // Default constructor constructs an empty result object.
+    DbResult() :
+        numAffectedRows_(-1)
+    {
+    }
+
+    /// Return number of columns in the resultset or 0 if there is no resultset.
+    unsigned GetNumColumns() const { return columns_.Size(); }
+
+    /// Return number of rows in the resultset or 0 if the number of rows is not available.
+    unsigned GetNumRows() const { return rows_.Size(); }
+
+    /// Return number of affected rows by the DML query or -1 if the number of affected rows is not available.
+    long GetNumAffectedRows() const { return numAffectedRows_; }
+
+    /// Return the column headers string collection.
+    const StringVector& GetColumns() const { return columns_; }
+
+    /// Return fetched rows collection. Filtered rows are not included in the collection.
+    const Vector<VariantVector>& GetRows() const { return rows_; }
+
+private:
+    /// Column headers from the resultset.
+    StringVector columns_;
+    /// Fetched rows from the resultset.
+    Vector<VariantVector> rows_;
+    /// Number of affected rows by recent DML query.
+    long numAffectedRows_;
+};
+
+}

+ 0 - 3
Source/Atomic/Engine/Application.cpp

@@ -111,13 +111,10 @@ void Application::ErrorExit(const String& message)
     engine_->Exit(); // Close the rendering window
     exitCode_ = EXIT_FAILURE;
 
-    // Only for WIN32, otherwise the error messages would be double posted on Mac OS X and Linux platforms
     if (!message.Length())
     {
-#ifdef WIN32
         ErrorDialog(GetTypeName(), startupErrors_.Length() ? startupErrors_ :
             "Application has been terminated due to unexpected error.");
-#endif
     }
     else
         ErrorDialog(GetTypeName(), message);

+ 9 - 0
Source/Atomic/Engine/Engine.cpp

@@ -41,11 +41,15 @@
 #ifdef ATOMIC_NETWORK
 #include "../Network/Network.h"
 #endif
+#ifdef ATOMIC_DATABASE
+#include "../Database/Database.h"
+#endif
 #ifdef ATOMIC_PHYSICS
 #include "../Physics/PhysicsWorld.h"
 #endif
 #include "../Resource/XMLFile.h"
 #include "../Resource/ResourceCache.h"
+#include "../Resource/Localization.h"
 #include "../Scene/Scene.h"
 #include "../Scene/SceneEvents.h"
 #include "../UI/UI.h"
@@ -56,6 +60,7 @@
 #include "../Atomic3D/Atomic3D.h"
 #endif
 
+
 #if defined(EMSCRIPTEN) && defined(ATOMIC_TESTING)
 #include <emscripten.h>
 #endif
@@ -121,8 +126,12 @@ Engine::Engine(Context* context) :
     context_->RegisterSubsystem(new Log(context_));
 #endif
     context_->RegisterSubsystem(new ResourceCache(context_));
+    context_->RegisterSubsystem(new Localization(context_));
 #ifdef ATOMIC_NETWORK
     context_->RegisterSubsystem(new Network(context_));
+#endif
+#ifdef ATOMIC_DATABASE
+    context_->RegisterSubsystem(new Database(context_));
 #endif
     context_->RegisterSubsystem(new Input(context_));
     context_->RegisterSubsystem(new Audio(context_));

+ 17 - 5
Source/Atomic/Graphics/Batch.cpp

@@ -42,7 +42,9 @@ namespace Atomic
 
 inline bool CompareBatchesState(Batch* lhs, Batch* rhs)
 {
-    if (lhs->sortKey_ != rhs->sortKey_)
+    if (lhs->renderOrder_ != rhs->renderOrder_)
+        return lhs->renderOrder_ < rhs->renderOrder_;
+    else if (lhs->sortKey_ != rhs->sortKey_)
         return lhs->sortKey_ < rhs->sortKey_;
     else
         return lhs->distance_ < rhs->distance_;
@@ -50,7 +52,9 @@ inline bool CompareBatchesState(Batch* lhs, Batch* rhs)
 
 inline bool CompareBatchesFrontToBack(Batch* lhs, Batch* rhs)
 {
-    if (lhs->distance_ != rhs->distance_)
+    if (lhs->renderOrder_ != rhs->renderOrder_)
+        return lhs->renderOrder_ < rhs->renderOrder_;
+    else if (lhs->distance_ != rhs->distance_)
         return lhs->distance_ < rhs->distance_;
     else
         return lhs->sortKey_ < rhs->sortKey_;
@@ -58,7 +62,9 @@ inline bool CompareBatchesFrontToBack(Batch* lhs, Batch* rhs)
 
 inline bool CompareBatchesBackToFront(Batch* lhs, Batch* rhs)
 {
-    if (lhs->distance_ != rhs->distance_)
+    if (lhs->renderOrder_ != rhs->renderOrder_)
+        return lhs->renderOrder_ < rhs->renderOrder_;
+    else if (lhs->distance_ != rhs->distance_)
         return lhs->distance_ > rhs->distance_;
     else
         return lhs->sortKey_ < rhs->sortKey_;
@@ -69,6 +75,11 @@ inline bool CompareInstancesFrontToBack(const InstanceData& lhs, const InstanceD
     return lhs.distance_ < rhs.distance_;
 }
 
+inline bool CompareBatchGroupOrder(BatchGroup* lhs, BatchGroup* rhs)
+{
+    return lhs->renderOrder_ < rhs->renderOrder_;
+}
+
 void CalculateShadowMatrix(Matrix4& dest, LightBatchQueue* queue, unsigned split, Renderer* renderer, const Vector3& translation)
 {
     Camera* shadowCamera = queue->shadowSplits_[split].shadowCamera_;
@@ -669,7 +680,7 @@ void BatchGroup::Draw(View* view, bool allowDepthWrite) const
 unsigned BatchGroupKey::ToHash() const
 {
     return (unsigned)((size_t)zone_ / sizeof(Zone) + (size_t)lightQueue_ / sizeof(LightBatchQueue) + (size_t)pass_ / sizeof(Pass) +
-                      (size_t)material_ / sizeof(Material) + (size_t)geometry_ / sizeof(Geometry));
+                      (size_t)material_ / sizeof(Material) + (size_t)geometry_ / sizeof(Geometry)) + renderOrder_;
 }
 
 void BatchQueue::Clear(int maxSortedInstances)
@@ -689,12 +700,13 @@ void BatchQueue::SortBackToFront()
 
     Sort(sortedBatches_.Begin(), sortedBatches_.End(), CompareBatchesBackToFront);
 
-    // Do not actually sort batch groups, just list them
     sortedBatchGroups_.Resize(batchGroups_.Size());
 
     unsigned index = 0;
     for (HashMap<BatchGroupKey, BatchGroup>::Iterator i = batchGroups_.Begin(); i != batchGroups_.End(); ++i)
         sortedBatchGroups_[index++] = &i->second_;
+
+    Sort(sortedBatchGroups_.Begin(), sortedBatchGroups_.End(), CompareBatchGroupOrder);
 }
 
 void BatchQueue::SortFrontToBack()

+ 18 - 12
Source/Atomic/Graphics/Batch.h

@@ -24,6 +24,7 @@
 
 #include "../Container/Ptr.h"
 #include "../Graphics/Drawable.h"
+#include "../Graphics/Material.h"
 #include "../Math/MathDefs.h"
 #include "../Math/Matrix3x4.h"
 #include "../Math/Rect.h"
@@ -50,21 +51,22 @@ struct Batch
 {
     /// Construct with defaults.
     Batch() :
-        lightQueue_(0),
-        isBase_(false)
+        isBase_(false),
+        lightQueue_(0)
     {
     }
 
     /// Construct from a drawable's source batch.
     Batch(const SourceBatch& rhs) :
         distance_(rhs.distance_),
+        renderOrder_(rhs.material_ ? rhs.material_->GetRenderOrder() : DEFAULT_RENDER_ORDER),
+        isBase_(false),
         geometry_(rhs.geometry_),
         material_(rhs.material_),
         worldTransform_(rhs.worldTransform_),
         numWorldTransforms_(rhs.numWorldTransforms_),
         lightQueue_(0),
-        geometryType_(rhs.geometryType_),
-        isBase_(false)
+        geometryType_(rhs.geometryType_)
     {
     }
 
@@ -74,11 +76,16 @@ struct Batch
     void Prepare(View* view, bool setModelTransform, bool allowDepthWrite) const;
     /// Prepare and draw.
     void Draw(View* view, bool allowDepthWrite) const;
-
     /// State sorting key.
     unsigned long long sortKey_;
     /// Distance from camera.
     float distance_;
+    /// 8-bit render order modifier from material.
+    unsigned char renderOrder_;
+    /// 8-bit light mask for stencil marking in deferred rendering.
+    unsigned char lightMask_;
+   /// Base batch flag. This tells to draw the object fully without light optimizations.
+    bool isBase_;
     /// Geometry.
     Geometry* geometry_;
     /// Material.
@@ -101,10 +108,6 @@ struct Batch
     ShaderVariation* pixelShader_;
     /// %Geometry type.
     GeometryType geometryType_;
-    /// Base batch flag. This tells to draw the object fully without light optimizations.
-    bool isBase_;
-    /// 8-bit light mask for stencil marking in deferred rendering.
-    unsigned char lightMask_;
 };
 
 /// Data for one geometry instance.
@@ -187,7 +190,8 @@ struct BatchGroupKey
         lightQueue_(batch.lightQueue_),
         pass_(batch.pass_),
         material_(batch.material_),
-        geometry_(batch.geometry_)
+        geometry_(batch.geometry_),
+        renderOrder_(batch.renderOrder_)
     {
     }
 
@@ -201,19 +205,21 @@ struct BatchGroupKey
     Material* material_;
     /// Geometry.
     Geometry* geometry_;
+    /// 8-bit render order modifier from material.
+    unsigned char renderOrder_;
 
     /// Test for equality with another batch group key.
     bool operator ==(const BatchGroupKey& rhs) const
     {
         return zone_ == rhs.zone_ && lightQueue_ == rhs.lightQueue_ && pass_ == rhs.pass_ && material_ == rhs.material_ &&
-               geometry_ == rhs.geometry_;
+               geometry_ == rhs.geometry_ && renderOrder_ == rhs.renderOrder_;
     }
 
     /// Test for inequality with another batch group key.
     bool operator !=(const BatchGroupKey& rhs) const
     {
         return zone_ != rhs.zone_ || lightQueue_ != rhs.lightQueue_ || pass_ != rhs.pass_ || material_ != rhs.material_ ||
-               geometry_ != rhs.geometry_;
+               geometry_ != rhs.geometry_ || renderOrder_ != rhs.renderOrder_;
     }
 
     /// Return hash value.

+ 4 - 0
Source/Atomic/Graphics/Drawable.cpp

@@ -35,6 +35,10 @@
 
 #include "../DebugNew.h"
 
+#ifdef _MSC_VER
+#pragma warning(disable:6293)
+#endif
+
 namespace Atomic
 {
 

+ 15 - 0
Source/Atomic/Graphics/Material.cpp

@@ -390,6 +390,10 @@ bool Material::Load(const XMLElement& source)
     if (depthBiasElem)
         SetDepthBias(BiasParameters(depthBiasElem.GetFloat("constant"), depthBiasElem.GetFloat("slopescaled")));
 
+    XMLElement renderOrderElem = source.GetChild("renderorder");
+    if (renderOrderElem)
+        SetRenderOrder((unsigned char)renderOrderElem.GetUInt("value"));
+
     RefreshShaderParameterHash();
     RefreshMemoryUse();
     CheckOcclusion();
@@ -468,6 +472,10 @@ bool Material::Save(XMLElement& dest) const
     depthBiasElem.SetFloat("constant", depthBias_.constantBias_);
     depthBiasElem.SetFloat("slopescaled", depthBias_.slopeScaledBias_);
 
+    // Write render order
+    XMLElement renderOrderElem = dest.CreateChild("renderorder");
+    renderOrderElem.SetUInt("value", renderOrder_);
+
     return true;
 }
 
@@ -632,6 +640,11 @@ void Material::SetDepthBias(const BiasParameters& parameters)
     depthBias_.Validate();
 }
 
+void Material::SetRenderOrder(unsigned char order)
+{
+    renderOrder_ = order;
+}
+
 void Material::SetScene(Scene* scene)
 {
     UnsubscribeFromEvent(E_UPDATE);
@@ -676,6 +689,7 @@ SharedPtr<Material> Material::Clone(const String& cloneName) const
     ret->cullMode_ = cullMode_;
     ret->shadowCullMode_ = shadowCullMode_;
     ret->fillMode_ = fillMode_;
+    ret->renderOrder_ = renderOrder_;
     ret->RefreshMemoryUse();
 
     return ret;
@@ -797,6 +811,7 @@ void Material::ResetToDefaults()
     shadowCullMode_ = CULL_CCW;
     fillMode_ = FILL_SOLID;
     depthBias_ = BiasParameters(0.0f, 0.0f);
+    renderOrder_ = DEFAULT_RENDER_ORDER;
 
     RefreshShaderParameterHash();
     RefreshMemoryUse();

+ 9 - 0
Source/Atomic/Graphics/Material.h

@@ -40,6 +40,8 @@ class Texture2D;
 class TextureCube;
 class ValueAnimationInfo;
 
+static const unsigned char DEFAULT_RENDER_ORDER = 128;
+
 /// %Material's shader parameter definition.
 struct MaterialShaderParameter
 {
@@ -150,6 +152,8 @@ public:
     void SetFillMode(FillMode mode);
     /// Set depth bias.
     void SetDepthBias(const BiasParameters& parameters);
+    /// Set 8-bit render order within pass. Default 128. Lower values will render earlier and higher values later, taking precedence over e.g. state and distance sorting.
+    void SetRenderOrder(unsigned char order);
     /// Associate the material with a scene to ensure that shader parameter animation happens in sync with scene update, respecting the scene time scale. If no scene is set, the global update events will be used.
     void SetScene(Scene* scene);
     /// Remove shader parameter.
@@ -205,6 +209,9 @@ public:
     /// Return depth bias.
     const BiasParameters& GetDepthBias() const { return depthBias_; }
 
+    /// Return render order.
+    unsigned char GetRenderOrder() const { return renderOrder_; }
+
     /// Return last auxiliary view rendered frame number.
     unsigned GetAuxViewFrameNumber() const { return auxViewFrameNumber_; }
 
@@ -257,6 +264,8 @@ private:
     FillMode fillMode_;
     /// Depth bias parameters.
     BiasParameters depthBias_;
+    /// Render order value.
+    unsigned char renderOrder_;
     /// Last auxiliary view rendered frame number.
     unsigned auxViewFrameNumber_;
     /// Shader parameter hash value.

+ 2 - 1
Source/Atomic/Graphics/OcclusionBuffer.cpp

@@ -147,9 +147,10 @@ void OcclusionBuffer::Clear()
 
     int* dest = buffer_;
     int count = width_ * height_;
+    int fillValue = (int)OCCLUSION_Z_SCALE;
 
     while (count--)
-        *dest++ = 0x7fffffff;
+        *dest++ = fillValue;
 
     depthHierarchyDirty_ = true;
 }

+ 3 - 2
Source/Atomic/Graphics/Octree.cpp

@@ -25,6 +25,7 @@
 #include "../Core/Context.h"
 #include "../Core/CoreEvents.h"
 #include "../Core/Profiler.h"
+#include "../Core/Thread.h"
 #include "../Core/WorkQueue.h"
 #include "../Graphics/DebugRenderer.h"
 #include "../Graphics/Graphics.h"
@@ -514,8 +515,8 @@ void Octree::Raycast(RayOctreeQuery& query) const
 
     WorkQueue* queue = GetSubsystem<WorkQueue>();
 
-    // If no worker threads or no triangle-level testing, do not create work items
-    if (!queue->GetNumThreads() || query.level_ < RAY_TRIANGLE)
+    // If no worker threads or no triangle-level testing, or we are being called from a worker thread do not create work items
+    if (query.level_ < RAY_TRIANGLE || !queue->GetNumThreads() || !Thread::IsMainThread() || queue->IsCompleting())
         GetDrawablesInternal(query);
     else
     {

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

@@ -363,7 +363,7 @@ bool Graphics::SetMode(int width, int height, bool fullscreen, bool borderless,
         return true;
     }
 
-    // If zero dimensions in windowed mode, set windowed mode to maximize and set a predefined default restored window size. 
+    // If zero dimensions in windowed mode, set windowed mode to maximize and set a predefined default restored window size.
     // If zero in fullscreen, use desktop mode
     if (!width || !height)
     {
@@ -2036,16 +2036,16 @@ unsigned Graphics::GetFormat(CompressedFormat format) const
 #ifdef GL_ES_VERSION_2_0
     case CF_ETC1:
         return etcTextureSupport_ ? GL_ETC1_RGB8_OES : 0;
-        
+
     case CF_PVRTC_RGB_2BPP:
         return pvrtcTextureSupport_ ? COMPRESSED_RGB_PVRTC_2BPPV1_IMG : 0;
-        
+
     case CF_PVRTC_RGB_4BPP:
         return pvrtcTextureSupport_ ? COMPRESSED_RGB_PVRTC_4BPPV1_IMG : 0;
-        
+
     case CF_PVRTC_RGBA_2BPP:
         return pvrtcTextureSupport_ ? COMPRESSED_RGBA_PVRTC_2BPPV1_IMG : 0;
-        
+
     case CF_PVRTC_RGBA_4BPP:
         return pvrtcTextureSupport_ ? COMPRESSED_RGBA_PVRTC_4BPPV1_IMG : 0;
 #endif

+ 1 - 1
Source/Atomic/Graphics/OpenGL/OGLGraphics.h

@@ -172,7 +172,7 @@ public:
     bool NeedParameterUpdate(ShaderParameterGroup group, const void* source);
     /// Check whether a shader parameter exists on the currently set shaders.
     bool HasShaderParameter(StringHash param);
-    /// Check whether the current pixel shader uses a texture unit.
+    /// Check whether the current shader program uses a texture unit.
     bool HasTextureUnit(TextureUnit unit);
     /// Clear remembered shader parameter source group.
     void ClearParameterSource(ShaderParameterGroup group);

+ 68 - 54
Source/Atomic/Graphics/OpenGL/OGLTextureCube.cpp

@@ -122,8 +122,6 @@ bool TextureCube::BeginLoad(Deserializer& source)
         if (GetPath(name).Empty())
             name = texPath + name;
 
-        CubeMapLayout layout =
-            (CubeMapLayout)GetStringListIndex(imageElem.GetAttribute("layout").CString(), cubeMapLayoutNames, CML_HORIZONTAL);
         SharedPtr<Image> image = cache->GetTempResource<Image>(name);
         if (!image)
             return false;
@@ -131,63 +129,79 @@ bool TextureCube::BeginLoad(Deserializer& source)
         int faceWidth, faceHeight;
         loadImages_.Resize(MAX_CUBEMAP_FACES);
 
-        switch (layout)
+        if (image->IsCubemap())
+        {
+            loadImages_[FACE_POSITIVE_X] = image;
+            loadImages_[FACE_NEGATIVE_X] = loadImages_[FACE_POSITIVE_X]->GetNextSibling();
+            loadImages_[FACE_POSITIVE_Y] = loadImages_[FACE_NEGATIVE_X]->GetNextSibling();
+            loadImages_[FACE_NEGATIVE_Y] = loadImages_[FACE_POSITIVE_Y]->GetNextSibling();
+            loadImages_[FACE_POSITIVE_Z] = loadImages_[FACE_NEGATIVE_Y]->GetNextSibling();
+            loadImages_[FACE_NEGATIVE_Z] = loadImages_[FACE_POSITIVE_Z]->GetNextSibling();
+        }
+        else
         {
-        case CML_HORIZONTAL:
-            faceWidth = image->GetWidth() / MAX_CUBEMAP_FACES;
-            faceHeight = image->GetHeight();
-            loadImages_[FACE_POSITIVE_Z] = GetTileImage(image, 0, 0, faceWidth, faceHeight);
-            loadImages_[FACE_POSITIVE_X] = GetTileImage(image, 1, 0, faceWidth, faceHeight);
-            loadImages_[FACE_NEGATIVE_Z] = GetTileImage(image, 2, 0, faceWidth, faceHeight);
-            loadImages_[FACE_NEGATIVE_X] = GetTileImage(image, 3, 0, faceWidth, faceHeight);
-            loadImages_[FACE_POSITIVE_Y] = GetTileImage(image, 4, 0, faceWidth, faceHeight);
-            loadImages_[FACE_NEGATIVE_Y] = GetTileImage(image, 5, 0, faceWidth, faceHeight);
-            break;
-
-        case CML_HORIZONTALNVIDIA:
-            faceWidth = image->GetWidth() / MAX_CUBEMAP_FACES;
-            faceHeight = image->GetHeight();
-            for (unsigned i = 0; i < MAX_CUBEMAP_FACES; ++i)
-                loadImages_[i] = GetTileImage(image, i, 0, faceWidth, faceHeight);
-            break;
 
-        case CML_HORIZONTALCROSS:
-            faceWidth = image->GetWidth() / 4;
-            faceHeight = image->GetHeight() / 3;
-            loadImages_[FACE_POSITIVE_Y] = GetTileImage(image, 1, 0, faceWidth, faceHeight);
-            loadImages_[FACE_NEGATIVE_X] = GetTileImage(image, 0, 1, faceWidth, faceHeight);
-            loadImages_[FACE_POSITIVE_Z] = GetTileImage(image, 1, 1, faceWidth, faceHeight);
-            loadImages_[FACE_POSITIVE_X] = GetTileImage(image, 2, 1, faceWidth, faceHeight);
-            loadImages_[FACE_NEGATIVE_Z] = GetTileImage(image, 3, 1, faceWidth, faceHeight);
-            loadImages_[FACE_NEGATIVE_Y] = GetTileImage(image, 1, 2, faceWidth, faceHeight);
-            break;
+            CubeMapLayout layout =
+                (CubeMapLayout)GetStringListIndex(imageElem.GetAttribute("layout").CString(), cubeMapLayoutNames, CML_HORIZONTAL);
 
-        case CML_VERTICALCROSS:
-            faceWidth = image->GetWidth() / 3;
-            faceHeight = image->GetHeight() / 4;
-            loadImages_[FACE_POSITIVE_Y] = GetTileImage(image, 1, 0, faceWidth, faceHeight);
-            loadImages_[FACE_NEGATIVE_X] = GetTileImage(image, 0, 1, faceWidth, faceHeight);
-            loadImages_[FACE_POSITIVE_Z] = GetTileImage(image, 1, 1, faceWidth, faceHeight);
-            loadImages_[FACE_POSITIVE_X] = GetTileImage(image, 2, 1, faceWidth, faceHeight);
-            loadImages_[FACE_NEGATIVE_Y] = GetTileImage(image, 1, 2, faceWidth, faceHeight);
-            loadImages_[FACE_NEGATIVE_Z] = GetTileImage(image, 1, 3, faceWidth, faceHeight);
-            if (loadImages_[FACE_NEGATIVE_Z])
+            switch (layout)
             {
-                loadImages_[FACE_NEGATIVE_Z]->FlipVertical();
-                loadImages_[FACE_NEGATIVE_Z]->FlipHorizontal();
+            case CML_HORIZONTAL:
+                faceWidth = image->GetWidth() / MAX_CUBEMAP_FACES;
+                faceHeight = image->GetHeight();
+                loadImages_[FACE_POSITIVE_Z] = GetTileImage(image, 0, 0, faceWidth, faceHeight);
+                loadImages_[FACE_POSITIVE_X] = GetTileImage(image, 1, 0, faceWidth, faceHeight);
+                loadImages_[FACE_NEGATIVE_Z] = GetTileImage(image, 2, 0, faceWidth, faceHeight);
+                loadImages_[FACE_NEGATIVE_X] = GetTileImage(image, 3, 0, faceWidth, faceHeight);
+                loadImages_[FACE_POSITIVE_Y] = GetTileImage(image, 4, 0, faceWidth, faceHeight);
+                loadImages_[FACE_NEGATIVE_Y] = GetTileImage(image, 5, 0, faceWidth, faceHeight);
+                break;
+
+            case CML_HORIZONTALNVIDIA:
+                faceWidth = image->GetWidth() / MAX_CUBEMAP_FACES;
+                faceHeight = image->GetHeight();
+                for (unsigned i = 0; i < MAX_CUBEMAP_FACES; ++i)
+                    loadImages_[i] = GetTileImage(image, i, 0, faceWidth, faceHeight);
+                break;
+
+            case CML_HORIZONTALCROSS:
+                faceWidth = image->GetWidth() / 4;
+                faceHeight = image->GetHeight() / 3;
+                loadImages_[FACE_POSITIVE_Y] = GetTileImage(image, 1, 0, faceWidth, faceHeight);
+                loadImages_[FACE_NEGATIVE_X] = GetTileImage(image, 0, 1, faceWidth, faceHeight);
+                loadImages_[FACE_POSITIVE_Z] = GetTileImage(image, 1, 1, faceWidth, faceHeight);
+                loadImages_[FACE_POSITIVE_X] = GetTileImage(image, 2, 1, faceWidth, faceHeight);
+                loadImages_[FACE_NEGATIVE_Z] = GetTileImage(image, 3, 1, faceWidth, faceHeight);
+                loadImages_[FACE_NEGATIVE_Y] = GetTileImage(image, 1, 2, faceWidth, faceHeight);
+                break;
+
+            case CML_VERTICALCROSS:
+                faceWidth = image->GetWidth() / 3;
+                faceHeight = image->GetHeight() / 4;
+                loadImages_[FACE_POSITIVE_Y] = GetTileImage(image, 1, 0, faceWidth, faceHeight);
+                loadImages_[FACE_NEGATIVE_X] = GetTileImage(image, 0, 1, faceWidth, faceHeight);
+                loadImages_[FACE_POSITIVE_Z] = GetTileImage(image, 1, 1, faceWidth, faceHeight);
+                loadImages_[FACE_POSITIVE_X] = GetTileImage(image, 2, 1, faceWidth, faceHeight);
+                loadImages_[FACE_NEGATIVE_Y] = GetTileImage(image, 1, 2, faceWidth, faceHeight);
+                loadImages_[FACE_NEGATIVE_Z] = GetTileImage(image, 1, 3, faceWidth, faceHeight);
+                if (loadImages_[FACE_NEGATIVE_Z])
+                {
+                    loadImages_[FACE_NEGATIVE_Z]->FlipVertical();
+                    loadImages_[FACE_NEGATIVE_Z]->FlipHorizontal();
+                }
+                break;
+
+            case CML_BLENDER:
+                faceWidth = image->GetWidth() / 3;
+                faceHeight = image->GetHeight() / 2;
+                loadImages_[FACE_NEGATIVE_X] = GetTileImage(image, 0, 0, faceWidth, faceHeight);
+                loadImages_[FACE_NEGATIVE_Z] = GetTileImage(image, 1, 0, faceWidth, faceHeight);
+                loadImages_[FACE_POSITIVE_X] = GetTileImage(image, 2, 0, faceWidth, faceHeight);
+                loadImages_[FACE_NEGATIVE_Y] = GetTileImage(image, 0, 1, faceWidth, faceHeight);
+                loadImages_[FACE_POSITIVE_Y] = GetTileImage(image, 1, 1, faceWidth, faceHeight);
+                loadImages_[FACE_POSITIVE_Z] = GetTileImage(image, 2, 1, faceWidth, faceHeight);
+                break;
             }
-            break;
-
-        case CML_BLENDER:
-            faceWidth = image->GetWidth() / 3;
-            faceHeight = image->GetHeight() / 2;
-            loadImages_[FACE_NEGATIVE_X] = GetTileImage(image, 0, 0, faceWidth, faceHeight);
-            loadImages_[FACE_NEGATIVE_Z] = GetTileImage(image, 1, 0, faceWidth, faceHeight);
-            loadImages_[FACE_POSITIVE_X] = GetTileImage(image, 2, 0, faceWidth, faceHeight);
-            loadImages_[FACE_NEGATIVE_Y] = GetTileImage(image, 0, 1, faceWidth, faceHeight);
-            loadImages_[FACE_POSITIVE_Y] = GetTileImage(image, 1, 1, faceWidth, faceHeight);
-            loadImages_[FACE_POSITIVE_Z] = GetTileImage(image, 2, 1, faceWidth, faceHeight);
-            break;
         }
     }
     // Face per image

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

@@ -30,6 +30,10 @@
 
 #include "../DebugNew.h"
 
+#ifdef _MSC_VER
+#pragma warning(disable:6293)
+#endif
+
 namespace Atomic
 {
 

+ 4 - 0
Source/Atomic/Graphics/Renderer.cpp

@@ -50,6 +50,10 @@
 
 #include "../DebugNew.h"
 
+#ifdef _MSC_VER
+#pragma warning(disable:6293)
+#endif
+
 namespace Atomic
 {
 

+ 1 - 1
Source/Atomic/Graphics/Renderer.h

@@ -194,7 +194,7 @@ public:
     void SetMinInstances(int instances);
     /// Set maximum number of sorted instances per batch group. If exceeded, instances are rendered unsorted.
     void SetMaxSortedInstances(int instances);
-    /// Set maximum number of occluder trianges.
+    /// Set maximum number of occluder triangles.
     void SetMaxOccluderTriangles(int triangles);
     /// Set occluder buffer width.
     void SetOcclusionBufferSize(int size);

+ 1 - 1
Source/Atomic/Graphics/Shader.cpp

@@ -98,7 +98,7 @@ bool Shader::BeginLoad(Deserializer& source)
     CommentOutFunction(vsSourceCode_, "void PS(");
     CommentOutFunction(psSourceCode_, "void VS(");
 
-    // OpenGL: rename either VS() or PS() to main(), comment out vertex attributes in pixel shaders
+    // OpenGL: rename either VS() or PS() to main()
 #ifdef ATOMIC_OPENGL
     vsSourceCode_.Replace("void VS(", "void main(");
     psSourceCode_.Replace("void PS(", "void main(");

+ 37 - 61
Source/Atomic/Graphics/View.cpp

@@ -295,6 +295,7 @@ View::View(Context* context) :
     camera_(0),
     cameraZone_(0),
     farClipZone_(0),
+    occlusionBuffer_(0),
     renderTarget_(0),
     substituteRenderTarget_(0)
 {
@@ -644,7 +645,6 @@ void View::Render()
     octree_ = 0;
     cameraZone_ = 0;
     farClipZone_ = 0;
-    occlusionBuffer_ = 0;
     frame_.camera_ = 0;
 }
 
@@ -1688,7 +1688,7 @@ void View::SetRenderTargets(RenderPathCommand& command)
         }
     }
 
-    // When rendering to the final destination rendertarget, use the actual viewport. Otherwise texture rendertargets should use 
+    // 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 = (useViewportOutput && currentRenderTarget_ == renderTarget_) ? viewRect_ : IntRect(0, 0, rtSizeNow.x_,
@@ -1854,33 +1854,50 @@ bool View::CheckPingpong(unsigned index)
 
 void View::AllocateScreenBuffers()
 {
+    bool hasScenePassToRTs = false;
+    bool hasCustomDepth = false;
+    bool hasViewportRead = false;
+    bool hasPingpong = false;
     bool needSubstitute = false;
     unsigned numViewportTextures = 0;
     depthOnlyDummyTexture_ = 0;
 
-#ifdef ATOMIC_OPENGL
-    // Due to FBO limitations, in OpenGL deferred modes need to render to texture first and then blit to the backbuffer
-    // Also, if rendering to a texture with full deferred rendering, it must be RGBA to comply with the rest of the buffers,
-    // unless using OpenGL 3
-    if ((deferred_ && !renderTarget_) || (!Graphics::GetGL3Support() && deferredAmbient_ && renderTarget_ &&
-                                          renderTarget_->GetParentTexture()->GetFormat() != Graphics::GetRGBAFormat()))
-        needSubstitute = true;
-    // Also need substitute if rendering to backbuffer using a custom (readable) depth buffer
-    if (!renderTarget_ && !needSubstitute)
+    // Check for commands with special meaning: has custom depth, renders a scene pass to other than the destination viewport,
+    // read the viewport, or pingpong between viewport textures. These may trigger the need to substitute the destination RT
+    for (unsigned i = 0; i < renderPath_->commands_.Size(); ++i)
     {
-        for (unsigned i = 0; i < renderPath_->commands_.Size(); ++i)
+        const RenderPathCommand& command = renderPath_->commands_[i];
+        if (!IsNecessary(command))
+            continue;
+        if (!hasViewportRead && CheckViewportRead(command))
+            hasViewportRead = true;
+        if (!hasPingpong && CheckPingpong(i))
+            hasPingpong = true;
+        if (command.depthStencilName_.Length())
+            hasCustomDepth = true;
+        if (!hasScenePassToRTs && command.type_ == CMD_SCENEPASS)
         {
-            const RenderPathCommand& command = renderPath_->commands_[i];
-            if (!IsNecessary(command))
-                continue;
-            if (command.depthStencilName_.Length() && command.outputs_.Size() && !command.outputs_[0].first_.Compare("viewport",
-                false))
+            for (unsigned j = 0; j < command.outputs_.Size(); ++j)
             {
-                needSubstitute = true;
-                break;
+                if (command.outputs_[j].first_.Compare("viewport", false))
+                {
+                    hasScenePassToRTs = true;
+                    break;
+                }
             }
         }
     }
+
+#ifdef ATOMIC_OPENGL
+    // Due to FBO limitations, in OpenGL deferred modes need to render to texture first and then blit to the backbuffer
+    // Also, if rendering to a texture with full deferred rendering, it must be RGBA to comply with the rest of the buffers,
+    // unless using OpenGL 3
+    if (((deferred_ || hasScenePassToRTs) && !renderTarget_) || (!Graphics::GetGL3Support() && deferredAmbient_ && renderTarget_
+        && renderTarget_->GetParentTexture()->GetFormat() != Graphics::GetRGBAFormat()))
+            needSubstitute = true;
+    // Also need substitute if rendering to backbuffer using a custom (readable) depth buffer
+    if (!renderTarget_ && hasCustomDepth)
+        needSubstitute = true;
 #endif
     // If backbuffer is antialiased when using deferred rendering, need to reserve a buffer
     if (deferred_ && !renderTarget_ && graphics_->GetMultiSample() > 1)
@@ -1889,34 +1906,8 @@ void View::AllocateScreenBuffers()
     // textures will be sized equal to the viewport
     if (viewSize_.x_ < rtSize_.x_ || viewSize_.y_ < rtSize_.y_)
     {
-        if (deferred_)
+        if (deferred_ || hasScenePassToRTs || hasCustomDepth)
             needSubstitute = true;
-        else if (!needSubstitute)
-        {
-            // Check also if using MRT without deferred rendering and rendering to the viewport and another texture,
-            // or using custom depth
-            for (unsigned i = 0; i < renderPath_->commands_.Size(); ++i)
-            {
-                const RenderPathCommand& command = renderPath_->commands_[i];
-                if (!IsNecessary(command))
-                    continue;
-                if (command.depthStencilName_.Length())
-                    needSubstitute = true;
-                if (!needSubstitute && command.outputs_.Size() > 1)
-                {
-                    for (unsigned j = 0; j < command.outputs_.Size(); ++j)
-                    {
-                        if (!command.outputs_[j].first_.Compare("viewport", false))
-                        {
-                            needSubstitute = true;
-                            break;
-                        }
-                    }
-                }
-                if (needSubstitute)
-                    break;
-            }
-        }
     }
 
     // Follow final rendertarget format, or use RGB to match the backbuffer format
@@ -1935,21 +1926,6 @@ void View::AllocateScreenBuffers()
         format = Graphics::GetRGBAFormat();
 #endif
 
-    // Check for commands which read the viewport, or pingpong between viewport textures
-    bool hasViewportRead = false;
-    bool hasPingpong = false;
-
-    for (unsigned i = 0; i < renderPath_->commands_.Size(); ++i)
-    {
-        const RenderPathCommand& command = renderPath_->commands_[i];
-        if (!IsNecessary(command))
-            continue;
-        if (CheckViewportRead(command))
-            hasViewportRead = true;
-        if (!hasPingpong && CheckPingpong(i))
-            hasPingpong = true;
-    }
-
     if (hasViewportRead)
     {
         ++numViewportTextures;

+ 4 - 3
Source/Atomic/Graphics/View.h

@@ -85,8 +85,6 @@ struct ScenePassInfo
     bool allowInstancing_;
     /// Mark to stencil flag.
     bool markToStencil_;
-    /// Light scissor optimization flag.
-    bool useScissor_;
     /// Vertex light flag.
     bool vertexLights_;
     /// Batch queue.
@@ -164,6 +162,9 @@ public:
     /// Return light batch queues.
     const Vector<LightBatchQueue>& GetLightQueues() const { return lightQueues_; }
 
+    /// Return the last used software occlusion buffer.
+    OcclusionBuffer* GetOcclusionBuffer() const { return occlusionBuffer_; }
+
     /// Set global (per-frame) shader parameters. Called by Batch and internally by View.
     void SetGlobalShaderParameters();
     /// Set camera-specific shader parameters. Called by Batch and internally by View.
@@ -374,7 +375,7 @@ private:
     /// Intermediate light processing results.
     Vector<LightQueryResult> lightQueryResults_;
     /// Info for scene render passes defined by the renderpath.
-    Vector<ScenePassInfo> scenePasses_;
+    PODVector<ScenePassInfo> scenePasses_;
     /// Per-pixel light queues.
     Vector<LightBatchQueue> lightQueues_;
     /// Per-vertex light queues.

+ 12 - 1
Source/Atomic/IO/Deserializer.cpp

@@ -330,6 +330,9 @@ Variant Deserializer::ReadVariant(VariantType type)
     case VAR_VARIANTVECTOR:
         return Variant(ReadVariantVector());
 
+    case VAR_STRINGVECTOR:
+        return Variant(ReadStringVector());
+
     case VAR_VARIANTMAP:
         return Variant(ReadVariantMap());
 
@@ -347,7 +350,7 @@ Variant Deserializer::ReadVariant(VariantType type)
 
     case VAR_MATRIX4:
         return Variant(ReadMatrix4());
-        
+
     case VAR_DOUBLE:
         return Variant(ReadDouble());
 
@@ -364,6 +367,14 @@ VariantVector Deserializer::ReadVariantVector()
     return ret;
 }
 
+StringVector Deserializer::ReadStringVector()
+{
+    StringVector ret(ReadVLE());
+    for (unsigned i = 0; i < ret.Size(); ++i)
+        ret[i] = ReadString();
+    return ret;
+}
+
 VariantMap Deserializer::ReadVariantMap()
 {
     VariantMap ret;

+ 2 - 0
Source/Atomic/IO/Deserializer.h

@@ -122,6 +122,8 @@ public:
     Variant ReadVariant(VariantType type);
     /// Read a variant vector.
     VariantVector ReadVariantVector();
+    /// Read a string vector.
+    StringVector ReadStringVector();
     /// Read a variant map.
     VariantMap ReadVariantMap();
     /// Read a variable-length encoded unsigned integer, which can use 29 bits maximum.

+ 3 - 2
Source/Atomic/IO/File.cpp

@@ -56,6 +56,7 @@ static const char* openMode[] =
 #endif
 
 #ifdef ANDROID
+const char* APK = "/apk/";
 static const unsigned READ_BUFFER_SIZE = 32768;
 #endif
 static const unsigned SKIP_BUFFER_SIZE = 1024;
@@ -131,7 +132,7 @@ bool File::Open(const String& fileName, FileMode mode)
     }
 
 #ifdef ANDROID
-    if (fileName.StartsWith("/apk/"))
+    if (IS_ASSET(fileName))
     {
         if (mode != FILE_READ)
         {
@@ -139,7 +140,7 @@ bool File::Open(const String& fileName, FileMode mode)
             return false;
         }
 
-        assetHandle_ = SDL_RWFromFile(fileName.Substring(5).CString(), "rb");
+        assetHandle_ = SDL_RWFromFile(ASSET(fileName), "rb");
         if (!assetHandle_)
         {
             LOGERRORF("Could not open asset file %s", fileName.CString());

+ 9 - 0
Source/Atomic/IO/File.h

@@ -34,6 +34,15 @@
 namespace Atomic
 {
 
+#ifdef ANDROID
+extern const char* APK;
+
+// Macro for checking if a given pathname is inside APK's assets directory
+#define IS_ASSET(p) p.StartsWith(APK)
+// Macro for truncating the APK prefix string from the asset pathname and at the same time patching the directory name components (see custom_rules.xml)
+#define ASSET(p) p.Substring(5).Replaced("/", ASSET_DIR_INDICATOR "/").CString()
+#endif
+
 /// File open mode.
 enum FileMode
 {

+ 74 - 79
Source/Atomic/IO/FileSystem.cpp

@@ -60,13 +60,17 @@
 #include <mach-o/dyld.h>
 #endif
 
+extern "C"
+{
 #ifdef ANDROID
-extern "C" const char* SDL_Android_GetFilesDir();
-#endif
-#ifdef IOS
-extern "C" const char* SDL_IOS_GetResourceDir();
-extern "C" const char* SDL_IOS_GetDocumentsDir();
+const char* SDL_Android_GetFilesDir();
+char** SDL_Android_GetFileList(const char* path, int* count);
+void SDL_Android_FreeFileList(char*** array, int* count);
+#elif IOS
+const char* SDL_IOS_GetResourceDir();
+const char* SDL_IOS_GetDocumentsDir();
 #endif
+}
 
 #include "../DebugNew.h"
 
@@ -81,7 +85,7 @@ int DoSystemCommand(const String& commandLine, bool redirectToLog, Context* cont
     // Get a platform-agnostic temporary file name for stderr redirection
     String stderrFilename;
     String adjustedCommandLine(commandLine);
-    char* prefPath = SDL_GetPrefPath("urho3d", "temp");
+    char* prefPath = SDL_GetPrefPath("atomicgameengine", "temp");
     if (prefPath)
     {
         stderrFilename = String(prefPath) + "command-stderr";
@@ -559,12 +563,10 @@ bool FileSystem::FileExists(const String& fileName) const
     if (!CheckAccess(GetPath(fileName)))
         return false;
 
-    String fixedName = GetNativePath(RemoveTrailingSlash(fileName));
-
 #ifdef ANDROID
-    if (fixedName.StartsWith("/apk/"))
+    if (IS_ASSET(fileName))
     {
-        SDL_RWops* rwOps = SDL_RWFromFile(fileName.Substring(5).CString(), "rb");
+        SDL_RWops* rwOps = SDL_RWFromFile(ASSET(fileName), "rb");
         if (rwOps)
         {
             SDL_RWclose(rwOps);
@@ -575,6 +577,8 @@ bool FileSystem::FileExists(const String& fileName) const
     }
 #endif
 
+    String fixedName = GetNativePath(RemoveTrailingSlash(fileName));
+
 #ifdef WIN32
     DWORD attributes = GetFileAttributesW(WString(fixedName).CString());
     if (attributes == INVALID_FILE_ATTRIBUTES || attributes & FILE_ATTRIBUTE_DIRECTORY)
@@ -602,9 +606,31 @@ bool FileSystem::DirExists(const String& pathName) const
     String fixedName = GetNativePath(RemoveTrailingSlash(pathName));
 
 #ifdef ANDROID
-    /// \todo Actually check for existence, now true is always returned for directories within the APK
-    if (fixedName.StartsWith("/apk/"))
-        return true;
+    if (IS_ASSET(fixedName))
+    {
+        // Split the pathname into two components: the longest parent directory path and the last name component
+        String assetPath(ASSET((fixedName + '/')));
+        String parentPath;
+        unsigned pos = assetPath.FindLast('/', assetPath.Length() - 2);
+        if (pos != String::NPOS)
+        {
+            parentPath = assetPath.Substring(0, pos - 1);
+            assetPath = assetPath.Substring(pos + 1);
+        }
+        assetPath.Resize(assetPath.Length() - 1);
+
+        bool exist = false;
+        int count;
+        char** list = SDL_Android_GetFileList(parentPath.CString(), &count);
+        for (int i = 0; i < count; ++i)
+        {
+            exist = assetPath == list[i];
+            if (exist)
+                break;
+        }
+        SDL_Android_FreeFileList(&list, &count);
+        return exist;
+    }
 #endif
 
 #ifdef WIN32
@@ -640,7 +666,7 @@ String FileSystem::GetProgramDir() const
 #if defined(ANDROID)
     // This is an internal directory specifier pointing to the assets in the .apk
     // Files from this directory will be opened using special handling
-    programDir_ = "/apk/";
+    programDir_ = APK;
     return programDir_;
 #elif defined(IOS)
     programDir_ = AddTrailingSlash(SDL_IOS_GetResourceDir());
@@ -744,81 +770,51 @@ bool FileSystem::SetLastModifiedTime(const String& fileName, unsigned newTime)
 #endif
 }
 
-#if defined(ANDROID)
-
-// the android asset manager is quite limited, so use a manifest instead
-static Vector<String> _atomicManifest;
-
 void FileSystem::ScanDirInternal(Vector<String>& result, String path, const String& startPath,
     const String& filter, unsigned flags, bool recursive) const
 {
-
-    if (!_atomicManifest.Size())
-    {
-        File file(context_, "/apk/AtomicManifest", FILE_READ);
-        if (!file.IsOpen())
-        {
-            LOGERRORF("Unabled to open AtomicManifest");
-            return;
-        }
-
-        String manifest = file.ReadString();
-        _atomicManifest = manifest.Split(';');
-    }
-
-    if (path.StartsWith("/apk/"))
-        path = path.Substring(5);
-
-    path.Replace(String("//"), String("/"));
-    path = RemoveTrailingSlash(path);
-
-    // first path is the CoreData/AtomicResources folder
-    path = path.Substring(path.Find('/') + 1) + "/";
+    path = AddTrailingSlash(path);
+    String deltaPath;
+    if (path.Length() > startPath.Length())
+        deltaPath = path.Substring(startPath.Length());
 
     String filterExtension = filter.Substring(filter.Find('.'));
     if (filterExtension.Contains('*'))
         filterExtension.Clear();
 
-    for (unsigned i = 0; i < _atomicManifest.Size(); i++ )
+#ifdef ANDROID
+    if (IS_ASSET(path))
     {
-        const String& file = _atomicManifest[i];
-        String filePath = GetPath(file);
-        String filename = GetFileNameAndExtension(file);
-
-        //LOGINFOF("%s : %s : %s", path.CString(), filePath.CString(), filename.CString());
-
-        if (filePath.StartsWith(path))
+        String assetPath(ASSET(path));
+        assetPath.Resize(assetPath.Length() - 1);       // AssetManager.list() does not like trailing slash
+        int count;
+        char** list = SDL_Android_GetFileList(assetPath.CString(), &count);
+        for (int i = 0; i < count; ++i)
         {
-            if (flags & SCAN_FILES)
-            {
-                if (filterExtension.Empty() || filename.EndsWith(filterExtension))
-                {
-                    String deltaPath;
-
-                    if (path.Length() > startPath.Length())
-                        deltaPath = path.Substring(startPath.Length());
+            String fileName(list[i]);
+            if (!(flags & SCAN_HIDDEN) && fileName.StartsWith("."))
+                continue;
 
-                    result.Push(deltaPath + GetFileNameAndExtension(file));
-                }
+            // Patch the directory name back after retrieving the directory flag
+            bool isDirectory = fileName.EndsWith(ASSET_DIR_INDICATOR);
+            if (isDirectory)
+            {
+                fileName.Resize(fileName.Length() - sizeof(ASSET_DIR_INDICATOR) / sizeof(char) + 1);
+                if (flags & SCAN_DIRS)
+                    result.Push(deltaPath + fileName);
+                if (recursive)
+                    ScanDirInternal(result, path + fileName, startPath, filter, flags, recursive);
+            }
+            else if (flags & SCAN_FILES)
+            {
+                if (filterExtension.Empty() || fileName.EndsWith(filterExtension))
+                    result.Push(deltaPath + fileName);
             }
         }
+        SDL_Android_FreeFileList(&list, &count);
+        return;
     }
-}
-
-#else
-
-void FileSystem::ScanDirInternal(Vector<String>& result, String path, const String& startPath,
-    const String& filter, unsigned flags, bool recursive) const
-{
-    path = AddTrailingSlash(path);
-    String deltaPath;
-    if (path.Length() > startPath.Length())
-        deltaPath = path.Substring(startPath.Length());
-
-    String filterExtension = filter.Substring(filter.Find('.'));
-    if (filterExtension.Contains('*'))
-        filterExtension.Clear();
-
+#endif
 #ifdef WIN32
     WIN32_FIND_DATAW info;
     HANDLE handle = FindFirstFileW(WString(path + "*").CString(), &info);
@@ -844,11 +840,11 @@ void FileSystem::ScanDirInternal(Vector<String>& result, String path, const Stri
                         result.Push(deltaPath + fileName);
                 }
             }
-        } while (FindNextFileW(handle, &info));
+        }
+        while (FindNextFileW(handle, &info));
 
         FindClose(handle);
     }
-}
 #else
     DIR* dir;
     struct dirent* de;
@@ -882,10 +878,9 @@ void FileSystem::ScanDirInternal(Vector<String>& result, String path, const Stri
         }
         closedir(dir);
     }
-}
 #endif
 
-#endif
+}
 
 bool FileSystem::CreateDirs(const String& root, const String& subdirectory)
 {

+ 2 - 2
Source/Atomic/IO/PackageFile.cpp

@@ -53,7 +53,7 @@ PackageFile::~PackageFile()
 bool PackageFile::Open(const String& fileName, unsigned startOffset)
 {
 #ifdef ANDROID
-    if (fileName.StartsWith("/apk/"))
+    if (IS_ASSET(fileName))
     {
         LOGERROR("Package files within the apk are not supported on Android");
         return false;
@@ -142,7 +142,7 @@ const PackageEntry* PackageFile::GetEntry(const String& fileName) const
     HashMap<String, PackageEntry>::ConstIterator i = entries_.Find(fileName);
     if (i != entries_.End())
         return &i->second_;
-    
+
 #ifdef WIN32
     // On Windows perform a fallback case-insensitive search
     else

+ 13 - 1
Source/Atomic/IO/Serializer.cpp

@@ -282,6 +282,9 @@ bool Serializer::WriteVariantData(const Variant& value)
     case VAR_VARIANTVECTOR:
         return WriteVariantVector(value.GetVariantVector());
 
+    case VAR_STRINGVECTOR:
+        return WriteStringVector(value.GetStringVector());
+
     case VAR_VARIANTMAP:
         return WriteVariantMap(value.GetVariantMap());
 
@@ -299,7 +302,7 @@ bool Serializer::WriteVariantData(const Variant& value)
 
     case VAR_MATRIX4:
         return WriteMatrix4(value.GetMatrix4());
-        
+
     case VAR_DOUBLE:
         return WriteDouble(value.GetDouble());
 
@@ -317,6 +320,15 @@ bool Serializer::WriteVariantVector(const VariantVector& value)
     return success;
 }
 
+bool Serializer::WriteStringVector(const StringVector& value)
+{
+    bool success = true;
+    success &= WriteVLE(value.Size());
+    for (StringVector::ConstIterator i = value.Begin(); i != value.End(); ++i)
+        success &= WriteString(*i);
+    return success;
+}
+
 bool Serializer::WriteVariantMap(const VariantMap& value)
 {
     bool success = true;

+ 2 - 0
Source/Atomic/IO/Serializer.h

@@ -113,6 +113,8 @@ public:
     bool WriteVariantData(const Variant& value);
     /// Write a variant vector.
     bool WriteVariantVector(const VariantVector& value);
+    /// Write a variant vector.
+    bool WriteStringVector(const StringVector& value);
     /// Write a variant map.
     bool WriteVariantMap(const VariantMap& value);
     /// Write a variable-length encoded unsigned integer, which can use 29 bits maximum.

+ 2 - 2
Source/Atomic/Input/Input.cpp

@@ -80,7 +80,7 @@ UIElement* TouchState::GetTouchedElement()
 #define EM_TRUE 1
 
 /// Glue between Urho Input and Emscripten HTML5
-/** HTML5 (Emscripten) is limited in the way it handles input. The EmscriptenInput class attempts to provide the glue between Urho3D Input behavior and HTML5, where SDL currently fails to do so.
+/** HTML5 (Emscripten) is limited in the way it handles input. The EmscriptenInput class attempts to provide the glue between Atomic Input behavior and HTML5, where SDL currently fails to do so.
  *
  * Mouse Input:
  * - The OS mouse cursor position can't be set.
@@ -1178,7 +1178,7 @@ void Input::ResetJoysticks()
     joysticks_.Clear();
 
     // Open each detected joystick automatically on startup
-    int size = SDL_NumJoysticks();
+    unsigned size = static_cast<unsigned>(SDL_NumJoysticks());
     for (unsigned i = 0; i < size; ++i)
         OpenJoystick(i);
 }

+ 7 - 2
Source/Atomic/Math/AreaAllocator.cpp

@@ -24,6 +24,8 @@
 
 #include "../Math/AreaAllocator.h"
 
+#include "../DebugNew.h"
+
 namespace Atomic
 {
 
@@ -147,7 +149,7 @@ bool AreaAllocator::Allocate(int width, int height, int& x, int& y)
         // Remove the reserved area from all free areas
         for (unsigned i = 0; i < freeAreas_.Size();)
         {
-            if (SplitRect(freeAreas_[i], reserved))
+            if (SplitRect(i, reserved))
                 freeAreas_.Erase(i);
             else
                 ++i;
@@ -159,8 +161,11 @@ bool AreaAllocator::Allocate(int width, int height, int& x, int& y)
     return true;
 }
 
-bool AreaAllocator::SplitRect(IntRect original, const IntRect& reserve)
+bool AreaAllocator::SplitRect(unsigned freeAreaIndex, const IntRect& reserve)
 {
+    // Make a copy, as the vector will be modified
+    IntRect original = freeAreas_[freeAreaIndex];
+
     if (reserve.right_ > original.left_ && reserve.left_ < original.right_ && reserve.bottom_ > original.top_ &&
         reserve.top_ < original.bottom_)
     {

+ 1 - 1
Source/Atomic/Math/AreaAllocator.h

@@ -54,7 +54,7 @@ public:
 
 private:
     /// Remove space from a free rectangle. Return true if the original rectangle should be erased from the free list. Not called in fast mode.
-    bool SplitRect(IntRect original, const IntRect& reserve);
+    bool SplitRect(unsigned freeAreaIndex, const IntRect& reserve);
     /// Clean up redundant free space. Not called in fast mode.
     void Cleanup();
 

+ 2 - 0
Source/Atomic/Math/BoundingBox.cpp

@@ -25,6 +25,8 @@
 #include "../Math/Frustum.h"
 #include "../Math/Polyhedron.h"
 
+#include "../DebugNew.h"
+
 namespace Atomic
 {
 

+ 2 - 0
Source/Atomic/Math/Color.cpp

@@ -26,6 +26,8 @@
 
 #include <cstdio>
 
+#include "../DebugNew.h"
+
 namespace Atomic
 {
 

+ 13 - 0
Source/Atomic/Math/Color.h

@@ -90,6 +90,16 @@ public:
     {
     }
 
+    /// Assign from another color.
+    Color& operator =(const Color& rhs)
+    {
+        r_ = rhs.r_;
+        g_ = rhs.g_;
+        b_ = rhs.b_;
+        a_ = rhs.a_;
+        return *this;
+    }
+
     /// Test for equality with another color without epsilon.
     bool operator ==(const Color& rhs) const { return r_ == rhs.r_ && g_ == rhs.g_ && b_ == rhs.b_ && a_ == rhs.a_; }
 
@@ -102,6 +112,9 @@ public:
     /// Add a color.
     Color operator +(const Color& rhs) const { return Color(r_ + rhs.r_, g_ + rhs.g_, b_ + rhs.b_, a_ + rhs.a_); }
 
+    /// Return negation.
+    Color operator -() const { return Color(-r_, -g_, -b_, -a_); }
+
     /// Substract a color.
     Color operator -(const Color& rhs) const { return Color(r_ - rhs.r_, g_ - rhs.g_, b_ - rhs.b_, a_ - rhs.a_); }
 

+ 2 - 0
Source/Atomic/Math/Frustum.cpp

@@ -24,6 +24,8 @@
 
 #include "../Math/Frustum.h"
 
+#include "../DebugNew.h"
+
 namespace Atomic
 {
 

+ 48 - 2
Source/Atomic/Math/MathDefs.h

@@ -62,6 +62,9 @@ inline bool Equals(float lhs, float rhs) { return lhs + M_EPSILON >= rhs && lhs
 /// Linear interpolation between two float values.
 inline float Lerp(float lhs, float rhs, float t) { return lhs * (1.0f - t) + rhs * t; }
 
+/// Linear interpolation between two double values.
+inline double Lerp(double lhs, double rhs, float t) { return lhs * (1.0f - t) + rhs * t; }
+
 /// Return the smaller of two floats.
 inline float Min(float lhs, float rhs) { return lhs < rhs ? lhs : rhs; }
 
@@ -189,12 +192,55 @@ inline float Random(float range) { return Rand() * range / 32767.0f; }
 inline float Random(float min, float max) { return Rand() * (max - min) / 32767.0f + min; }
 
 /// Return a random integer between 0 and range - 1.
-inline int Random(int range) { return (Rand() * (range - 1) + 16384) / 32767; }
+inline int Random(int range) { return (int)(Random() * range); }
 
 /// Return a random integer between min and max - 1.
-inline int Random(int min, int max) { return (Rand() * (max - min - 1) + 16384) / 32767 + min; }
+inline int Random(int min, int max) { float range = (float)(max - min); return (int)(Random() * range) + min; }
 
 /// Return a random normal distributed number with the given mean value and variance.
 inline float RandomNormal(float meanValue, float variance) { return RandStandardNormal() * sqrtf(variance) + meanValue; }
 
+/// Convert float to half float. From https://gist.github.com/martinkallman/5049614
+inline unsigned short FloatToHalf(float value)
+{
+    unsigned inu = *((unsigned*)&value);
+    unsigned t1 = inu & 0x7fffffff;         // Non-sign bits
+    unsigned t2 = inu & 0x80000000;         // Sign bit
+    unsigned t3 = inu & 0x7f800000;         // Exponent
+
+    t1 >>= 13;                              // Align mantissa on MSB
+    t2 >>= 16;                              // Shift sign bit into position
+
+    t1 -= 0x1c000;                          // Adjust bias
+
+    t1 = (t3 < 0x38800000) ? 0 : t1;        // Flush-to-zero
+    t1 = (t3 > 0x47000000) ? 0x7bff : t1;   // Clamp-to-max
+    t1 = (t3 == 0 ? 0 : t1);                // Denormals-as-zero
+
+    t1 |= t2;                               // Re-insert sign bit
+
+    return (unsigned short)t1;
+}
+
+/// Convert half float to float. From https://gist.github.com/martinkallman/5049614
+inline float HalfToFloat(unsigned short value)
+{
+    unsigned t1 = value & 0x7fff;           // Non-sign bits
+    unsigned t2 = value & 0x8000;           // Sign bit
+    unsigned t3 = value & 0x7c00;           // Exponent
+
+    t1 <<= 13;                              // Align mantissa on MSB
+    t2 <<= 16;                              // Shift sign bit into position
+
+    t1 += 0x38000000;                       // Adjust bias
+
+    t1 = (t3 == 0 ? 0 : t1);                // Denormals-as-zero
+
+    t1 |= t2;                               // Re-insert sign bit
+
+    float out;
+    *((unsigned*)&out) = t1;
+    return out;
+}
+
 }

+ 2 - 0
Source/Atomic/Math/Plane.cpp

@@ -24,6 +24,8 @@
 
 #include "../Math/Plane.h"
 
+#include "../DebugNew.h"
+
 namespace Atomic
 {
 

+ 9 - 0
Source/Atomic/Math/Plane.h

@@ -63,6 +63,15 @@ public:
         Define(plane);
     }
 
+    /// Assign from another plane.
+    Plane& operator =(const Plane& rhs)
+    {
+        normal_ = rhs.normal_;
+        absNormal_ = rhs.absNormal_;
+        d_ = rhs.d_;
+        return *this;
+    }
+
     /// Define from 3 vertices.
     void Define(const Vector3& v0, const Vector3& v1, const Vector3& v2)
     {

+ 6 - 0
Source/Atomic/Math/Polyhedron.cpp

@@ -25,6 +25,12 @@
 #include "../Math/Frustum.h"
 #include "../Math/Polyhedron.h"
 
+#include "../DebugNew.h"
+
+#ifdef _MSC_VER
+#pragma warning(disable:6293)
+#endif
+
 namespace Atomic
 {
 

+ 7 - 0
Source/Atomic/Math/Polyhedron.h

@@ -69,6 +69,13 @@ public:
     /// Destruct.
     ~Polyhedron();
 
+    /// Assign from another polyhedron.
+    Polyhedron& operator =(const Polyhedron& rhs)
+    {
+        faces_ = rhs.faces_;
+        return *this;
+    }
+
     /// Define from a bounding box.
     void Define(const BoundingBox& box);
     /// Define from a frustum.

+ 14 - 5
Source/Atomic/Math/Quaternion.cpp

@@ -26,6 +26,8 @@
 
 #include <cstdio>
 
+#include "../DebugNew.h"
+
 namespace Atomic
 {
 
@@ -148,13 +150,20 @@ void Quaternion::FromRotationMatrix(const Matrix3& matrix)
 
 bool Quaternion::FromLookRotation(const Vector3& direction, const Vector3& upDirection)
 {
+    Quaternion ret;
     Vector3 forward = direction.Normalized();
-    Vector3 v = forward.CrossProduct(upDirection).Normalized();
-    Vector3 up = v.CrossProduct(forward);
-    Vector3 right = up.CrossProduct(forward);
 
-    Quaternion ret;
-    ret.FromAxes(right, up, forward);
+    Vector3 v = forward.CrossProduct(upDirection);
+    // If direction & upDirection are parallel and crossproduct becomes zero, use FromRotationTo() fallback
+    if (v.LengthSquared() >= M_EPSILON)
+    {
+        v.Normalize();
+        Vector3 up = v.CrossProduct(forward);
+        Vector3 right = up.CrossProduct(forward);
+        ret.FromAxes(right, up, forward);
+    }
+    else
+        ret.FromRotationTo(Vector3::FORWARD, forward);
 
     if (!ret.IsNaN())
     {

+ 2 - 0
Source/Atomic/Math/Random.cpp

@@ -24,6 +24,8 @@
 
 #include "../Math/Random.h"
 
+#include "../DebugNew.h"
+
 namespace Atomic
 {
 

+ 2 - 0
Source/Atomic/Math/Ray.cpp

@@ -26,6 +26,8 @@
 #include "../Math/Frustum.h"
 #include "../Math/Ray.h"
 
+#include "../DebugNew.h"
+
 namespace Atomic
 {
 

+ 2 - 0
Source/Atomic/Math/Rect.cpp

@@ -26,6 +26,8 @@
 
 #include <cstdio>
 
+#include "../DebugNew.h"
+
 namespace Atomic
 {
 

+ 2 - 0
Source/Atomic/Math/Sphere.cpp

@@ -25,6 +25,8 @@
 #include "../Math/Frustum.h"
 #include "../Math/Polyhedron.h"
 
+#include "../DebugNew.h"
+
 namespace Atomic
 {
 

+ 8 - 0
Source/Atomic/Math/Vector2.h

@@ -253,6 +253,14 @@ public:
     {
     }
 
+    /// Assign from another vector.
+    IntVector2& operator =(const IntVector2& rhs)
+    {
+        x_ = rhs.x_;
+        y_ = rhs.y_;
+        return *this;
+    }
+
     /// Test for equality with another vector.
     bool operator ==(const IntVector2& rhs) const { return x_ == rhs.x_ && y_ == rhs.y_; }
 

+ 311 - 198
Source/Atomic/Navigation/CrowdAgent.cpp

@@ -22,19 +22,15 @@
 
 #include "../Precompiled.h"
 
-#include "../Scene/Component.h"
 #include "../Core/Context.h"
-#include "../Navigation/CrowdAgent.h"
+#include "../Core/Profiler.h"
 #include "../Graphics/DebugRenderer.h"
-#include "../Navigation/DetourCrowdManager.h"
 #include "../IO/Log.h"
 #include "../IO/MemoryBuffer.h"
 #include "../Navigation/NavigationEvents.h"
+#include "../Navigation/CrowdAgent.h"
 #include "../Scene/Node.h"
-#include "../Core/Profiler.h"
 #include "../Scene/Scene.h"
-#include "../Scene/Serializable.h"
-#include "../Core/Variant.h"
 
 #include <Detour/include/DetourCommon.h>
 #include <DetourCrowd/include/DetourCrowd.h>
@@ -46,42 +42,54 @@ namespace Atomic
 
 extern const char* NAVIGATION_CATEGORY;
 
-static const unsigned DEFAULT_AGENT_NAVIGATION_FILTER_TYPE = 0;
+static const CrowdAgentRequestedTarget DEFAULT_AGENT_REQUEST_TARGET_TYPE = CA_REQUESTEDTARGET_NONE;
 static const float DEFAULT_AGENT_MAX_SPEED = 0.f;
 static const float DEFAULT_AGENT_MAX_ACCEL = 0.f;
+static const unsigned DEFAULT_AGENT_QUERY_FILTER_TYPE = 0;
+static const unsigned DEFAULT_AGENT_OBSTACLE_AVOIDANCE_TYPE = 0;
 static const NavigationQuality DEFAULT_AGENT_AVOIDANCE_QUALITY = NAVIGATIONQUALITY_HIGH;
-static const NavigationPushiness DEFAULT_AGENT_NAVIGATION_PUSHINESS = PUSHINESS_MEDIUM;
+static const NavigationPushiness DEFAULT_AGENT_NAVIGATION_PUSHINESS = NAVIGATIONPUSHINESS_MEDIUM;
+
+static const unsigned SCOPE_NAVIGATION_QUALITY_PARAMS = 1;
+static const unsigned SCOPE_NAVIGATION_PUSHINESS_PARAMS = 2;
+static const unsigned SCOPE_BASE_PARAMS = M_MAX_UNSIGNED & ~SCOPE_NAVIGATION_QUALITY_PARAMS & ~SCOPE_NAVIGATION_PUSHINESS_PARAMS;
 
-const char* crowdAgentAvoidanceQualityNames[] = {
+static const char* crowdAgentRequestedTargetTypeNames[] = {
+    "none",
+    "position",
+    "velocity",
+    0
+};
+
+static const char* crowdAgentAvoidanceQualityNames[] = {
     "low",
     "medium",
     "high",
     0
 };
 
-const char* crowdAgentPushinessNames[] = {
+static const char* crowdAgentPushinessNames[] = {
     "low",
     "medium",
     "high",
     0
 };
 
-
 CrowdAgent::CrowdAgent(Context* context) :
     Component(context),
-    inCrowd_(false),
     agentCrowdId_(-1),
-    targetRef_(-1),
+    requestedTargetType_(DEFAULT_AGENT_REQUEST_TARGET_TYPE),
     updateNodePosition_(true),
     maxAccel_(DEFAULT_AGENT_MAX_ACCEL),
     maxSpeed_(DEFAULT_AGENT_MAX_SPEED),
     radius_(0.0f),
     height_(0.0f),
-    filterType_(DEFAULT_AGENT_NAVIGATION_FILTER_TYPE),
+    queryFilterType_(DEFAULT_AGENT_QUERY_FILTER_TYPE),
+    obstacleAvoidanceType_(DEFAULT_AGENT_OBSTACLE_AVOIDANCE_TYPE),
     navQuality_(DEFAULT_AGENT_AVOIDANCE_QUALITY),
     navPushiness_(DEFAULT_AGENT_NAVIGATION_PUSHINESS),
-    previousTargetState_(CROWD_AGENT_TARGET_NONE),
-    previousAgentState_(CROWD_AGENT_READY),
+    previousTargetState_(CA_TARGET_NONE),
+    previousAgentState_(CA_STATE_WALKING),
     ignoreTransformChanges_(false)
 {
 }
@@ -95,43 +103,58 @@ 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("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);
-    MIXED_ACCESSOR_ATTRIBUTE("Agent Data", GetAgentDataAttr, SetAgentDataAttr, PODVector<unsigned char>, Variant::emptyBuffer, AM_FILE | AM_NOEDIT);
-}
-
-void CrowdAgent::OnNodeSet(Node* node)
-{
-    if (node)
-        node->AddListener(this);
-}
-
-void CrowdAgent::OnSceneSet(Scene* scene)
-{
-    if (scene)
+    ATTRIBUTE("Target Position", Vector3, targetPosition_, Vector3::ZERO, AM_DEFAULT);
+    ATTRIBUTE("Target Velocity", Vector3, targetVelocity_, Vector3::ZERO, AM_DEFAULT);
+    ENUM_ATTRIBUTE("Requested Target Type", requestedTargetType_, crowdAgentRequestedTargetTypeNames,
+        DEFAULT_AGENT_REQUEST_TARGET_TYPE, AM_DEFAULT);
+    ACCESSOR_ATTRIBUTE("Update Node Position", GetUpdateNodePosition, SetUpdateNodePosition, bool, true, AM_DEFAULT);
+    ATTRIBUTE("Max Accel", float, maxAccel_, DEFAULT_AGENT_MAX_ACCEL, AM_DEFAULT);
+    ATTRIBUTE("Max Speed", float, maxSpeed_, DEFAULT_AGENT_MAX_SPEED, AM_DEFAULT);
+    ATTRIBUTE("Radius", float, radius_, 0.0f, AM_DEFAULT);
+    ATTRIBUTE("Height", float, height_, 0.0f, AM_DEFAULT);
+    ATTRIBUTE("Query Filter Type", unsigned, queryFilterType_, DEFAULT_AGENT_QUERY_FILTER_TYPE, AM_DEFAULT);
+    ATTRIBUTE("Obstacle Avoidance Type", unsigned, obstacleAvoidanceType_, DEFAULT_AGENT_OBSTACLE_AVOIDANCE_TYPE, AM_DEFAULT);
+    ENUM_ATTRIBUTE("Navigation Pushiness", navPushiness_, crowdAgentPushinessNames, DEFAULT_AGENT_NAVIGATION_PUSHINESS, AM_DEFAULT);
+    ENUM_ATTRIBUTE("Navigation Quality", navQuality_, crowdAgentAvoidanceQualityNames, DEFAULT_AGENT_AVOIDANCE_QUALITY, AM_DEFAULT);
+}
+
+void CrowdAgent::ApplyAttributes()
+{
+    // Values from Editor, saved-file, or network must be checked before applying
+    maxAccel_ = Max(0.f, maxAccel_);
+    maxSpeed_ = Max(0.f, maxSpeed_);
+    radius_ = Max(0.f, radius_);
+    height_ = Max(0.f, height_);
+    queryFilterType_ = Min(queryFilterType_, DT_CROWD_MAX_QUERY_FILTER_TYPE - 1);
+    obstacleAvoidanceType_ = Min(obstacleAvoidanceType_, DT_CROWD_MAX_OBSTAVOIDANCE_PARAMS - 1);
+
+    UpdateParameters();
+
+    // Set or reset target after we have attributes applied to the agent's parameters.
+    CrowdAgentRequestedTarget requestedTargetType = requestedTargetType_;
+    if (CA_REQUESTEDTARGET_NONE != requestedTargetType_)
     {
-        if (scene == node_)
-            LOGERROR(GetTypeName() + " should not be created to the root scene node");
-        crowdManager_ = scene->GetOrCreateComponent<DetourCrowdManager>();
-        AddAgentToCrowd();
+        // Assign a dummy value such that the value check in the setter method passes
+        requestedTargetType_ = CA_REQUESTEDTARGET_NONE;
+        if (requestedTargetType == CA_REQUESTEDTARGET_POSITION)
+            SetTargetPosition(targetPosition_);
+        else
+            SetTargetVelocity(targetVelocity_);
     }
     else
-        RemoveAgentFromCrowd();
+    {
+        requestedTargetType_ = CA_REQUESTEDTARGET_POSITION;
+        ResetTarget();
+    }
 }
 
 void CrowdAgent::OnSetEnabled()
 {
     bool enabled = IsEnabledEffective();
 
-    if (enabled && !inCrowd_)
+    if (enabled && !IsInCrowd())
         AddAgentToCrowd();
-    else if (!enabled && inCrowd_)
+    else if (!enabled && IsInCrowd())
         RemoveAgentFromCrowd();
 }
 
@@ -161,44 +184,112 @@ void CrowdAgent::DrawDebugGeometry(DebugRenderer* debug, bool depthTest)
     }
 }
 
-const dtCrowdAgent* CrowdAgent::GetDetourCrowdAgent() const
+void CrowdAgent::UpdateParameters(unsigned scope)
 {
-    return crowdManager_ && inCrowd_ ? crowdManager_->GetCrowdAgent(agentCrowdId_) : 0;
+    const dtCrowdAgent* agent = GetDetourCrowdAgent();
+    if (agent)
+    {
+        dtCrowdAgentParams params = agent->params;
+
+        if (scope & SCOPE_NAVIGATION_QUALITY_PARAMS)
+        {
+            switch (navQuality_)
+            {
+            case NAVIGATIONQUALITY_LOW:
+                params.updateFlags = 0
+                                     | DT_CROWD_OPTIMIZE_VIS
+                                     | DT_CROWD_ANTICIPATE_TURNS;
+                break;
+
+            case NAVIGATIONQUALITY_MEDIUM:
+                params.updateFlags = 0
+                                     | DT_CROWD_OPTIMIZE_TOPO
+                                     | DT_CROWD_OPTIMIZE_VIS
+                                     | DT_CROWD_ANTICIPATE_TURNS
+                                     | DT_CROWD_SEPARATION;
+                break;
+
+            case NAVIGATIONQUALITY_HIGH:
+                params.updateFlags = 0
+                                     // Path finding
+                                     | DT_CROWD_OPTIMIZE_TOPO
+                                     | DT_CROWD_OPTIMIZE_VIS
+                                     // Steering
+                                     | DT_CROWD_ANTICIPATE_TURNS
+                                     | DT_CROWD_SEPARATION
+                                     // Velocity planning
+                                     | DT_CROWD_OBSTACLE_AVOIDANCE;
+                break;
+            }
+        }
+
+        if (scope & SCOPE_NAVIGATION_PUSHINESS_PARAMS)
+        {
+            switch (navPushiness_)
+            {
+            case NAVIGATIONPUSHINESS_LOW:
+                params.separationWeight = 4.0f;
+                params.collisionQueryRange = radius_ * 16.0f;
+                break;
+
+            case NAVIGATIONPUSHINESS_MEDIUM:
+                params.separationWeight = 2.0f;
+                params.collisionQueryRange = radius_ * 8.0f;
+                break;
+
+            case NAVIGATIONPUSHINESS_HIGH:
+                params.separationWeight = 0.5f;
+                params.collisionQueryRange = radius_ * 1.0f;
+                break;
+            }
+        }
+
+        if (scope & SCOPE_BASE_PARAMS)
+        {
+            params.radius = radius_;
+            params.height = height_;
+            params.maxAcceleration = maxAccel_;
+            params.maxSpeed = maxSpeed_;
+            params.pathOptimizationRange = radius_ * 30.0f;
+            params.queryFilterType = (unsigned char)queryFilterType_;
+            params.obstacleAvoidanceType = (unsigned char)obstacleAvoidanceType_;
+        }
+
+        crowdManager_->GetCrowd()->updateAgentParameters(agentCrowdId_, &params);
+    }
 }
 
-void CrowdAgent::AddAgentToCrowd()
+int CrowdAgent::AddAgentToCrowd(bool force)
 {
-    if (!crowdManager_ || !crowdManager_->crowd_ || !node_)
-        return;
+    if (!node_ || !crowdManager_ || !crowdManager_->crowd_)
+        return -1;
 
-    PROFILE(AddAgentToCrowd);
-
-    if (!inCrowd_)
+    if (force || !IsInCrowd())
     {
-        inCrowd_ = true;
+        PROFILE(AddAgentToCrowd);
+
         agentCrowdId_ = crowdManager_->AddAgent(this, node_->GetPosition());
         if (agentCrowdId_ == -1)
-        {
-            inCrowd_ = false;
-            LOGERROR("AddAgentToCrowd: Could not add agent to crowd");
-            return;
-        }
-        crowdManager_->UpdateAgentNavigationQuality(this, navQuality_);
-        crowdManager_->UpdateAgentPushiness(this, navPushiness_);
+            return -1;
+
+        ApplyAttributes();
+
         previousAgentState_ = GetAgentState();
         previousTargetState_ = GetTargetState();
 
         // Agent created, but initial state is invalid and needs to be addressed
-        if (previousAgentState_ == CROWD_AGENT_INVALID)
+        if (previousAgentState_ == CA_STATE_INVALID)
         {
-            VariantMap& map = GetContext()->GetEventDataMap();
-            map[CrowdAgentFailure::P_NODE] = GetNode();
-            map[CrowdAgentFailure::P_CROWD_AGENT] = this;
-            map[CrowdAgentFailure::P_CROWD_TARGET_STATE] = previousTargetState_;
-            map[CrowdAgentFailure::P_CROWD_AGENT_STATE] = previousAgentState_;
-            map[CrowdAgentFailure::P_POSITION] = GetPosition();
-            map[CrowdAgentFailure::P_VELOCITY] = GetActualVelocity();
-            SendEvent(E_CROWD_AGENT_FAILURE, map);
+            using namespace CrowdAgentFailure;
+
+            VariantMap& map = GetEventDataMap();
+            map[P_NODE] = GetNode();
+            map[P_CROWD_AGENT] = this;
+            map[P_CROWD_TARGET_STATE] = previousTargetState_;
+            map[P_CROWD_AGENT_STATE] = previousAgentState_;
+            map[P_POSITION] = GetPosition();
+            map[P_VELOCITY] = GetActualVelocity();
+            crowdManager_->SendEvent(E_CROWD_AGENT_FAILURE, map);
 
             // Reevaluate states as handling of event may have resulted in changes
             previousAgentState_ = GetAgentState();
@@ -208,133 +299,162 @@ void CrowdAgent::AddAgentToCrowd()
         // Save the initial position to prevent CrowdAgentReposition event being triggered unnecessarily
         previousPosition_ = GetPosition();
     }
+
+    return agentCrowdId_;
 }
 
 void CrowdAgent::RemoveAgentFromCrowd()
 {
-    if (crowdManager_ && agentCrowdId_ != -1 && inCrowd_)
+    if (IsInCrowd())
     {
         crowdManager_->RemoveAgent(this);
-        inCrowd_ = false;
         agentCrowdId_ = -1;
     }
 }
 
-void CrowdAgent::SetNavigationFilterType(unsigned filterType)
+void CrowdAgent::SetTargetPosition(const Vector3& position)
 {
-    filterType_ = filterType;
-    if (crowdManager_ && inCrowd_)
+    if (position != targetPosition_ || CA_REQUESTEDTARGET_POSITION != requestedTargetType_)
     {
-        // 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);
+        targetPosition_ = position;
+        requestedTargetType_ = CA_REQUESTEDTARGET_POSITION;
         MarkNetworkUpdate();
+
+        if (!IsInCrowd())
+            AddAgentToCrowd();
+        if (IsInCrowd())   // Make sure the previous method call is successful
+        {
+            dtPolyRef nearestRef;
+            Vector3 nearestPos = crowdManager_->FindNearestPoint(position, queryFilterType_, &nearestRef);
+            crowdManager_->GetCrowd()->requestMoveTarget(agentCrowdId_, nearestRef, nearestPos.Data());
+        }
     }
 }
 
-void CrowdAgent::SetMoveTarget(const Vector3& position)
+void CrowdAgent::SetTargetVelocity(const Vector3& velocity)
 {
-    if (crowdManager_) {
-        if (!inCrowd_)
-            AddAgentToCrowd();
-        targetPosition_ = position;
-        if (crowdManager_->SetAgentTarget(this, position, targetRef_))
-            MarkNetworkUpdate();
+    if (velocity != targetVelocity_ || CA_REQUESTEDTARGET_VELOCITY != requestedTargetType_)
+    {
+        targetVelocity_ = velocity;
+        requestedTargetType_ = CA_REQUESTEDTARGET_VELOCITY;
+        MarkNetworkUpdate();
+
+        if (IsInCrowd())
+            crowdManager_->GetCrowd()->requestMoveVelocity(agentCrowdId_, velocity.Data());
     }
 }
 
-void CrowdAgent::ResetMoveTarget()
+void CrowdAgent::ResetTarget()
 {
-    const dtCrowdAgent* agent = GetDetourCrowdAgent();
-    if (agent && agent->active)
+    if (CA_REQUESTEDTARGET_NONE != requestedTargetType_)
     {
-        targetPosition_ = Vector3::ZERO;
-        crowdManager_->GetCrowd()->resetMoveTarget(agentCrowdId_);
+        requestedTargetType_ = CA_REQUESTEDTARGET_NONE;
         MarkNetworkUpdate();
+
+        if (IsInCrowd())
+            crowdManager_->GetCrowd()->resetMoveTarget(agentCrowdId_);
     }
 }
 
-void CrowdAgent::SetMoveVelocity(const Vector3& velocity)
+void CrowdAgent::SetUpdateNodePosition(bool unodepos)
 {
-    const dtCrowdAgent* agent = GetDetourCrowdAgent();
-    if (agent && agent->active)
+    if (unodepos != updateNodePosition_)
     {
-        crowdManager_->GetCrowd()->requestMoveVelocity(agentCrowdId_, velocity.Data());
+        updateNodePosition_ = unodepos;
         MarkNetworkUpdate();
     }
 }
 
-void CrowdAgent::SetMaxSpeed(float speed)
+void CrowdAgent::SetMaxAccel(float maxAccel)
 {
-    const dtCrowdAgent* agent = GetDetourCrowdAgent();
-    if (agent)
+    if (maxAccel != maxAccel_ && maxAccel >= 0.f)
     {
-        maxSpeed_ = speed;
-        dtCrowdAgentParams params = agent->params;
-        params.maxSpeed = speed;
-        crowdManager_->GetCrowd()->updateAgentParameters(agentCrowdId_, &params);
+        maxAccel_ = maxAccel;
+        UpdateParameters(SCOPE_BASE_PARAMS);
         MarkNetworkUpdate();
     }
 }
 
-void CrowdAgent::SetMaxAccel(float accel)
+void CrowdAgent::SetMaxSpeed(float maxSpeed)
 {
-    const dtCrowdAgent* agent = GetDetourCrowdAgent();
-    if (agent)
+    if (maxSpeed != maxSpeed_ && maxSpeed >= 0.f)
     {
-        maxAccel_ = accel;
-        dtCrowdAgentParams params = agent->params;
-        params.maxAcceleration = accel;
-        crowdManager_->GetCrowd()->updateAgentParameters(agentCrowdId_, &params);
+        maxSpeed_ = maxSpeed;
+        UpdateParameters(SCOPE_BASE_PARAMS);
         MarkNetworkUpdate();
     }
 }
 
 void CrowdAgent::SetRadius(float radius)
 {
-    const dtCrowdAgent* agent = GetDetourCrowdAgent();
-    if (agent)
+    if (radius != radius_ && radius > 0.f)
     {
         radius_ = radius;
-        dtCrowdAgentParams params = agent->params;
-        params.radius = radius;
-        crowdManager_->GetCrowd()->updateAgentParameters(agentCrowdId_, &params);
+        UpdateParameters(SCOPE_BASE_PARAMS | SCOPE_NAVIGATION_PUSHINESS_PARAMS);
         MarkNetworkUpdate();
     }
 }
 
 void CrowdAgent::SetHeight(float height)
 {
-    const dtCrowdAgent* agent = GetDetourCrowdAgent();
-    if (agent)
+    if (height != height_ && height > 0.f)
     {
         height_ = height;
-        dtCrowdAgentParams params = agent->params;
-        params.height = height;
-        crowdManager_->GetCrowd()->updateAgentParameters(agentCrowdId_, &params);
+        UpdateParameters(SCOPE_BASE_PARAMS);
+        MarkNetworkUpdate();
+    }
+}
+
+void CrowdAgent::SetQueryFilterType(unsigned queryFilterType)
+{
+    if (queryFilterType != queryFilterType_)
+    {
+        if (queryFilterType >= DT_CROWD_MAX_QUERY_FILTER_TYPE)
+        {
+            LOGERRORF("The specified filter type index (%d) exceeds the maximum allowed value (%d)", queryFilterType,
+                DT_CROWD_MAX_QUERY_FILTER_TYPE);
+            return;
+        }
+
+        queryFilterType_ = queryFilterType;
+        UpdateParameters(SCOPE_BASE_PARAMS);
+        MarkNetworkUpdate();
+    }
+}
+
+void CrowdAgent::SetObstacleAvoidanceType(unsigned obstacleAvoidanceType)
+{
+    if (obstacleAvoidanceType != obstacleAvoidanceType_)
+    {
+        if (obstacleAvoidanceType >= DT_CROWD_MAX_OBSTAVOIDANCE_PARAMS)
+        {
+            LOGERRORF("The specified obstacle avoidance type index (%d) exceeds the maximum allowed value (%d)",
+                obstacleAvoidanceType, DT_CROWD_MAX_OBSTAVOIDANCE_PARAMS);
+            return;
+        }
+
+        obstacleAvoidanceType_ = obstacleAvoidanceType;
+        UpdateParameters(SCOPE_BASE_PARAMS);
         MarkNetworkUpdate();
     }
 }
 
 void CrowdAgent::SetNavigationQuality(NavigationQuality val)
 {
-    const dtCrowdAgent* agent = GetDetourCrowdAgent();
-    if (agent)
+    if (val != navQuality_)
     {
         navQuality_ = val;
-        crowdManager_->UpdateAgentNavigationQuality(this, navQuality_);
+        UpdateParameters(SCOPE_NAVIGATION_QUALITY_PARAMS);
         MarkNetworkUpdate();
     }
 }
 
 void CrowdAgent::SetNavigationPushiness(NavigationPushiness val)
 {
-    const dtCrowdAgent* agent = GetDetourCrowdAgent();
-    if (agent)
+    if (val != navPushiness_)
     {
         navPushiness_ = val;
-        crowdManager_->UpdateAgentPushiness(this, navPushiness_);
+        UpdateParameters(SCOPE_NAVIGATION_PUSHINESS_PARAMS);
         MarkNetworkUpdate();
     }
 }
@@ -342,64 +462,70 @@ void CrowdAgent::SetNavigationPushiness(NavigationPushiness val)
 Vector3 CrowdAgent::GetPosition() const
 {
     const dtCrowdAgent* agent = GetDetourCrowdAgent();
-    return agent && agent->active ? Vector3(agent->npos) : node_->GetPosition();
+    return agent ? Vector3(agent->npos) : node_->GetPosition();
 }
 
 Vector3 CrowdAgent::GetDesiredVelocity() const
 {
     const dtCrowdAgent* agent = GetDetourCrowdAgent();
-    return agent && agent->active ? Vector3(agent->dvel) : Vector3::ZERO;
+    return agent ? Vector3(agent->dvel) : Vector3::ZERO;
 }
 
 Vector3 CrowdAgent::GetActualVelocity() const
 {
     const dtCrowdAgent* agent = GetDetourCrowdAgent();
-    return agent && agent->active ? Vector3(agent->vel) : Vector3::ZERO;
+    return agent ? Vector3(agent->vel) : Vector3::ZERO;
 }
 
-Atomic::CrowdAgentState CrowdAgent::GetAgentState() const
+CrowdAgentState CrowdAgent::GetAgentState() const
 {
     const dtCrowdAgent* agent = GetDetourCrowdAgent();
-    return agent && agent->active ? (CrowdAgentState)agent->state : CROWD_AGENT_INVALID;
+    return agent ? (CrowdAgentState)agent->state : CA_STATE_INVALID;
 }
 
-Atomic::CrowdTargetState CrowdAgent::GetTargetState() const
+CrowdAgentTargetState CrowdAgent::GetTargetState() const
 {
     const dtCrowdAgent* agent = GetDetourCrowdAgent();
-    return agent && agent->active ? (CrowdTargetState)agent->targetState : CROWD_AGENT_TARGET_NONE;
+    return agent ? (CrowdAgentTargetState)agent->targetState : CA_TARGET_NONE;
 }
 
 bool CrowdAgent::HasArrived() const
 {
-    // Is the agent at or near the end of its path?
+    // Is the agent at or near the end of its path and within its own radius of the goal?
     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)));
+    return agent && (!agent->ncorners || (agent->cornerFlags[agent->ncorners - 1] & DT_STRAIGHTPATH_END &&
+                                          dtVdist2D(agent->npos, &agent->cornerVerts[(agent->ncorners - 1) * 3]) <=
+                                          agent->params.radius));
 }
 
-void CrowdAgent::SetUpdateNodePosition(bool unodepos)
+bool CrowdAgent::IsInCrowd() const
 {
-    updateNodePosition_ = unodepos;
-    MarkNetworkUpdate();
+    return crowdManager_ && agentCrowdId_ != -1;
 }
 
-void CrowdAgent::OnCrowdAgentReposition(const Vector3& newPos, const Vector3& newVel)
+void CrowdAgent::OnCrowdUpdate(dtCrowdAgent* ag, float dt)
 {
+    assert (ag);
     if (node_)
     {
+        Vector3 newPos(ag->npos);
+        Vector3 newVel(ag->vel);
+
         // Notify parent node of the reposition
         if (newPos != previousPosition_)
         {
             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);
+            using namespace CrowdAgentReposition;
+
+            VariantMap& map = GetEventDataMap();
+            map[P_NODE] = node_;
+            map[P_CROWD_AGENT] = this;
+            map[P_POSITION] = newPos;
+            map[P_VELOCITY] = newVel;
+            map[P_ARRIVED] = HasArrived();
+            map[P_TIMESTEP] = dt;
+            crowdManager_->SendEvent(E_CROWD_AGENT_REPOSITION, map);
 
             if (updateNodePosition_)
             {
@@ -410,30 +536,32 @@ void CrowdAgent::OnCrowdAgentReposition(const Vector3& newPos, const Vector3& ne
         }
 
         // Send a notification event if we've reached the destination
-        CrowdTargetState newTargetState = GetTargetState();
+        CrowdAgentTargetState newTargetState = GetTargetState();
         CrowdAgentState newAgentState = GetAgentState();
         if (newAgentState != previousAgentState_ || newTargetState != previousTargetState_)
         {
-            VariantMap& map = GetContext()->GetEventDataMap();
-            map[CrowdAgentStateChanged::P_NODE] = GetNode();
-            map[CrowdAgentStateChanged::P_CROWD_AGENT] = this;
-            map[CrowdAgentStateChanged::P_CROWD_TARGET_STATE] = newTargetState;
-            map[CrowdAgentStateChanged::P_CROWD_AGENT_STATE] = newAgentState;
-            map[CrowdAgentStateChanged::P_POSITION] = newPos;
-            map[CrowdAgentStateChanged::P_VELOCITY] = newVel;
-            SendEvent(E_CROWD_AGENT_STATE_CHANGED, map);
+            using namespace CrowdAgentStateChanged;
+
+            VariantMap& map = GetEventDataMap();
+            map[P_NODE] = node_;
+            map[P_CROWD_AGENT] = this;
+            map[P_CROWD_TARGET_STATE] = newTargetState;
+            map[P_CROWD_AGENT_STATE] = newAgentState;
+            map[P_POSITION] = newPos;
+            map[P_VELOCITY] = newVel;
+            crowdManager_->SendEvent(E_CROWD_AGENT_STATE_CHANGED, map);
 
             // Send a failure event if either state is a failed status
-            if (newAgentState == CROWD_AGENT_INVALID || newTargetState == CROWD_AGENT_TARGET_FAILED)
+            if (newAgentState == CA_STATE_INVALID || newTargetState == CA_TARGET_FAILED)
             {
-                VariantMap& map = GetContext()->GetEventDataMap();
-                map[CrowdAgentFailure::P_NODE] = GetNode();
-                map[CrowdAgentFailure::P_CROWD_AGENT] = this;
-                map[CrowdAgentFailure::P_CROWD_TARGET_STATE] = newTargetState;
-                map[CrowdAgentFailure::P_CROWD_AGENT_STATE] = newAgentState;
-                map[CrowdAgentFailure::P_POSITION] = newPos;
-                map[CrowdAgentFailure::P_VELOCITY] = newVel;
-                SendEvent(E_CROWD_AGENT_FAILURE, map);
+                VariantMap& map = GetEventDataMap();
+                map[P_NODE] = node_;
+                map[P_CROWD_AGENT] = this;
+                map[P_CROWD_TARGET_STATE] = newTargetState;
+                map[P_CROWD_AGENT_STATE] = newAgentState;
+                map[P_POSITION] = newPos;
+                map[P_VELOCITY] = newVel;
+                crowdManager_->SendEvent(E_CROWD_AGENT_FAILURE, map);
             }
 
             // State may have been altered during the handling of the event
@@ -443,43 +571,23 @@ void CrowdAgent::OnCrowdAgentReposition(const Vector3& newPos, const Vector3& ne
     }
 }
 
-PODVector<unsigned char> CrowdAgent::GetAgentDataAttr() const
+void CrowdAgent::OnNodeSet(Node* node)
 {
-    const dtCrowdAgent* agent = GetDetourCrowdAgent();
-    if (!agent)
-        return Variant::emptyBuffer;
-
-    // Reading it back in isn't this simple, see SetAgentDataAttr
-    VectorBuffer ret;
-    ret.Write(agent, sizeof(dtCrowdAgent));
-
-    return ret.GetBuffer();
+    if (node)
+        node->AddListener(this);
 }
 
-void CrowdAgent::SetAgentDataAttr(const PODVector<unsigned char>& value)
+void CrowdAgent::OnSceneSet(Scene* scene)
 {
-    if (value.Empty())
-        return;
-
-    dtCrowdAgent* agent = const_cast<dtCrowdAgent*>(GetDetourCrowdAgent());
-    if (!agent)
-        return;
-
-    MemoryBuffer buffer(value);
-
-    // 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;
+    if (scene)
+    {
+        if (scene == node_)
+            LOGERROR(GetTypeName() + " should not be created to the root scene node");
+        crowdManager_ = scene->GetOrCreateComponent<CrowdManager>();
+        AddAgentToCrowd();
+    }
+    else
+        RemoveAgentFromCrowd();
 }
 
 void CrowdAgent::OnMarkedDirty(Node* node)
@@ -492,10 +600,15 @@ void CrowdAgent::OnMarkedDirty(Node* node)
             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 (agent->state == CROWD_AGENT_INVALID)
-                agent->state = CROWD_AGENT_READY;
+            if (agent->state == CA_STATE_INVALID)
+                agent->state = CA_STATE_WALKING;
         }
     }
 }
 
+const dtCrowdAgent* CrowdAgent::GetDetourCrowdAgent() const
+{
+    return IsInCrowd() ? crowdManager_->GetDetourCrowdAgent(agentCrowdId_) : 0;
+}
+
 }

+ 110 - 57
Source/Atomic/Navigation/CrowdAgent.h

@@ -22,35 +22,58 @@
 
 #pragma once
 
+#include "../Navigation/CrowdManager.h"
 #include "../Scene/Component.h"
-#include "../Navigation/DetourCrowdManager.h"
 
 namespace Atomic
 {
 
-enum CrowdTargetState
+enum CrowdAgentRequestedTarget
 {
-    CROWD_AGENT_TARGET_NONE = 0,
-    CROWD_AGENT_TARGET_FAILED,
-    CROWD_AGENT_TARGET_VALID,
-    CROWD_AGENT_TARGET_REQUESTING,
-    CROWD_AGENT_TARGET_WAITINGFORQUEUE,
-    CROWD_AGENT_TARGET_WAITINGFORPATH,
-    CROWD_AGENT_TARGET_VELOCITY
+    CA_REQUESTEDTARGET_NONE = 0,
+    CA_REQUESTEDTARGET_POSITION,
+    CA_REQUESTEDTARGET_VELOCITY
+};
+
+enum CrowdAgentTargetState
+{
+    CA_TARGET_NONE = 0,
+    CA_TARGET_FAILED,
+    CA_TARGET_VALID,
+    CA_TARGET_REQUESTING,
+    CA_TARGET_WAITINGFORQUEUE,
+    CA_TARGET_WAITINGFORPATH,
+    CA_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.
+    CA_STATE_INVALID = 0,   ///< The agent is not in a valid state.
+    CA_STATE_WALKING,       ///< The agent is traversing a normal navigation mesh polygon.
+    CA_STATE_OFFMESH        ///< The agent is traversing an off-mesh connection.
+};
+
+enum NavigationQuality
+{
+    NAVIGATIONQUALITY_LOW = 0,
+    NAVIGATIONQUALITY_MEDIUM = 1,
+    NAVIGATIONQUALITY_HIGH = 2
+};
+
+enum NavigationPushiness
+{
+    NAVIGATIONPUSHINESS_LOW = 0,
+    NAVIGATIONPUSHINESS_MEDIUM,
+    NAVIGATIONPUSHINESS_HIGH
 };
 
-/// DetourCrowd Agent, requires a DetourCrowdManager in the scene. Agent's radius and height is set through the navigation mesh.
+/// Crowd agent component, requires a CrowdManager component in the scene. When not set explicitly, agent's radius and height are defaulted to navigation mesh's agent radius and height, respectively.
 class ATOMIC_API CrowdAgent : public Component
 {
     OBJECT(CrowdAgent);
-    friend class DetourCrowdManager;
+
+    friend class CrowdManager;
+    friend void CrowdAgentUpdateCallback(dtCrowdAgent* ag, float dt);
 
 public:
     /// Construct.
@@ -59,6 +82,8 @@ public:
     virtual ~CrowdAgent();
     /// Register object factory.
     static void RegisterObject(Context* context);
+    /// Apply attribute changes that can not be applied immediately. Called after scene load or a network update.
+    virtual void ApplyAttributes();
 
     /// Handle enabled/disabled state change.
     virtual void OnSetEnabled();
@@ -67,70 +92,93 @@ public:
     /// Draw debug feelers.
     virtual void DrawDebugGeometry(DebugRenderer* debug, bool depthTest);
 
-    /// Set the navigation filter type the agent will use.
-    void SetNavigationFilterType(unsigned filterTypeID);
-    /// Submit a new move request for this agent.
-    void SetMoveTarget(const Vector3& position);
-    /// Reset any request for the specified agent.
-    void ResetMoveTarget();
-    /// Submit a new move velocity request for this agent.
-    void SetMoveVelocity(const Vector3& velocity);
+    /// Submit a new target position request for this agent.
+    void SetTargetPosition(const Vector3& position);
+    /// Submit a new target velocity request for this agent.
+    void SetTargetVelocity(const Vector3& velocity);
+    /// Reset any target request for the specified agent.
+    void ResetTarget();
     /// 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.
-    void SetMaxAccel(float val);
+    void SetMaxAccel(float maxAccel);
     /// Set the agent's max velocity.
-    void SetMaxSpeed(float val);
+    void SetMaxSpeed(float maxSpeed);
     /// Set the agent's radius.
-    void SetRadius(float val);
+    void SetRadius(float radius);
     /// Set the agent's height.
-    void SetHeight(float val);
+    void SetHeight(float height);
+    /// Set the agent's query filter type.
+    void SetQueryFilterType(unsigned queryFilterType);
+    /// Set the agent's obstacle avoidance type.
+    void SetObstacleAvoidanceType(unsigned obstacleAvoidanceType);
     /// Set the agent's navigation quality.
     void SetNavigationQuality(NavigationQuality val);
     /// Set the agent's navigation pushiness.
     void SetNavigationPushiness(NavigationPushiness val);
 
-    /// Get the navigation filter type this agent is using.
-    unsigned GetNavigationFilterType() const { return filterType_; }
     /// Return the agent's position.
     Vector3 GetPosition() const;
     /// Return the agent's desired velocity.
     Vector3 GetDesiredVelocity() const;
     /// Return the agent's actual velocity.
     Vector3 GetActualVelocity() const;
-    /// Return the agent's target position.
+
+    /// Return the agent's requested target position.
     const Vector3& GetTargetPosition() const { return targetPosition_; }
+
+    /// Return the agent's requested target velocity.
+    const Vector3& GetTargetVelocity() const { return targetVelocity_; }
+
+    /// Return the agent's requested target type, if any.
+    CrowdAgentRequestedTarget GetRequestedTargetType() const { return requestedTargetType_; }
+
     /// Return the agent's  state.
     CrowdAgentState GetAgentState() const;
     /// Return the agent's target state.
-    CrowdTargetState GetTargetState() const;
+    CrowdAgentTargetState GetTargetState() const;
+
     /// Return true when the node's position should be updated by the CrowdManager.
     bool GetUpdateNodePosition() const { return updateNodePosition_; }
+
     /// Return the agent id.
     int GetAgentCrowdId() const { return agentCrowdId_; }
-    /// Get the agent's max velocity.
-    float GetMaxSpeed() const { return maxSpeed_; }
+
     /// Get the agent's max acceleration.
     float GetMaxAccel() const { return maxAccel_; }
+
+    /// Get the agent's max velocity.
+    float GetMaxSpeed() const { return maxSpeed_; }
+
     /// Get the agent's radius.
     float GetRadius() const { return radius_; }
+
     /// Get the agent's height.
     float GetHeight() const { return height_; }
+
+    /// Get the agent's query filter type.
+    unsigned GetQueryFilterType() const { return queryFilterType_; }
+
+    /// Get the agent's obstacle avoidance type.
+    unsigned GetObstacleAvoidanceType() const { return obstacleAvoidanceType_; }
+
     /// Get the agent's navigation quality.
-    NavigationQuality GetNavigationQuality() const {return navQuality_; }
+    NavigationQuality GetNavigationQuality() const { return navQuality_; }
+
     /// Get the agent's navigation pushiness.
-    NavigationPushiness GetNavigationPushiness() const {return navPushiness_; }
+    NavigationPushiness GetNavigationPushiness() const { return navPushiness_; }
+
+    /// Return true when the agent has a target.
+    bool HasRequestedTarget() const { return requestedTargetType_ != CA_REQUESTEDTARGET_NONE; }
+
     /// Return true when the agent has arrived at its target.
     bool HasArrived() const;
-
-    /// Get serialized data of internal state.
-    PODVector<unsigned char> GetAgentDataAttr() const;
-    /// Set serialized data of internal state.
-    void SetAgentDataAttr(const PODVector<unsigned char>& value);
+    /// Return true when the agent is in crowd (being managed by a crowd manager).
+    bool IsInCrowd() const;
 
 protected:
-    /// Update the nodes position if updateNodePosition is set. Is called in DetourCrowdManager::Update().
-    virtual void OnCrowdAgentReposition(const Vector3& newPos, const Vector3& newVel);
+    /// Handle crowd agent being updated. It is called by CrowdManager::Update() via callback.
+    virtual void OnCrowdUpdate(dtCrowdAgent* ag, float dt);
     /// Handle node being assigned.
     virtual void OnNodeSet(Node* node);
     /// Handle node being assigned.
@@ -139,21 +187,24 @@ protected:
     virtual void OnMarkedDirty(Node* node);
     /// Get internal Detour crowd agent.
     const dtCrowdAgent* GetDetourCrowdAgent() const;
+
 private:
-    /// Create or re-add.
-    void AddAgentToCrowd();
-    /// Remove.
+    /// Update Detour crowd agent parameter.
+    void UpdateParameters(unsigned scope = M_MAX_UNSIGNED);
+    /// Add agent into crowd.
+    int AddAgentToCrowd(bool force = false);
+    /// Remove agent from crowd.
     void RemoveAgentFromCrowd();
-    /// Detour crowd manager.
-    WeakPtr<DetourCrowdManager> crowdManager_;
-    /// Flag indicating agent is in DetourCrowd.
-    bool inCrowd_;
-    /// DetourCrowd reference to this agent.
+    /// Crowd manager.
+    WeakPtr<CrowdManager> crowdManager_;
+    /// Crowd manager reference to this agent.
     int agentCrowdId_;
-    /// Reference to poly closest to requested target position.
-    unsigned targetRef_;
-    /// Actual target position, closest to that requested.
+    /// Requested target position.
     Vector3 targetPosition_;
+    /// Requested target velocity.
+    Vector3 targetVelocity_;
+    /// Requested target type.
+    CrowdAgentRequestedTarget requestedTargetType_;
     /// Flag indicating the node's position should be updated by Detour crowd manager.
     bool updateNodePosition_;
     /// Agent's max acceleration.
@@ -164,16 +215,18 @@ private:
     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.
+    /// Agent's query filter type, it is an index to the query filter buffer configured in Detour crowd manager.
+    unsigned queryFilterType_;
+    /// Agent's obstacle avoidance type, it is an index to the obstacle avoidance array configured in Detour crowd manager. It is ignored when agent's navigation quality is not set to "NAVIGATIONQUALITY_HIGH".
+    unsigned obstacleAvoidanceType_;
+    /// Agent's navigation quality. The higher the setting, the higher the CPU usage during crowd simulation.
     NavigationQuality navQuality_;
-    /// Agent's Navigation Pushiness.
+    /// Agent's navigation pushiness. The higher the setting, the stronger the agent pushes its colliding neighbours around.
     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_;
+    CrowdAgentTargetState previousTargetState_;
     /// Agent's previous agent state used to check for state changes.
     CrowdAgentState previousAgentState_;
     /// Internal flag to ignore transform changes because it came from us, used in OnCrowdAgentReposition().

+ 690 - 0
Source/Atomic/Navigation/CrowdManager.cpp

@@ -0,0 +1,690 @@
+//
+// 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 "../Core/Profiler.h"
+#include "../Graphics/DebugRenderer.h"
+#include "../IO/Log.h"
+#include "../Navigation/CrowdAgent.h"
+#include "../Navigation/CrowdManager.h"
+#include "../Navigation/DynamicNavigationMesh.h"
+#include "../Navigation/NavigationEvents.h"
+#include "../Scene/Node.h"
+#include "../Scene/Scene.h"
+#include "../Scene/SceneEvents.h"
+
+#include <DetourCrowd/include/DetourCrowd.h>
+
+#include "../DebugNew.h"
+
+namespace Atomic
+{
+
+extern const char* NAVIGATION_CATEGORY;
+
+static const unsigned DEFAULT_MAX_AGENTS = 512;
+static const float DEFAULT_MAX_AGENT_RADIUS = 0.f;
+
+void CrowdAgentUpdateCallback(dtCrowdAgent* ag, float dt)
+{
+    static_cast<CrowdAgent*>(ag->params.userData)->OnCrowdUpdate(ag, dt);
+}
+
+CrowdManager::CrowdManager(Context* context) :
+    Component(context),
+    crowd_(0),
+    navigationMesh_(0),
+    navigationMeshId_(0),
+    maxAgents_(DEFAULT_MAX_AGENTS),
+    maxAgentRadius_(DEFAULT_MAX_AGENT_RADIUS),
+    numQueryFilterTypes_(0),
+    numObstacleAvoidanceTypes_(0)
+{
+    // The actual buffer is allocated inside dtCrowd, we only track the number of "slots" being configured explicitly
+    numAreas_.Reserve(DT_CROWD_MAX_QUERY_FILTER_TYPE);
+    for (unsigned i = 0; i < DT_CROWD_MAX_QUERY_FILTER_TYPE; ++i)
+        numAreas_.Push(0);
+}
+
+CrowdManager::~CrowdManager()
+{
+    dtFreeCrowd(crowd_);
+    crowd_ = 0;
+}
+
+void CrowdManager::RegisterObject(Context* context)
+{
+    context->RegisterFactory<CrowdManager>(NAVIGATION_CATEGORY);
+
+    ATTRIBUTE("Max Agents", unsigned, maxAgents_, DEFAULT_MAX_AGENTS, AM_DEFAULT);
+    ATTRIBUTE("Max Agent Radius", float, maxAgentRadius_, DEFAULT_MAX_AGENT_RADIUS, AM_DEFAULT);
+    ATTRIBUTE("Navigation Mesh", unsigned, navigationMeshId_, 0, AM_DEFAULT | AM_COMPONENTID);
+    MIXED_ACCESSOR_ATTRIBUTE("Filter Types", GetQueryFilterTypesAttr, SetQueryFilterTypesAttr, VariantVector,
+        Variant::emptyVariantVector, AM_DEFAULT);
+    MIXED_ACCESSOR_ATTRIBUTE("Obstacle Avoidance Types", GetObstacleAvoidanceTypesAttr, SetObstacleAvoidanceTypesAttr,
+        VariantVector, Variant::emptyVariantVector, AM_DEFAULT);
+}
+
+void CrowdManager::ApplyAttributes()
+{
+    // Values from Editor, saved-file, or network must be checked before applying
+    maxAgents_ = (unsigned)Max(1, maxAgents_);
+    maxAgentRadius_ = Max(0.f, maxAgentRadius_);
+
+    bool navMeshChange = false;
+    Scene* scene = GetScene();
+    if (scene && navigationMeshId_)
+    {
+        NavigationMesh* navMesh = dynamic_cast<NavigationMesh*>(scene->GetComponent(navigationMeshId_));
+        if (navMesh)
+        {
+            navMeshChange = navMesh != navigationMesh_;
+            navigationMesh_ = navMesh;
+        }
+    }
+    // In case of receiving an invalid component id, revert it back to the existing navmesh component id (if any)
+    navigationMeshId_ = navigationMesh_ ? navigationMesh_->GetID() : 0;
+
+    // If the Detour crowd initialization parameters have changed then recreate it
+    if (crowd_ && (navMeshChange || crowd_->getAgentCount() != maxAgents_ || crowd_->getMaxAgentRadius() != maxAgentRadius_))
+        CreateCrowd();
+}
+
+void CrowdManager::DrawDebugGeometry(DebugRenderer* debug, bool depthTest)
+{
+    if (debug && crowd_)
+    {
+        // Current position-to-target line
+        for (int i = 0; i < crowd_->getAgentCount(); i++)
+        {
+            const dtCrowdAgent* ag = crowd_->getAgent(i);
+            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() == CA_TARGET_NONE)
+                continue;
+
+            Color color(0.6f, 0.2f, 0.2f, 1.0f);
+
+            // 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)
+            {
+                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);
+
+            // Draw target circle
+            debug->AddSphere(Sphere(pos2, 0.5f), color, depthTest);
+        }
+    }
+}
+
+void CrowdManager::DrawDebugGeometry(bool depthTest)
+{
+    Scene* scene = GetScene();
+    if (scene)
+    {
+        DebugRenderer* debug = scene->GetComponent<DebugRenderer>();
+        if (debug)
+            DrawDebugGeometry(debug, depthTest);
+    }
+}
+
+void CrowdManager::SetCrowdTarget(const Vector3& position, Node* node)
+{
+    if (!crowd_)
+        return;
+
+    PODVector<CrowdAgent*> agents = GetAgents(node, false);     // Get all crowd agent components
+    Vector3 moveTarget(position);
+    for (unsigned i = 0; i < agents.Size(); ++i)
+    {
+        // Give application a chance to determine the desired crowd formation when they reach the target position
+        CrowdAgent* agent = agents[i];
+
+        using namespace CrowdAgentFormation;
+
+        VariantMap& map = GetEventDataMap();
+        map[P_NODE] = agent->GetNode();
+        map[P_CROWD_AGENT] = agent;
+        map[P_INDEX] = i;
+        map[P_SIZE] = agents.Size();
+        map[P_POSITION] = moveTarget;   // Expect the event handler will modify this position accordingly
+
+        SendEvent(E_CROWD_AGENT_FORMATION, map);
+
+        moveTarget = map[P_POSITION].GetVector3();
+        agent->SetTargetPosition(moveTarget);
+    }
+}
+
+void CrowdManager::SetCrowdVelocity(const Vector3& velocity, Node* node)
+{
+    if (!crowd_)
+        return;
+
+    PODVector<CrowdAgent*> agents = GetAgents(node, true);      // Get only crowd agent components already in the crowd
+    for (unsigned i = 0; i < agents.Size(); ++i)
+        agents[i]->SetTargetVelocity(velocity);
+}
+
+void CrowdManager::ResetCrowdTarget(Node* node)
+{
+    if (!crowd_)
+        return;
+
+    PODVector<CrowdAgent*> agents = GetAgents(node, true);
+    for (unsigned i = 0; i < agents.Size(); ++i)
+        agents[i]->ResetTarget();
+}
+
+void CrowdManager::SetMaxAgents(unsigned maxAgents)
+{
+    if (maxAgents != maxAgents_ && maxAgents > 0)
+    {
+        maxAgents_ = maxAgents;
+        CreateCrowd();
+        MarkNetworkUpdate();
+    }
+}
+
+void CrowdManager::SetMaxAgentRadius(float maxAgentRadius)
+{
+    if (maxAgentRadius != maxAgentRadius_ && maxAgentRadius > 0.f)
+    {
+        maxAgentRadius_ = maxAgentRadius;
+        CreateCrowd();
+        MarkNetworkUpdate();
+    }
+}
+
+void CrowdManager::SetNavigationMesh(NavigationMesh* navMesh)
+{
+    if (navMesh != navigationMesh_)     // It is possible to reset navmesh pointer back to 0
+    {
+        navigationMesh_ = navMesh;
+        navigationMeshId_ = navMesh ? navMesh->GetID() : 0;
+        CreateCrowd();
+        MarkNetworkUpdate();
+    }
+}
+
+void CrowdManager::SetQueryFilterTypesAttr(const VariantVector& value)
+{
+    if (!crowd_)
+        return;
+
+    unsigned index = 0;
+    unsigned queryFilterType = 0;
+    numQueryFilterTypes_ = index < value.Size() ? Min(value[index++].GetUInt(), DT_CROWD_MAX_QUERY_FILTER_TYPE) : 0;
+
+    while (queryFilterType < numQueryFilterTypes_)
+    {
+        if (index + 3 <= value.Size())
+        {
+            dtQueryFilter* filter = crowd_->getEditableFilter(queryFilterType);
+            assert(filter);
+            filter->setIncludeFlags((unsigned short)value[index++].GetUInt());
+            filter->setExcludeFlags((unsigned short)value[index++].GetUInt());
+            unsigned prevNumAreas = numAreas_[queryFilterType];
+            numAreas_[queryFilterType] = Min(value[index++].GetUInt(), DT_MAX_AREAS);
+
+            // Must loop thru based on previous number of areas, the new area cost (if any) can only be set in the next attribute get/set iteration
+            if (index + prevNumAreas <= value.Size())
+            {
+                for (int i = 0; i < prevNumAreas; ++i)
+                    filter->setAreaCost(i, value[index++].GetFloat());
+            }
+        }
+        ++queryFilterType;
+    }
+}
+
+void CrowdManager::SetIncludeFlags(unsigned queryFilterType, unsigned short flags)
+{
+    dtQueryFilter* filter = const_cast<dtQueryFilter*>(GetDetourQueryFilter(queryFilterType));
+    if (filter)
+    {
+        filter->setIncludeFlags(flags);
+        if (numQueryFilterTypes_ < queryFilterType + 1)
+            numQueryFilterTypes_ = queryFilterType + 1;
+        MarkNetworkUpdate();
+    }
+}
+
+void CrowdManager::SetExcludeFlags(unsigned queryFilterType, unsigned short flags)
+{
+    dtQueryFilter* filter = const_cast<dtQueryFilter*>(GetDetourQueryFilter(queryFilterType));
+    if (filter)
+    {
+        filter->setExcludeFlags(flags);
+        if (numQueryFilterTypes_ < queryFilterType + 1)
+            numQueryFilterTypes_ = queryFilterType + 1;
+        MarkNetworkUpdate();
+    }
+}
+
+void CrowdManager::SetAreaCost(unsigned queryFilterType, unsigned areaID, float cost)
+{
+    dtQueryFilter* filter = const_cast<dtQueryFilter*>(GetDetourQueryFilter(queryFilterType));
+    if (filter && areaID < DT_MAX_AREAS)
+    {
+        filter->setAreaCost((int)areaID, cost);
+        if (numQueryFilterTypes_ < queryFilterType + 1)
+            numQueryFilterTypes_ = queryFilterType + 1;
+        if (numAreas_[queryFilterType] < areaID + 1)
+            numAreas_[queryFilterType] = areaID + 1;
+        MarkNetworkUpdate();
+    }
+}
+
+void CrowdManager::SetObstacleAvoidanceTypesAttr(const VariantVector& value)
+{
+    if (!crowd_)
+        return;
+
+    unsigned index = 0;
+    unsigned obstacleAvoidanceType = 0;
+    numObstacleAvoidanceTypes_ = index < value.Size() ? Min(value[index++].GetUInt(), DT_CROWD_MAX_OBSTAVOIDANCE_PARAMS) : 0;
+
+    while (obstacleAvoidanceType < numObstacleAvoidanceTypes_)
+    {
+        if (index + 10 <= value.Size())
+        {
+            dtObstacleAvoidanceParams params;
+            params.velBias = value[index++].GetFloat();
+            params.weightDesVel = value[index++].GetFloat();
+            params.weightCurVel = value[index++].GetFloat();
+            params.weightSide = value[index++].GetFloat();
+            params.weightToi = value[index++].GetFloat();
+            params.horizTime = value[index++].GetFloat();
+            params.gridSize = (unsigned char)value[index++].GetUInt();
+            params.adaptiveDivs = (unsigned char)value[index++].GetUInt();
+            params.adaptiveRings = (unsigned char)value[index++].GetUInt();
+            params.adaptiveDepth = (unsigned char)value[index++].GetUInt();
+            crowd_->setObstacleAvoidanceParams(obstacleAvoidanceType, &params);
+        }
+        ++obstacleAvoidanceType;
+    }
+}
+
+void CrowdManager::SetObstacleAvoidanceParams(unsigned obstacleAvoidanceType, const CrowdObstacleAvoidanceParams& params)
+{
+    if (crowd_ && obstacleAvoidanceType < DT_CROWD_MAX_OBSTAVOIDANCE_PARAMS)
+    {
+        crowd_->setObstacleAvoidanceParams(obstacleAvoidanceType, reinterpret_cast<const dtObstacleAvoidanceParams*>(&params));
+        if (numObstacleAvoidanceTypes_ < obstacleAvoidanceType + 1)
+            numObstacleAvoidanceTypes_ = obstacleAvoidanceType + 1;
+        MarkNetworkUpdate();
+    }
+}
+
+Vector3 CrowdManager::FindNearestPoint(const Vector3& point, int queryFilterType, dtPolyRef* nearestRef)
+{
+    if (nearestRef)
+        *nearestRef = 0;
+    return crowd_ && navigationMesh_ ?
+        navigationMesh_->FindNearestPoint(point, crowd_->getQueryExtents(), crowd_->getFilter(queryFilterType), nearestRef) : point;
+}
+
+Vector3 CrowdManager::MoveAlongSurface(const Vector3& start, const Vector3& end, int queryFilterType, int maxVisited)
+{
+    return crowd_ && navigationMesh_ ?
+        navigationMesh_->MoveAlongSurface(start, end, crowd_->getQueryExtents(), maxVisited, crowd_->getFilter(queryFilterType)) :
+        end;
+}
+
+void CrowdManager::FindPath(PODVector<Vector3>& dest, const Vector3& start, const Vector3& end, int queryFilterType)
+{
+    if (crowd_ && navigationMesh_)
+        navigationMesh_->FindPath(dest, start, end, crowd_->getQueryExtents(), crowd_->getFilter(queryFilterType));
+}
+
+Vector3 CrowdManager::GetRandomPoint(int queryFilterType, dtPolyRef* randomRef)
+{
+    if (randomRef)
+        *randomRef = 0;
+    return crowd_ && navigationMesh_ ? navigationMesh_->GetRandomPoint(crowd_->getFilter(queryFilterType), randomRef) :
+        Vector3::ZERO;
+}
+
+Vector3 CrowdManager::GetRandomPointInCircle(const Vector3& center, float radius, int queryFilterType, dtPolyRef* randomRef)
+{
+    if (randomRef)
+        *randomRef = 0;
+    return crowd_ && navigationMesh_ ?
+        navigationMesh_->GetRandomPointInCircle(center, radius, crowd_->getQueryExtents(), crowd_->getFilter(queryFilterType),
+            randomRef) : center;
+}
+
+float CrowdManager::GetDistanceToWall(const Vector3& point, float radius, int queryFilterType, Vector3* hitPos, Vector3* hitNormal)
+{
+    if (hitPos)
+        *hitPos = Vector3::ZERO;
+    if (hitNormal)
+        *hitNormal = Vector3::DOWN;
+    return crowd_ && navigationMesh_ ?
+        navigationMesh_->GetDistanceToWall(point, radius, crowd_->getQueryExtents(), crowd_->getFilter(queryFilterType), hitPos,
+            hitNormal) : radius;
+}
+
+Vector3 CrowdManager::Raycast(const Vector3& start, const Vector3& end, int queryFilterType, Vector3* hitNormal)
+{
+    if (hitNormal)
+        *hitNormal = Vector3::DOWN;
+    return crowd_ && navigationMesh_ ?
+        navigationMesh_->Raycast(start, end, crowd_->getQueryExtents(), crowd_->getFilter(queryFilterType), hitNormal) : end;
+}
+
+unsigned CrowdManager::GetNumAreas(unsigned queryFilterType) const
+{
+    return queryFilterType < numQueryFilterTypes_ ? numAreas_[queryFilterType] : 0;
+}
+
+VariantVector CrowdManager::GetQueryFilterTypesAttr() const
+{
+    VariantVector ret;
+    if (crowd_)
+    {
+        unsigned totalNumAreas = 0;
+        for (unsigned i = 0; i < numQueryFilterTypes_; ++i)
+            totalNumAreas += numAreas_[i];
+
+        ret.Reserve(numQueryFilterTypes_ * 3 + totalNumAreas + 1);
+        ret.Push(numQueryFilterTypes_);
+
+        for (unsigned i = 0; i < numQueryFilterTypes_; ++i)
+        {
+            const dtQueryFilter* filter = crowd_->getFilter(i);
+            assert(filter);
+            ret.Push(filter->getIncludeFlags());
+            ret.Push(filter->getExcludeFlags());
+            ret.Push(numAreas_[i]);
+
+            for (unsigned j = 0; j < numAreas_[i]; ++j)
+                ret.Push(filter->getAreaCost(j));
+        }
+    }
+    else
+        ret.Push(0);
+
+    return ret;
+}
+
+unsigned short CrowdManager::GetIncludeFlags(unsigned queryFilterType) const
+{
+    if (queryFilterType >= numQueryFilterTypes_)
+        LOGWARNINGF("Query filter type %d is not configured yet, returning the default include flags initialized by dtCrowd",
+            queryFilterType);
+    const dtQueryFilter* filter = GetDetourQueryFilter(queryFilterType);
+    return (unsigned short)(filter ? filter->getIncludeFlags() : 0xffff);
+}
+
+unsigned short CrowdManager::GetExcludeFlags(unsigned queryFilterType) const
+{
+    if (queryFilterType >= numQueryFilterTypes_)
+        LOGWARNINGF("Query filter type %d is not configured yet, returning the default exclude flags initialized by dtCrowd",
+            queryFilterType);
+    const dtQueryFilter* filter = GetDetourQueryFilter(queryFilterType);
+    return (unsigned short)(filter ? filter->getExcludeFlags() : 0);
+}
+
+float CrowdManager::GetAreaCost(unsigned queryFilterType, unsigned areaID) const
+{
+    if (queryFilterType >= numQueryFilterTypes_ || areaID >= numAreas_[queryFilterType])
+        LOGWARNINGF(
+            "Query filter type %d and/or area id %d are not configured yet, returning the default area cost initialized by dtCrowd",
+            queryFilterType, areaID);
+    const dtQueryFilter* filter = GetDetourQueryFilter(queryFilterType);
+    return filter ? filter->getAreaCost((int)areaID) : 1.f;
+}
+
+VariantVector CrowdManager::GetObstacleAvoidanceTypesAttr() const
+{
+    VariantVector ret;
+    if (crowd_)
+    {
+        ret.Reserve(numObstacleAvoidanceTypes_ * 10 + 1);
+        ret.Push(numObstacleAvoidanceTypes_);
+
+        for (unsigned i = 0; i < numObstacleAvoidanceTypes_; ++i)
+        {
+            const dtObstacleAvoidanceParams* params = crowd_->getObstacleAvoidanceParams(i);
+            assert(params);
+            ret.Push(params->velBias);
+            ret.Push(params->weightDesVel);
+            ret.Push(params->weightCurVel);
+            ret.Push(params->weightSide);
+            ret.Push(params->weightToi);
+            ret.Push(params->horizTime);
+            ret.Push(params->gridSize);
+            ret.Push(params->adaptiveDivs);
+            ret.Push(params->adaptiveRings);
+            ret.Push(params->adaptiveDepth);
+        }
+    }
+    else
+        ret.Push(0);
+
+    return ret;
+}
+
+const CrowdObstacleAvoidanceParams& CrowdManager::GetObstacleAvoidanceParams(unsigned obstacleAvoidanceType) const
+{
+    static const CrowdObstacleAvoidanceParams EMPTY_PARAMS = CrowdObstacleAvoidanceParams();
+    const dtObstacleAvoidanceParams* params = crowd_ ? crowd_->getObstacleAvoidanceParams(obstacleAvoidanceType) : 0;
+    return params ? *reinterpret_cast<const CrowdObstacleAvoidanceParams*>(params) : EMPTY_PARAMS;
+}
+
+PODVector<CrowdAgent*> CrowdManager::GetAgents(Node* node, bool inCrowdFilter) const
+{
+    if (!node)
+        node = GetScene();
+    PODVector<CrowdAgent*> agents;
+    node->GetComponents<CrowdAgent>(agents, true);
+    if (inCrowdFilter)
+    {
+        PODVector<CrowdAgent*>::Iterator i = agents.Begin();
+        while (i != agents.End())
+        {
+            if ((*i)->IsInCrowd())
+                ++i;
+            else
+                i = agents.Erase(i);
+        }
+    }
+    return agents;
+}
+
+bool CrowdManager::CreateCrowd()
+{
+    if (!navigationMesh_ || !navigationMesh_->InitializeQuery())
+        return false;
+
+    // Preserve the existing crowd configuration before recreating it
+    VariantVector queryFilterTypeConfiguration, obstacleAvoidanceTypeConfiguration;
+    bool recreate = crowd_ != 0;
+    if (recreate)
+    {
+        queryFilterTypeConfiguration = GetQueryFilterTypesAttr();
+        obstacleAvoidanceTypeConfiguration = GetObstacleAvoidanceTypesAttr();
+        dtFreeCrowd(crowd_);
+    }
+    crowd_ = dtAllocCrowd();
+
+    // Initialize the crowd
+    if (maxAgentRadius_ == 0.f)
+        maxAgentRadius_ = navigationMesh_->GetAgentRadius();
+    if (!crowd_->init(maxAgents_, maxAgentRadius_, navigationMesh_->navMesh_, CrowdAgentUpdateCallback))
+    {
+        LOGERROR("Could not initialize DetourCrowd");
+        return false;
+    }
+
+    if (recreate)
+    {
+        // Reconfigure the newly initialized crowd
+        SetQueryFilterTypesAttr(queryFilterTypeConfiguration);
+        SetObstacleAvoidanceTypesAttr(obstacleAvoidanceTypeConfiguration);
+
+        // Re-add the existing crowd agents
+        PODVector<CrowdAgent*> agents = GetAgents();
+        for (unsigned i = 0; i < agents.Size(); ++i)
+        {
+            // Keep adding until the crowd cannot take it anymore
+            if (agents[i]->AddAgentToCrowd(true) == -1)
+            {
+                LOGWARNINGF("CrowdManager: %d crowd agents orphaned", agents.Size() - i);
+                break;
+            }
+        }
+    }
+
+    return true;
+}
+
+int CrowdManager::AddAgent(CrowdAgent* agent, const Vector3& pos)
+{
+    if (!crowd_ || !navigationMesh_ || !agent)
+        return -1;
+    dtCrowdAgentParams params;
+    params.userData = agent;
+    if (agent->radius_ == 0.f)
+        agent->radius_ = navigationMesh_->GetAgentRadius();
+    if (agent->height_ == 0.f)
+        agent->height_ = navigationMesh_->GetAgentHeight();
+    return crowd_->addAgent(pos.Data(), &params);
+}
+
+void CrowdManager::RemoveAgent(CrowdAgent* agent)
+{
+    if (!crowd_ || !agent)
+        return;
+    dtCrowdAgent* agt = crowd_->getEditableAgent(agent->GetAgentCrowdId());
+    if (agt)
+        agt->params.userData = 0;
+    crowd_->removeAgent(agent->GetAgentCrowdId());
+}
+
+void CrowdManager::OnSceneSet(Scene* scene)
+{
+    // Subscribe to the scene subsystem update, which will trigger the crowd update step, and grab a reference
+    // to the scene's NavigationMesh
+    if (scene)
+    {
+        if (scene != node_)
+        {
+            LOGERROR("CrowdManager is a scene component and should only be attached to the scene node");
+            return;
+        }
+
+        SubscribeToEvent(scene, E_SCENESUBSYSTEMUPDATE, HANDLER(CrowdManager, HandleSceneSubsystemUpdate));
+
+        // Attempt to auto discover a NavigationMesh component (or its derivative) under the scene node
+        NavigationMesh* navMesh = scene->GetDerivedComponent<NavigationMesh>(true);
+        if (navMesh)
+        {
+            navigationMesh_ = navMesh;
+            navigationMeshId_ = navMesh->GetID();
+            CreateCrowd();
+
+            SubscribeToEvent(navMesh->GetNode(), E_NAVIGATION_MESH_REBUILT, HANDLER(CrowdManager, HandleNavMeshChanged));
+            SubscribeToEvent(navMesh->GetNode(), E_COMPONENTREMOVED, HANDLER(CrowdManager, HandleNavMeshChanged));
+        }
+    }
+    else
+    {
+        UnsubscribeFromEvent(E_SCENESUBSYSTEMUPDATE);
+        UnsubscribeFromEvent(E_NAVIGATION_MESH_REBUILT);
+        UnsubscribeFromEvent(E_COMPONENTREMOVED);
+
+        navigationMesh_ = 0;
+    }
+}
+
+void CrowdManager::Update(float delta)
+{
+    assert(crowd_ && navigationMesh_);
+    PROFILE(UpdateCrowd);
+    crowd_->update(delta, 0);
+}
+
+const dtCrowdAgent* CrowdManager::GetDetourCrowdAgent(int agent) const
+{
+    return crowd_ ? crowd_->getAgent(agent) : 0;
+}
+
+const dtQueryFilter* CrowdManager::GetDetourQueryFilter(unsigned queryFilterType) const
+{
+    return crowd_ ? crowd_->getFilter(queryFilterType) : 0;
+}
+
+void CrowdManager::HandleSceneSubsystemUpdate(StringHash eventType, VariantMap& eventData)
+{
+    // Perform update tick as long as the crowd is initialized and the associated navmesh has not been removed
+    if (crowd_ && navigationMesh_)
+    {
+        using namespace SceneSubsystemUpdate;
+
+        if (IsEnabledEffective())
+            Update(eventData[P_TIMESTEP].GetFloat());
+    }
+}
+
+void CrowdManager::HandleNavMeshChanged(StringHash eventType, VariantMap& eventData)
+{
+    NavigationMesh* navMesh;
+    if (eventType == E_NAVIGATION_MESH_REBUILT)
+    {
+        // The mesh being rebuilt may not have existed before
+        navMesh = static_cast<NavigationMesh*>(eventData[NavigationMeshRebuilt::P_MESH].GetPtr());
+    }
+    else
+    {
+        // eventType == E_COMPONENTREMOVED
+        navMesh = static_cast<NavigationMesh*>(eventData[ComponentRemoved::P_COMPONENT].GetPtr());
+        // Only interested in navmesh component being used to initialized the crowd
+        if (navMesh != navigationMesh_)
+            return;
+        // Since this is a component removed event, reset our own navmesh pointer
+        navMesh = 0;
+    }
+    SetNavigationMesh(navMesh);
+}
+
+}

+ 198 - 0
Source/Atomic/Navigation/CrowdManager.h

@@ -0,0 +1,198 @@
+//
+// 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"
+
+#ifdef DT_POLYREF64
+typedef uint64_t dtPolyRef;
+#else
+typedef unsigned int dtPolyRef;
+#endif
+
+class dtCrowd;
+class dtQueryFilter;
+struct dtCrowdAgent;
+
+namespace Atomic
+{
+
+class CrowdAgent;
+class NavigationMesh;
+
+/// Parameter structure for obstacle avoidance params (copied from DetourObstacleAvoidance.h in order to hide Detour header from Atomic library users).
+struct CrowdObstacleAvoidanceParams
+{
+    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
+};
+
+/// Crowd manager scene component. Should be added only to the root scene node.
+class ATOMIC_API CrowdManager : public Component
+{
+    OBJECT(CrowdManager);
+
+    friend class CrowdAgent;
+
+public:
+    /// Construct.
+    CrowdManager(Context* context);
+    /// Destruct.
+    virtual ~CrowdManager();
+    /// Register object factory.
+    static void RegisterObject(Context* context);
+    /// Apply attribute changes that can not be applied immediately. Called after scene load or a network update.
+    virtual void ApplyAttributes();
+
+    /// Draw the agents' pathing debug data.
+    virtual void DrawDebugGeometry(DebugRenderer* debug, bool depthTest);
+    /// Add debug geometry to the debug renderer.
+    void DrawDebugGeometry(bool depthTest);
+
+    /// Set the crowd target position. The target position is set to all crowd agents found in the specified node. Defaulted to scene node.
+    void SetCrowdTarget(const Vector3& position, Node* node = 0);
+    /// Set the crowd move velocity. The move velocity is applied to all crowd agents found in the specified node. Defaulted to scene node.
+    void SetCrowdVelocity(const Vector3& velocity, Node* node = 0);
+    /// Reset any crowd target for all crowd agents found in the specified node. Defaulted to scene node.
+    void ResetCrowdTarget(Node* node = 0);
+    /// Set the maximum number of agents.
+    void SetMaxAgents(unsigned maxAgents);
+    /// Set the maximum radius of any agent.
+    void SetMaxAgentRadius(float maxAgentRadius);
+    /// Assigns the navigation mesh for the crowd.
+    void SetNavigationMesh(NavigationMesh* navMesh);
+    /// Set all the query filter types configured in the crowd based on the corresponding attribute.
+    void SetQueryFilterTypesAttr(const VariantVector& value);
+    /// Set the include flags for the specified query filter type.
+    void SetIncludeFlags(unsigned queryFilterType, unsigned short flags);
+    /// Set the exclude flags for the specified query filter type.
+    void SetExcludeFlags(unsigned queryFilterType, unsigned short flags);
+    /// Set the cost of an area for the specified query filter type.
+    void SetAreaCost(unsigned queryFilterType, unsigned areaID, float cost);
+    /// Set all the obstacle avoidance types configured in the crowd based on the corresponding attribute.
+    void SetObstacleAvoidanceTypesAttr(const VariantVector& value);
+    /// Set the params for the specified obstacle avoidance type.
+    void SetObstacleAvoidanceParams(unsigned obstacleAvoidanceType, const CrowdObstacleAvoidanceParams& params);
+
+    /// Get all the crowd agent components in the specified node hierarchy. If the node is not specified then use scene node. When inCrowdFilter is set to true then only get agents that are in the crowd.
+    PODVector<CrowdAgent*> GetAgents(Node* node = 0, bool inCrowdFilter = true) const;
+    /// Find the nearest point on the navigation mesh to a given point using the crowd initialized query extent (based on maxAgentRadius) and the specified query filter type.
+    Vector3 FindNearestPoint(const Vector3& point, int queryFilterType, dtPolyRef* nearestRef = 0);
+    /// Try to move along the surface from one point to another using the crowd initialized query extent (based on maxAgentRadius) and the specified query filter type.
+    Vector3 MoveAlongSurface(const Vector3& start, const Vector3& end, int queryFilterType, int maxVisited = 3);
+    /// Find a path between world space points using the crowd initialized query extent (based on maxAgentRadius) and the specified query filter type. Return non-empty list of points if successful.
+    void FindPath(PODVector<Vector3>& dest, const Vector3& start, const Vector3& end, int queryFilterType);
+    /// Return a random point on the navigation mesh using the crowd initialized query extent (based on maxAgentRadius) and the specified query filter type.
+    Vector3 GetRandomPoint(int queryFilterType, dtPolyRef* randomRef = 0);
+    /// Return a random point on the navigation mesh within a circle using the crowd initialized query extent (based on maxAgentRadius) and the specified query filter type. The circle radius is only a guideline and in practice the returned point may be further away.
+    Vector3 GetRandomPointInCircle(const Vector3& center, float radius, int queryFilterType, dtPolyRef* randomRef = 0);
+    /// Return distance to wall from a point using the crowd initialized query extent (based on maxAgentRadius) and the specified query filter type. Maximum search radius must be specified.
+    float GetDistanceToWall(const Vector3& point, float radius, int queryFilterType, Vector3* hitPos = 0, Vector3* hitNormal = 0);
+    /// Perform a walkability raycast on the navigation mesh between start and end using the crowd initialized query extent (based on maxAgentRadius) and the specified query filter type. Return the point where a wall was hit, or the end point if no walls.
+    Vector3 Raycast(const Vector3& start, const Vector3& end, int queryFilterType, Vector3* hitNormal = 0);
+
+    /// Get the maximum number of agents.
+    unsigned GetMaxAgents() const { return maxAgents_; }
+
+    /// Get the maximum radius of any agent.
+    float GetMaxAgentRadius() const { return maxAgentRadius_; }
+
+    /// Get the Navigation mesh assigned to the crowd.
+    NavigationMesh* GetNavigationMesh() const { return navigationMesh_; }
+
+    /// Get the number of configured query filter types.
+    unsigned GetNumQueryFilterTypes() const { return numQueryFilterTypes_; }
+
+    /// Get the number of configured area in the specified query filter type.
+    unsigned GetNumAreas(unsigned queryFilterType) const;
+    /// Return all the filter types configured in the crowd as attribute.
+    VariantVector GetQueryFilterTypesAttr() const;
+    /// Get the include flags for the specified query filter type.
+    unsigned short GetIncludeFlags(unsigned queryFilterType) const;
+    /// Get the exclude flags for the specified query filter type.
+    unsigned short GetExcludeFlags(unsigned queryFilterType) const;
+    /// Get the cost of an area for the specified query filter type.
+    float GetAreaCost(unsigned queryFilterType, unsigned areaID) const;
+
+    /// Get the number of configured obstacle avoidance types.
+    unsigned GetNumObstacleAvoidanceTypes() const { return numObstacleAvoidanceTypes_; }
+
+    /// Return all the obstacle avoidance types configured in the crowd as attribute.
+    VariantVector GetObstacleAvoidanceTypesAttr() const;
+    /// Get the params for the specified obstacle avoidance type.
+    const CrowdObstacleAvoidanceParams& GetObstacleAvoidanceParams(unsigned obstacleAvoidanceType) const;
+
+protected:
+    /// Create and initialized internal Detour crowd object. When it is a recreate, it preserves the configuration and attempts to re-add existing agents in the previous crowd back to the newly created crowd.
+    bool CreateCrowd();
+    /// Create and adds an detour crowd agent, Agent's radius and height is set through the navigation mesh. Return -1 on error, agent ID on success.
+    int AddAgent(CrowdAgent* agent, const Vector3& pos);
+    /// Removes the detour crowd agent.
+    void RemoveAgent(CrowdAgent* agent);
+
+protected:
+    /// Handle scene being assigned.
+    virtual void OnSceneSet(Scene* scene);
+    /// Update the crowd simulation.
+    void Update(float delta);
+    /// Get the detour crowd agent.
+    const dtCrowdAgent* GetDetourCrowdAgent(int agent) const;
+    /// Get the detour query filter.
+    const dtQueryFilter* GetDetourQueryFilter(unsigned queryFilterType) const;
+
+    /// Get the internal detour crowd component.
+    dtCrowd* GetCrowd() const { return crowd_; }
+
+private:
+    /// Handle the scene subsystem update event.
+    void HandleSceneSubsystemUpdate(StringHash eventType, VariantMap& eventData);
+    /// Handle navigation mesh changed event. It can be navmesh being rebuilt or being removed from its node.
+    void HandleNavMeshChanged(StringHash eventType, VariantMap& eventData);
+
+    /// Internal Detour crowd object.
+    dtCrowd* crowd_;
+    /// NavigationMesh for which the crowd was created.
+    NavigationMesh* navigationMesh_;
+    /// The NavigationMesh component Id for pending crowd creation.
+    unsigned navigationMeshId_;
+    /// The maximum number of agents the crowd can manage.
+    unsigned maxAgents_;
+    /// The maximum radius of any agent that will be added to the crowd.
+    float maxAgentRadius_;
+    /// Number of query filter types configured in the crowd. Limit to DT_CROWD_MAX_QUERY_FILTER_TYPE.
+    unsigned numQueryFilterTypes_;
+    /// Number of configured area in each filter type. Limit to DT_MAX_AREAS.
+    PODVector<unsigned> numAreas_;
+    /// Number of obstacle avoidance types configured in the crowd. Limit to DT_CROWD_MAX_OBSTAVOIDANCE_PARAMS.
+    unsigned numObstacleAvoidanceTypes_;
+};
+
+}

+ 0 - 546
Source/Atomic/Navigation/DetourCrowdManager.cpp

@@ -1,546 +0,0 @@
-//
-// 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 ATOMIC_PHYSICS
-#include "../Physics/PhysicsEvents.h"
-#endif
-
-#include <DetourCrowd/include/DetourCrowd.h>
-#include <Recast/include/Recast.h>
-
-#include "../DebugNew.h"
-
-namespace Atomic
-{
-
-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_(0)
-{
-    agentBuffer_.Resize(maxAgents_);
-}
-
-DetourCrowdManager::~DetourCrowdManager()
-{
-    dtFreeCrowd(crowd_);
-    crowd_ = 0;
-    delete agentDebug_;
-    agentDebug_ = 0;
-}
-
-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_ = navMesh;
-    if (navigationMesh_ && !navigationMesh_->navMeshQuery_)
-        navigationMesh_->InitializeQuery();
-    CreateCrowd();
-    MarkNetworkUpdate();
-}
-
-void DetourCrowdManager::SetAreaCost(unsigned filterID, unsigned areaID, float weight)
-{
-    dtQueryFilter* filter = crowd_->getEditableFilter(filterID);
-    if (filter)
-        filter->setAreaCost((int)areaID, 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();
-}
-
-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)
-{
-    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
-{
-    if (crowd_ && navigationMesh_)
-    {
-        const dtQueryFilter* filter = crowd_->getFilter((int)filterID);
-        if (filter)
-            return filter->getAreaCost((int)areaID);
-    }
-    return 0.0f;
-}
-
-unsigned DetourCrowdManager::GetAgentCount() const
-{
-    return crowd_ ? crowd_->getAgentCount() : 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;
-
-            // 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);
-
-            // 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)
-            {
-                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);
-
-            // 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_)
-        return false;
-
-    if (crowd_)
-        dtFreeCrowd(crowd_);
-    crowd_ = dtAllocCrowd();
-    if (!agentDebug_)
-        agentDebug_ = new dtCrowdAgentDebugInfo();
-
-    // Initialize the crowd
-    if (!crowd_->init(maxAgents_, navigationMesh_->GetAgentRadius(), navigationMesh_->navMesh_))
-    {
-        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_ || navigationMesh_.Expired())
-        return -1;
-    dtCrowdAgentParams params;
-    params.userData = agent;
-    if (agent->radius_ <= 0.0f)
-        agent->radius_ = navigationMesh_->GetAgentRadius();
-    params.radius = agent->radius_;
-    if (agent->height_ <= 0.0f)
-        agent->height_ = navigationMesh_->GetAgentHeight();
-    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_)
-        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_)
-        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_)
-        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_)
-        return false;
-    dtPolyRef polyRef;
-    float nearestPos[3];
-    dtStatus status = navigationMesh_->navMeshQuery_->findNearestPoly(
-        target.Data(),
-        crowd_->getQueryExtents(),
-        crowd_->getFilter(agent->filterType_),
-        &polyRef,
-        nearestPos);
-
-    return !dtStatusFailed(status) && crowd_->requestMoveTarget(agent->GetAgentCrowdId(), polyRef, nearestPos);
-}
-
-bool DetourCrowdManager::SetAgentTarget(CrowdAgent* agent, Vector3 target, unsigned& targetRef)
-{
-    if (crowd_ == 0)
-        return false;
-    float nearestPos[3];
-    dtStatus status = navigationMesh_->navMeshQuery_->findNearestPoly(
-        target.Data(),
-        crowd_->getQueryExtents(),
-        crowd_->getFilter(agent->filterType_),
-        &targetRef,
-        nearestPos);
-
-    // Return true if detour has determined it can do something with our move target
-    return !dtStatusFailed(status) && crowd_->requestMoveTarget(agent->GetAgentCrowdId(), targetRef, nearestPos) &&
-        crowd_->getAgent(agent->GetAgentCrowdId())->targetState != DT_CROWDAGENT_TARGET_FAILED;
-}
-
-Vector3 DetourCrowdManager::GetClosestWalkablePosition(Vector3 pos) const
-{
-    if (!crowd_)
-        return Vector3::ZERO;
-    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_)
-        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)
-{
-    return crowd_ ? crowd_->getAgent(agent) : 0;
-}
-
-void DetourCrowdManager::HandleSceneSubsystemUpdate(StringHash eventType, VariantMap& eventData)
-{
-    using namespace SceneSubsystemUpdate;
-
-    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_)
-    {
-        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::OnSceneSet(Scene* scene)
-{
-    // Subscribe to the scene subsystem update, which will trigger the crowd update step, and grab a reference
-    // to the scene's NavigationMesh
-    if (scene)
-    {
-        SubscribeToEvent(scene, E_SCENESUBSYSTEMUPDATE, HANDLER(DetourCrowdManager, HandleSceneSubsystemUpdate));
-        NavigationMesh* mesh = GetScene()->GetComponent<NavigationMesh>();
-        if (!mesh)
-            mesh = GetScene()->GetComponent<DynamicNavigationMesh>();
-        if (mesh)
-        {
-            SubscribeToEvent(mesh, E_NAVIGATION_MESH_REBUILT, HANDLER(DetourCrowdManager, HandleNavMeshFullRebuild));
-            SetNavigationMesh(mesh);
-        }
-        else
-            LOGERROR("DetourCrowdManager requires an existing navigation mesh");
-    }
-    else
-    {
-        UnsubscribeFromEvent(E_SCENESUBSYSTEMUPDATE);
-        UnsubscribeFromEvent(E_NAVIGATION_MESH_REBUILT);
-        navigationMesh_.Reset();
-    }
-
-}
-
-}

+ 0 - 146
Source/Atomic/Navigation/DetourCrowdManager.h

@@ -1,146 +0,0 @@
-//
-// 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 Atomic
-{
-
-class CrowdAgent;
-class NavigationMesh;
-
-enum NavigationQuality
-{
-    NAVIGATIONQUALITY_LOW = 0,
-    NAVIGATIONQUALITY_MEDIUM = 1,
-    NAVIGATIONQUALITY_HIGH = 2
-};
-
-enum NavigationPushiness
-{
-    PUSHINESS_LOW = 0,
-    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 ATOMIC_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);
-    /// Set the cost of an area-type for the specified navigation filter type.
-    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() 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.
-    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);
-    /// Add debug geometry to the debug renderer.
-    void DrawDebugGeometry(bool depthTest);
-    /// Get the currently included agents.
-    const 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. Return -1 on error, agent ID on success.
-    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);
-
-    /// Set the move target for the specified agent.
-    bool SetAgentTarget(CrowdAgent* agent, Vector3 target);
-    /// Set the move target for the specified agent.
-    bool SetAgentTarget(CrowdAgent* agent, Vector3 target, unsigned& targetRef);
-
-    /// Get the closest walkable position.
-    Vector3 GetClosestWalkablePosition(Vector3 pos) const;
-
-protected:
-    /// Update the crowd simulation.
-    void Update(float delta);
-    /// Handle scene being assigned.
-    virtual void OnSceneSet(Scene* scene);
-    /// Get the detour crowd agent.
-    const dtCrowdAgent* GetCrowdAgent(int agent);
-    /// Get the internal detour crowd component.
-    dtCrowd* GetCrowd() const { return crowd_; }
-
-private:
-    /// Handle the scene subsystem update event.
-    void HandleSceneSubsystemUpdate(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_;
-    /// Container for fetching agents from DetourCrowd during update.
-    PODVector<CrowdAgent*> agents_;
-};
-
-}

+ 34 - 38
Source/Atomic/Navigation/DynamicNavigationMesh.cpp

@@ -22,35 +22,31 @@
 
 #include "../Precompiled.h"
 
-#include "../Navigation/DynamicNavigationMesh.h"
 
-#include "../Math/BoundingBox.h"
 #include "../Core/Context.h"
-#include "../Navigation/CrowdAgent.h"
+#include "../Core/Profiler.h"
 #include "../Graphics/DebugRenderer.h"
 #include "../IO/Log.h"
 #include "../IO/MemoryBuffer.h"
+#include "../Navigation/CrowdAgent.h"
+#include "../Navigation/DynamicNavigationMesh.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 "../Scene/Node.h"
 #include "../Scene/Scene.h"
 #include "../Scene/SceneEvents.h"
 
 #include <LZ4/lz4.h>
-#include <cfloat>
 #include <Detour/include/DetourNavMesh.h>
 #include <Detour/include/DetourNavMeshBuilder.h>
-#include <Detour/include/DetourNavMeshQuery.h>
 #include <DetourTileCache/include/DetourTileCache.h>
 #include <DetourTileCache/include/DetourTileCacheBuilder.h>
 #include <Recast/include/Recast.h>
-#include <Recast/include/RecastAlloc.h>
 
-//DebugNew is deliberately not used because the macro 'free' conflicts DetourTileCache's LinearAllocator interface
+// DebugNew is deliberately not used because the macro 'free' conflicts with DetourTileCache's LinearAllocator interface
 //#include "../DebugNew.h"
 
 #define TILECACHE_MAXLAYERS 128
@@ -72,7 +68,7 @@ struct TileCompressor : public dtTileCacheCompressor
 {
     virtual int maxCompressedSize(const int bufferSize)
     {
-        return (int)(bufferSize* 1.05f);
+        return (int)(bufferSize * 1.05f);
     }
 
     virtual dtStatus compress(const unsigned char* buffer, const int bufferSize,
@@ -136,9 +132,9 @@ struct MeshProcess : public dtTileCacheMeshProcess
                     offMeshVertices_.Push(start);
                     offMeshVertices_.Push(end);
                     offMeshRadii_.Push(connection->GetRadius());
-                    offMeshFlags_.Push(connection->GetMask());
+                    offMeshFlags_.Push((unsigned short)connection->GetMask());
                     offMeshAreas_.Push((unsigned char)connection->GetAreaID());
-                    offMeshDir_.Push(connection->IsBidirectional() ? DT_OFFMESH_CON_BIDIR : 0);
+                    offMeshDir_.Push((unsigned char)(connection->IsBidirectional() ? DT_OFFMESH_CON_BIDIR : 0));
                 }
             }
             params->offMeshConCount = offMeshRadii_.Size();
@@ -169,7 +165,8 @@ struct LinearAllocator : public dtTileCacheAlloc
     int top;
     int high;
 
-    LinearAllocator(const int cap) : buffer(0), capacity(0), top(0), high(0)
+    LinearAllocator(const int cap) :
+        buffer(0), capacity(0), top(0), high(0)
     {
         resize(cap);
     }
@@ -281,7 +278,7 @@ bool DynamicNavigationMesh::Build()
         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 maxTiles = NextPowerOfTwo((unsigned)(numTilesX_ * numTilesZ_)) * TILECACHE_MAXLAYERS;
         unsigned tileBits = 0;
         unsigned temp = maxTiles;
         while (temp > 1)
@@ -290,7 +287,7 @@ bool DynamicNavigationMesh::Build()
             ++tileBits;
         }
 
-        unsigned maxPolys = 1 << (22 - tileBits);
+        unsigned maxPolys = (unsigned)(1 << (22 - tileBits));
 
         dtNavMeshParams params;
         rcVcopy(params.orig, &boundingBox_.min_.x_);
@@ -356,7 +353,7 @@ bool DynamicNavigationMesh::Build()
                 {
                     dtCompressedTileRef tileRef;
                     int status = tileCache_->addTile(tiles[i].data, tiles[i].dataSize, DT_COMPRESSEDTILE_FREE_DATA, &tileRef);
-                    if (dtStatusFailed(status))
+                    if (dtStatusFailed((dtStatus)status))
                     {
                         dtFree(tiles[i].data);
                         tiles[i].data = 0x0;
@@ -369,7 +366,7 @@ bool DynamicNavigationMesh::Build()
         }
 
         // For a full build it's necessary to update the nav mesh
-        // not doing so will cause dependent components to crash, like DetourCrowdManager
+        // not doing so will cause dependent components to crash, like CrowdManager
         tileCache_->update(0, navMesh_);
 
         LOGDEBUG("Built navigation mesh with " + String(numTiles) + " tiles");
@@ -446,7 +443,7 @@ bool DynamicNavigationMesh::Build(const BoundingBox& boundingBox)
             {
                 dtCompressedTileRef tileRef;
                 int status = tileCache_->addTile(tiles[i].data, tiles[i].dataSize, DT_COMPRESSEDTILE_FREE_DATA, &tileRef);
-                if (dtStatusFailed(status))
+                if (dtStatusFailed((dtStatus)status))
                 {
                     dtFree(tiles[i].data);
                     tiles[i].data = 0x0;
@@ -492,12 +489,9 @@ void DynamicNavigationMesh::DrawDebugGeometry(DebugRenderer* debug, bool depthTe
                     dtPoly* poly = tile->polys + i;
                     for (unsigned j = 0; j < poly->vertCount; ++j)
                     {
-                        debug->AddLine(
-                            worldTransform * *reinterpret_cast<const Vector3*>(&tile->verts[poly->verts[j] * 3]),
+                        debug->AddLine(worldTransform * *reinterpret_cast<const Vector3*>(&tile->verts[poly->verts[j] * 3]),
                             worldTransform * *reinterpret_cast<const Vector3*>(&tile->verts[poly->verts[(j + 1) % poly->vertCount] * 3]),
-                            Color::YELLOW,
-                            depthTest
-                            );
+                            Color::YELLOW, depthTest);
                     }
                 }
             }
@@ -611,7 +605,7 @@ void DynamicNavigationMesh::SetNavigationDataAttr(const PODVector<unsigned char>
         buffer.Read(&header, sizeof(dtTileCacheLayerHeader));
         const int dataSize = buffer.ReadInt();
         unsigned char* data = (unsigned char*)dtAlloc(dataSize, DT_ALLOC_PERM);
-        buffer.Read(data, dataSize);
+        buffer.Read(data, (unsigned)dataSize);
 
         if (dtStatusFailed(tileCache_->addTile(data, dataSize, DT_TILE_FREE_DATA, 0)))
         {
@@ -659,7 +653,7 @@ PODVector<unsigned char> DynamicNavigationMesh::GetNavigationDataAttr() const
                     // 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);
+                    ret.Write(tile->data, (unsigned)tile->dataSize);
                 }
             }
         }
@@ -676,15 +670,13 @@ int DynamicNavigationMesh::BuildTile(Vector<NavigationGeometryInfo>& geometryLis
     float tileEdgeLength = (float)tileSize_ * cellSize_;
 
     BoundingBox tileBoundingBox(Vector3(
-        boundingBox_.min_.x_ + tileEdgeLength * (float)x,
-        boundingBox_.min_.y_,
-        boundingBox_.min_.z_ + tileEdgeLength * (float)z
-        ),
+            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)
-        ));
+            boundingBox_.min_.x_ + tileEdgeLength * (float)(x + 1),
+            boundingBox_.max_.y_,
+            boundingBox_.min_.z_ + tileEdgeLength * (float)(z + 1)));
 
     DynamicNavBuildData build(allocator_);
 
@@ -768,7 +760,8 @@ int DynamicNavigationMesh::BuildTile(Vector<NavigationGeometryInfo>& geometryLis
 
     // 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_);
+        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)
     {
@@ -800,7 +793,8 @@ int DynamicNavigationMesh::BuildTile(Vector<NavigationGeometryInfo>& geometryLis
         return 0;
     }
 
-    if (!rcBuildHeightfieldLayers(build.ctx_, *build.compactHeightField_, cfg.borderSize, cfg.walkableHeight, *build.heightFieldLayers_))
+    if (!rcBuildHeightfieldLayers(build.ctx_, *build.compactHeightField_, cfg.borderSize, cfg.walkableHeight,
+        *build.heightFieldLayers_))
     {
         LOGERROR("Could not build height field layers");
         return 0;
@@ -830,7 +824,9 @@ int DynamicNavigationMesh::BuildTile(Vector<NavigationGeometryInfo>& geometryLis
         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)))
+        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;
@@ -901,7 +897,7 @@ void DynamicNavigationMesh::AddObstacle(Obstacle* obstacle, bool silent)
         rcVcopy(pos, &obsPos.x_);
         dtObstacleRef refHolder;
 
-        // Because dtTileCache doesn't process obstacle requests while updating tiles 
+        // 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_);
@@ -941,7 +937,7 @@ void DynamicNavigationMesh::RemoveObstacle(Obstacle* obstacle, bool silent)
 {
     if (tileCache_ && obstacle->obstacleId_ > 0)
     {
-        // Because dtTileCache doesn't process obstacle requests while updating tiles 
+        // 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_);

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

@@ -41,6 +41,7 @@ class Obstacle;
 class ATOMIC_API DynamicNavigationMesh : public NavigationMesh
 {
     OBJECT(DynamicNavigationMesh)
+
     friend class Obstacle;
     friend struct MeshProcess;
 
@@ -69,11 +70,13 @@ public:
 
     /// 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_; }
 

+ 43 - 44
Source/Atomic/Navigation/NavArea.cpp

@@ -22,67 +22,66 @@
 
 #include "../Precompiled.h"
 
-#include "../Scene/Component.h"
 #include "../Core/Context.h"
 #include "../Graphics/DebugRenderer.h"
 #include "../IO/Log.h"
 #include "../Navigation/NavArea.h"
 #include "../Scene/Node.h"
-#include "../Container/Str.h"
 
 namespace Atomic
 {
-    static const unsigned MAX_NAV_AREA_ID = 255;
-    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_ID = 0;
 
-    extern const char* NAVIGATION_CATEGORY;
+static const unsigned MAX_NAV_AREA_ID = 255;
+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_AREA_ID = 0;
 
-    NavArea::NavArea(Context* context) :
-        Component(context),
-        areaID_(DEFAULT_AREA_ID),
-        boundingBox_(DEFAULT_BOUNDING_BOX_MIN, DEFAULT_BOUNDING_BOX_MAX)
-    {
-    }
+extern const char* NAVIGATION_CATEGORY;
 
-    NavArea::~NavArea()
-    {
-    }
-    
-    void NavArea::RegisterObject(Context* context)
-    {
-        context->RegisterFactory<NavArea>(NAVIGATION_CATEGORY);
+NavArea::NavArea(Context* context) :
+    Component(context),
+    areaID_(DEFAULT_AREA_ID),
+    boundingBox_(DEFAULT_BOUNDING_BOX_MIN, DEFAULT_BOUNDING_BOX_MAX)
+{
+}
 
-        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 ID", GetAreaID, SetAreaID, unsigned, DEFAULT_AREA_ID, AM_DEFAULT);
-    }
+NavArea::~NavArea()
+{
+}
 
-    void NavArea::SetAreaID(unsigned newID)
-    {
-        if (newID > MAX_NAV_AREA_ID)
-            LOGERRORF("NavArea Area ID %u exceeds maximum value of %u", newID, MAX_NAV_AREA_ID);
-        areaID_ = (unsigned char)newID;
-        MarkNetworkUpdate();
-    }
+void NavArea::RegisterObject(Context* context)
+{
+    context->RegisterFactory<NavArea>(NAVIGATION_CATEGORY);
 
-    BoundingBox NavArea::GetWorldBoundingBox() const
+    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 ID", GetAreaID, SetAreaID, unsigned, DEFAULT_AREA_ID, AM_DEFAULT);
+}
+
+void NavArea::SetAreaID(unsigned newID)
+{
+    if (newID > MAX_NAV_AREA_ID)
+        LOGERRORF("NavArea Area ID %u exceeds maximum value of %u", newID, MAX_NAV_AREA_ID);
+    areaID_ = (unsigned char)newID;
+    MarkNetworkUpdate();
+}
+
+BoundingBox NavArea::GetWorldBoundingBox() 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());
-        return boundingBox_.Transformed(mat);
+        debug->AddBoundingBox(boundingBox_, mat, Color::GREEN, depthTest);
     }
+}
 
-    void NavArea::DrawDebugGeometry(DebugRenderer* debug, bool depthTest) 
-    {
-        if (debug && IsEnabledEffective())
-        {
-            Matrix3x4 mat;
-            mat.SetTranslation(node_->GetWorldPosition());
-            debug->AddBoundingBox(boundingBox_, mat, Color::GREEN, depthTest);
-        }
-    }
 }

+ 33 - 31
Source/Atomic/Navigation/NavArea.h

@@ -22,45 +22,47 @@
 
 #pragma once
 
-#include "../Scene/Component.h"
 #include "../Math/BoundingBox.h"
+#include "../Scene/Component.h"
 
 namespace Atomic
 {
-    class ATOMIC_API NavArea : public Component
-    {
-        OBJECT(NavArea);
 
-    public:
-        /// Construct.
-        NavArea(Context*);
-        /// Destruct.
-        virtual ~NavArea();
-        /// Register object factory and attributes.
-        static void RegisterObject(Context*);
+class ATOMIC_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);
+
+    /// Get the area id for this volume.
+    unsigned GetAreaID() const { return (unsigned)areaID_; }
+
+    /// Set the area id for this volume.
+    void SetAreaID(unsigned newID);
 
-        /// Render debug geometry for the bounds.
-        virtual void DrawDebugGeometry(DebugRenderer* debug, bool depthTest);
+    /// Get the bounding box of this navigation area, in local space.
+    BoundingBox GetBoundingBox() const { return boundingBox_; }
 
-        /// Get the area id for this volume.
-        unsigned GetAreaID() const { return (unsigned)areaID_; }
-        /// Set the area id for this volume.
-        void SetAreaID(unsigned newID);
+    /// Set the bounding box of this area, in local space.
+    void SetBoundingBox(const BoundingBox& bnds) { boundingBox_ = bnds; }
 
-        /// Get the bounding box of this navigation area, in local space.
-        BoundingBox GetBoundingBox() const { return boundingBox_; }
-        /// Set the bounding box of this area, in local space.
-        void SetBoundingBox(const BoundingBox& bnds) { boundingBox_ = bnds; }
+    /// Get the bounds of this navigation area in world space.
+    BoundingBox GetWorldBoundingBox() const;
 
-        /// Get the bounds of this navigation area in world space.
-        BoundingBox GetWorldBoundingBox() const;
+private:
+    /// Bounds of area to mark.
+    BoundingBox boundingBox_;
+    /// Area id to assign to the marked area.
+    unsigned char areaID_;
+};
 
-    private:
-        /// Bounds of area to mark.
-        BoundingBox boundingBox_;
-        /// Flags to assign to the marked area of the navigation map.
-        unsigned flags_;
-        /// Area id to assign to the marked area.
-        unsigned char areaID_;
-    };
 }

+ 13 - 23
Source/Atomic/Navigation/NavBuildData.cpp

@@ -24,33 +24,26 @@
 
 #include "../Navigation/NavBuildData.h"
 
-#include <Recast/include/Recast.h>
-#include <Detour/include/DetourNavMesh.h>
-#include <Detour/include/DetourNavMeshBuilder.h>
-#include <Detour/include/DetourNavMeshQuery.h>
-#include <DetourTileCache/include/DetourTileCache.h>
 #include <DetourTileCache/include/DetourTileCacheBuilder.h>
+#include <Recast/include/Recast.h>
 
 namespace Atomic
 {
 
 NavBuildData::NavBuildData() :
-    ctx_(0),
+    ctx_(new rcContext(true)),
     heightField_(0),
     compactHeightField_(0)
 {
-    ctx_ = new rcContext(true);
 }
 
 NavBuildData::~NavBuildData()
 {
-    if (ctx_)
-        delete(ctx_);
-    rcFreeHeightField(heightField_);
-    rcFreeCompactHeightfield(compactHeightField_);
-
+    delete(ctx_);
     ctx_ = 0;
+    rcFreeHeightField(heightField_);
     heightField_ = 0;
+    rcFreeCompactHeightfield(compactHeightField_);
     compactHeightField_ = 0;
 }
 
@@ -65,33 +58,30 @@ SimpleNavBuildData::SimpleNavBuildData() :
 SimpleNavBuildData::~SimpleNavBuildData()
 {
     rcFreeContourSet(contourSet_);
-    rcFreePolyMesh(polyMesh_);
-    rcFreePolyMeshDetail(polyMeshDetail_);
-
     contourSet_ = 0;
+    rcFreePolyMesh(polyMesh_);
     polyMesh_ = 0;
+    rcFreePolyMeshDetail(polyMeshDetail_);
     polyMeshDetail_ = 0;
 }
 
 DynamicNavBuildData::DynamicNavBuildData(dtTileCacheAlloc* allocator) :
+    NavBuildData(),
     contourSet_(0),
-    heightFieldLayers_(0),
     polyMesh_(0),
+    heightFieldLayers_(0),
     alloc_(allocator)
 {
+    assert(allocator);
 }
 
 DynamicNavBuildData::~DynamicNavBuildData()
 {
-    if (contourSet_)
-        dtFreeTileCacheContourSet(alloc_, contourSet_);
-    if (polyMesh_)
-        dtFreeTileCachePolyMesh(alloc_, polyMesh_);
-    if (heightFieldLayers_)
-        rcFreeHeightfieldLayerSet(heightFieldLayers_);
-
+    dtFreeTileCacheContourSet(alloc_, contourSet_);
     contourSet_ = 0;
+    dtFreeTileCachePolyMesh(alloc_, polyMesh_);
     polyMesh_ = 0;
+    rcFreeHeightfieldLayerSet(heightFieldLayers_);
     heightFieldLayers_ = 0;
 }
 

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

@@ -27,16 +27,16 @@
 #include "../Math/Vector3.h"
 
 class rcContext;
-struct rcHeightfield;
-struct rcCompactHeightfield;
-struct rcContourSet;
-struct rcPolyMesh;
-struct rcPolyMeshDetail;
 
-struct rcHeightfieldLayerSet;
 struct dtTileCacheContourSet;
 struct dtTileCachePolyMesh;
 struct dtTileCacheAlloc;
+struct rcCompactHeightfield;
+struct rcContourSet;
+struct rcHeightfield;
+struct rcHeightfieldLayerSet;
+struct rcPolyMesh;
+struct rcPolyMeshDetail;
 
 namespace Atomic
 {

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

@@ -42,6 +42,7 @@ public:
 
     /// Set whether geometry is automatically collected from child nodes. Default true.
     void SetRecursive(bool enable);
+
     /// Return whether geometry is automatically collected from child nodes.
     bool IsRecursive() const { return recursive_; }
 

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

@@ -43,6 +43,16 @@ EVENT(E_NAVIGATION_AREA_REBUILT, NavigationAreaRebuilt)
     PARAM(P_BOUNDSMAX, BoundsMax); // Vector3
 }
 
+/// Crowd agent formation.
+EVENT(E_CROWD_AGENT_FORMATION, CrowdAgentFormation)
+{
+    PARAM(P_NODE, Node); // Node pointer
+    PARAM(P_CROWD_AGENT, CrowdAgent); // CrowdAgent pointer
+    PARAM(P_INDEX, Index); // unsigned
+    PARAM(P_SIZE, Size); // unsigned
+    PARAM(P_POSITION, Position); // Vector3 [in/out]
+}
+
 /// Crowd agent has been repositioned.
 EVENT(E_CROWD_AGENT_REPOSITION, CrowdAgentReposition)
 {
@@ -51,9 +61,10 @@ EVENT(E_CROWD_AGENT_REPOSITION, CrowdAgentReposition)
     PARAM(P_POSITION, Position); // Vector3
     PARAM(P_VELOCITY, Velocity); // Vector3
     PARAM(P_ARRIVED, Arrived); // bool
+    PARAM(P_TIMESTEP, TimeStep); // float
 }
 
-/// Crowd agent's internal state has become invalidated.
+/// Crowd agent's internal state has become invalidated. This is a special case of CrowdAgentStateChanged event.
 EVENT(E_CROWD_AGENT_FAILURE, CrowdAgentFailure)
 {
     PARAM(P_NODE, Node); // Node pointer

+ 99 - 66
Source/Atomic/Navigation/NavigationMesh.cpp

@@ -22,17 +22,18 @@
 
 #include "../Precompiled.h"
 
-#ifdef ATOMIC_PHYSICS
-#include "../Physics/CollisionShape.h"
-#endif
 #include "../Core/Context.h"
+#include "../Core/Profiler.h"
 #include "../Graphics/DebugRenderer.h"
 #include "../Graphics/Drawable.h"
-#include "../Navigation/DynamicNavigationMesh.h"
 #include "../Graphics/Geometry.h"
+#include "../Atomic3D//Model.h"
+#include "../Atomic3D/StaticModel.h"
+#include "../Atomic3D/TerrainPatch.h"
 #include "../IO/Log.h"
 #include "../IO/MemoryBuffer.h"
-#include "../Atomic3D/Model.h"
+#include "../Navigation/CrowdAgent.h"
+#include "../Navigation/DynamicNavigationMesh.h"
 #include "../Navigation/NavArea.h"
 #include "../Navigation/NavBuildData.h"
 #include "../Navigation/Navigable.h"
@@ -40,11 +41,10 @@
 #include "../Navigation/NavigationMesh.h"
 #include "../Navigation/Obstacle.h"
 #include "../Navigation/OffMeshConnection.h"
-#include "../Core/Profiler.h"
+#ifdef ATOMIC_PHYSICS
+#include "../Physics/CollisionShape.h"
+#endif
 #include "../Scene/Scene.h"
-#include "../Atomic3D/StaticModel.h"
-#include "../Atomic3D/TerrainPatch.h"
-#include "../IO/VectorBuffer.h"
 
 #include <cfloat>
 #include <Detour/include/DetourNavMesh.h>
@@ -52,9 +52,6 @@
 #include <Detour/include/DetourNavMeshQuery.h>
 #include <Recast/include/Recast.h>
 
-#include "../Navigation/CrowdAgent.h"
-#include "../Navigation/DetourCrowdManager.h"
-
 #include "../DebugNew.h"
 
 namespace Atomic
@@ -156,11 +153,15 @@ void NavigationMesh::RegisterObject(Context* context)
     ACCESSOR_ATTRIBUTE("Region Merge Size", GetRegionMergeSize, SetRegionMergeSize, float, DEFAULT_REGION_MERGE_SIZE, AM_DEFAULT);
     ACCESSOR_ATTRIBUTE("Edge Max Length", GetEdgeMaxLength, SetEdgeMaxLength, float, DEFAULT_EDGE_MAX_LENGTH, AM_DEFAULT);
     ACCESSOR_ATTRIBUTE("Edge Max Error", GetEdgeMaxError, SetEdgeMaxError, float, DEFAULT_EDGE_MAX_ERROR, AM_DEFAULT);
-    ACCESSOR_ATTRIBUTE("Detail Sample Distance", GetDetailSampleDistance, SetDetailSampleDistance, float, DEFAULT_DETAIL_SAMPLE_DISTANCE, AM_DEFAULT);
-    ACCESSOR_ATTRIBUTE("Detail Sample Max Error", GetDetailSampleMaxError, SetDetailSampleMaxError, float, DEFAULT_DETAIL_SAMPLE_MAX_ERROR, AM_DEFAULT);
+    ACCESSOR_ATTRIBUTE("Detail Sample Distance", GetDetailSampleDistance, SetDetailSampleDistance, float,
+        DEFAULT_DETAIL_SAMPLE_DISTANCE, AM_DEFAULT);
+    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);
+    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);
 }
@@ -194,7 +195,7 @@ void NavigationMesh::DrawDebugGeometry(DebugRenderer* debug, bool depthTest)
                             worldTransform * *reinterpret_cast<const Vector3*>(&tile->verts[poly->verts[(j + 1) % poly->vertCount] * 3]),
                             Color::YELLOW,
                             depthTest
-                            );
+                        );
                     }
                 }
             }
@@ -373,7 +374,7 @@ bool NavigationMesh::Build()
         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_);
+        unsigned maxTiles = NextPowerOfTwo((unsigned)(numTilesX_ * numTilesZ_));
         unsigned tileBits = 0;
         unsigned temp = maxTiles;
         while (temp > 1)
@@ -382,7 +383,7 @@ bool NavigationMesh::Build()
             ++tileBits;
         }
 
-        unsigned maxPolys = 1 << (22 - tileBits);
+        unsigned maxPolys = (unsigned)(1 << (22 - tileBits));
 
         dtNavMeshParams params;
         rcVcopy(params.orig, &boundingBox_.min_.x_);
@@ -475,9 +476,10 @@ bool NavigationMesh::Build(const BoundingBox& boundingBox)
     return true;
 }
 
-Vector3 NavigationMesh::FindNearestPoint(const Vector3& point, const Vector3& extents)
+Vector3 NavigationMesh::FindNearestPoint(const Vector3& point, const Vector3& extents, const dtQueryFilter* filter,
+    dtPolyRef* nearestRef)
 {
-    if(!InitializeQuery())
+    if (!InitializeQuery())
         return point;
 
     const Matrix3x4& transform = node_->GetWorldTransform();
@@ -487,14 +489,14 @@ Vector3 NavigationMesh::FindNearestPoint(const Vector3& point, const Vector3& ex
     Vector3 nearestPoint;
 
     dtPolyRef pointRef;
-    navMeshQuery_->findNearestPoly(&localPoint.x_, &extents.x_, queryFilter_, &pointRef, &nearestPoint.x_);
-    if (!pointRef)
-        return point;
-
-    return transform*nearestPoint;
+    if (!nearestRef)
+        nearestRef = &pointRef;
+    navMeshQuery_->findNearestPoly(&localPoint.x_, &extents.x_, filter ? filter : queryFilter_, nearestRef, &nearestPoint.x_);
+    return *nearestRef ? transform * nearestPoint : point;
 }
 
-Vector3 NavigationMesh::MoveAlongSurface(const Vector3& start, const Vector3& end, const Vector3& extents, int maxVisited)
+Vector3 NavigationMesh::MoveAlongSurface(const Vector3& start, const Vector3& end, const Vector3& extents, int maxVisited,
+    const dtQueryFilter* filter)
 {
     if (!InitializeQuery())
         return end;
@@ -505,21 +507,23 @@ Vector3 NavigationMesh::MoveAlongSurface(const Vector3& start, const Vector3& en
     Vector3 localStart = inverse * start;
     Vector3 localEnd = inverse * end;
 
+    const dtQueryFilter* queryFilter = filter ? filter : queryFilter_;
     dtPolyRef startRef;
-    navMeshQuery_->findNearestPoly(&localStart.x_, &extents.x_, queryFilter_, &startRef, 0);
+    navMeshQuery_->findNearestPoly(&localStart.x_, &extents.x_, queryFilter, &startRef, 0);
     if (!startRef)
         return end;
 
     Vector3 resultPos;
     int visitedCount = 0;
     maxVisited = Max(maxVisited, 0);
-    PODVector<dtPolyRef> visited(maxVisited);
-    navMeshQuery_->moveAlongSurface(startRef, &localStart.x_, &localEnd.x_, queryFilter_, &resultPos.x_, maxVisited ?
+    PODVector<dtPolyRef> visited((unsigned)maxVisited);
+    navMeshQuery_->moveAlongSurface(startRef, &localStart.x_, &localEnd.x_, queryFilter, &resultPos.x_, maxVisited ?
         &visited[0] : (dtPolyRef*)0, &visitedCount, maxVisited);
     return transform * resultPos;
 }
 
-void NavigationMesh::FindPath(PODVector<Vector3>& dest, const Vector3& start, const Vector3& end, const Vector3& extents)
+void NavigationMesh::FindPath(PODVector<Vector3>& dest, const Vector3& start, const Vector3& end, const Vector3& extents,
+    const dtQueryFilter* filter)
 {
     PROFILE(FindPath);
 
@@ -535,10 +539,11 @@ void NavigationMesh::FindPath(PODVector<Vector3>& dest, const Vector3& start, co
     Vector3 localStart = inverse * start;
     Vector3 localEnd = inverse * end;
 
+    const dtQueryFilter* queryFilter = filter ? filter : queryFilter_;
     dtPolyRef startRef;
     dtPolyRef endRef;
-    navMeshQuery_->findNearestPoly(&localStart.x_, &extents.x_, queryFilter_, &startRef, 0);
-    navMeshQuery_->findNearestPoly(&localEnd.x_, &extents.x_, queryFilter_, &endRef, 0);
+    navMeshQuery_->findNearestPoly(&localStart.x_, &extents.x_, queryFilter, &startRef, 0);
+    navMeshQuery_->findNearestPoly(&localEnd.x_, &extents.x_, queryFilter, &endRef, 0);
 
     if (!startRef || !endRef)
         return;
@@ -546,7 +551,7 @@ void NavigationMesh::FindPath(PODVector<Vector3>& dest, const Vector3& start, co
     int numPolys = 0;
     int numPathPoints = 0;
 
-    navMeshQuery_->findPath(startRef, endRef, &localStart.x_, &localEnd.x_, queryFilter_, pathData_->polys_, &numPolys,
+    navMeshQuery_->findPath(startRef, endRef, &localStart.x_, &localEnd.x_, queryFilter, pathData_->polys_, &numPolys,
         MAX_POLYS);
     if (!numPolys)
         return;
@@ -565,7 +570,7 @@ void NavigationMesh::FindPath(PODVector<Vector3>& dest, const Vector3& start, co
         dest.Push(transform * pathData_->pathPoints_[i]);
 }
 
-Vector3 NavigationMesh::GetRandomPoint()
+Vector3 NavigationMesh::GetRandomPoint(const dtQueryFilter* filter, dtPolyRef* randomRef)
 {
     if (!InitializeQuery())
         return Vector3::ZERO;
@@ -573,13 +578,17 @@ Vector3 NavigationMesh::GetRandomPoint()
     dtPolyRef polyRef;
     Vector3 point(Vector3::ZERO);
 
-    navMeshQuery_->findRandomPoint(queryFilter_, Random, &polyRef, &point.x_);
+    navMeshQuery_->findRandomPoint(filter ? filter : queryFilter_, Random, randomRef ? randomRef : &polyRef, &point.x_);
 
     return node_->GetWorldTransform() * point;
 }
 
-Vector3 NavigationMesh::GetRandomPointInCircle(const Vector3& center, float radius, const Vector3& extents)
+Vector3 NavigationMesh::GetRandomPointInCircle(const Vector3& center, float radius, const Vector3& extents,
+    const dtQueryFilter* filter, dtPolyRef* randomRef)
 {
+    if (randomRef)
+        *randomRef = 0;
+
     if (!InitializeQuery())
         return center;
 
@@ -587,21 +596,30 @@ Vector3 NavigationMesh::GetRandomPointInCircle(const Vector3& center, float radi
     Matrix3x4 inverse = transform.Inverse();
     Vector3 localCenter = inverse * center;
 
+    const dtQueryFilter* queryFilter = filter ? filter : queryFilter_;
     dtPolyRef startRef;
-    navMeshQuery_->findNearestPoly(&localCenter.x_, &extents.x_, queryFilter_, &startRef, 0);
+    navMeshQuery_->findNearestPoly(&localCenter.x_, &extents.x_, queryFilter, &startRef, 0);
     if (!startRef)
         return center;
 
     dtPolyRef polyRef;
+    if (!randomRef)
+        randomRef = &polyRef;
     Vector3 point(localCenter);
 
-    navMeshQuery_->findRandomPointAroundCircle(startRef, &localCenter.x_, radius, queryFilter_, Random, &polyRef, &point.x_);
+    navMeshQuery_->findRandomPointAroundCircle(startRef, &localCenter.x_, radius, queryFilter, Random, randomRef, &point.x_);
 
     return transform * point;
 }
 
-float NavigationMesh::GetDistanceToWall(const Vector3& point, float radius, const Vector3& extents)
+float NavigationMesh::GetDistanceToWall(const Vector3& point, float radius, const Vector3& extents, const dtQueryFilter* filter,
+    Vector3* hitPos, Vector3* hitNormal)
 {
+    if (hitPos)
+        *hitPos = Vector3::ZERO;
+    if (hitNormal)
+        *hitNormal = Vector3::DOWN;
+
     if (!InitializeQuery())
         return radius;
 
@@ -609,21 +627,30 @@ float NavigationMesh::GetDistanceToWall(const Vector3& point, float radius, cons
     Matrix3x4 inverse = transform.Inverse();
     Vector3 localPoint = inverse * point;
 
+    const dtQueryFilter* queryFilter = filter ? filter : queryFilter_;
     dtPolyRef startRef;
-    navMeshQuery_->findNearestPoly(&localPoint.x_, &extents.x_, queryFilter_, &startRef, 0);
+    navMeshQuery_->findNearestPoly(&localPoint.x_, &extents.x_, queryFilter, &startRef, 0);
     if (!startRef)
         return radius;
 
     float hitDist = radius;
-    Vector3 hitPos;
-    Vector3 hitNormal;
-
-    navMeshQuery_->findDistanceToWall(startRef, &localPoint.x_, radius, queryFilter_, &hitDist, &hitPos.x_, &hitNormal.x_);
+    Vector3 pos;
+    if (!hitPos)
+        hitPos = &pos;
+    Vector3 normal;
+    if (!hitNormal)
+        hitNormal = &normal;
+
+    navMeshQuery_->findDistanceToWall(startRef, &localPoint.x_, radius, queryFilter, &hitDist, &hitPos->x_, &hitNormal->x_);
     return hitDist;
 }
 
-Vector3 NavigationMesh::Raycast(const Vector3& start, const Vector3& end, const Vector3& extents)
+Vector3 NavigationMesh::Raycast(const Vector3& start, const Vector3& end, const Vector3& extents, const dtQueryFilter* filter,
+    Vector3* hitNormal)
 {
+    if (hitNormal)
+        *hitNormal = Vector3::DOWN;
+
     if (!InitializeQuery())
         return end;
 
@@ -633,16 +660,20 @@ Vector3 NavigationMesh::Raycast(const Vector3& start, const Vector3& end, const
     Vector3 localStart = inverse * start;
     Vector3 localEnd = inverse * end;
 
+    const dtQueryFilter* queryFilter = filter ? filter : queryFilter_;
     dtPolyRef startRef;
-    navMeshQuery_->findNearestPoly(&localStart.x_, &extents.x_, queryFilter_, &startRef, 0);
+    navMeshQuery_->findNearestPoly(&localStart.x_, &extents.x_, queryFilter, &startRef, 0);
     if (!startRef)
         return end;
 
-    Vector3 localHitNormal;
+    Vector3 normal;
+    if (!hitNormal)
+        hitNormal = &normal;
     float t;
     int numPolys;
 
-    navMeshQuery_->raycast(startRef, &localStart.x_, &localEnd.x_, queryFilter_, &t, &localHitNormal.x_, pathData_->polys_, &numPolys, MAX_POLYS);
+    navMeshQuery_->raycast(startRef, &localStart.x_, &localEnd.x_, queryFilter, &t, &hitNormal->x_, pathData_->polys_, &numPolys,
+        MAX_POLYS);
     if (t == FLT_MAX)
         t = 1.0f;
 
@@ -771,8 +802,8 @@ PODVector<unsigned char> NavigationMesh::GetNavigationDataAttr() const
                 ret.WriteInt(x);
                 ret.WriteInt(z);
                 ret.WriteUInt(navMesh->getTileRef(tile));
-                ret.WriteUInt(tile->dataSize);
-                ret.Write(tile->data, tile->dataSize);
+                ret.WriteUInt((unsigned)tile->dataSize);
+                ret.Write(tile->data, (unsigned)tile->dataSize);
             }
         }
     }
@@ -833,7 +864,8 @@ void NavigationMesh::CollectGeometries(Vector<NavigationGeometryInfo>& geometryL
     }
 }
 
-void NavigationMesh::CollectGeometries(Vector<NavigationGeometryInfo>& geometryList, Node* node, HashSet<Node*>& processedNodes, bool recursive)
+void NavigationMesh::CollectGeometries(Vector<NavigationGeometryInfo>& geometryList, Node* node, HashSet<Node*>& processedNodes,
+    bool recursive)
 {
     // Make sure nodes are not included twice
     if (processedNodes.Contains(node))
@@ -905,7 +937,7 @@ void NavigationMesh::CollectGeometries(Vector<NavigationGeometryInfo>& geometryL
     if (recursive)
     {
         const Vector<SharedPtr<Node> >& children = node->GetChildren();
-        for(unsigned i = 0; i < children.Size(); ++i)
+        for (unsigned i = 0; i < children.Size(); ++i)
             CollectGeometries(geometryList, children[i], processedNodes, recursive);
     }
 }
@@ -929,9 +961,9 @@ void NavigationMesh::GetTileGeometry(NavBuildData* build, Vector<NavigationGeome
                 build->offMeshVertices_.Push(start);
                 build->offMeshVertices_.Push(end);
                 build->offMeshRadii_.Push(connection->GetRadius());
-                build->offMeshFlags_.Push(connection->GetMask());
+                build->offMeshFlags_.Push((unsigned short)connection->GetMask());
                 build->offMeshAreas_.Push((unsigned char)connection->GetAreaID());
-                build->offMeshDir_.Push(connection->IsBidirectional() ? DT_OFFMESH_CON_BIDIR : 0);
+                build->offMeshDir_.Push((unsigned char)(connection->IsBidirectional() ? DT_OFFMESH_CON_BIDIR : 0));
                 continue;
             }
             else if (geometryList[i].component_->GetType() == NavArea::GetTypeStatic())
@@ -1088,15 +1120,15 @@ bool NavigationMesh::BuildTile(Vector<NavigationGeometryInfo>& geometryList, int
     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)
-    ));
+            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)
+        ));
 
     SimpleNavBuildData build;
 
@@ -1180,7 +1212,8 @@ bool NavigationMesh::BuildTile(Vector<NavigationGeometryInfo>& geometryList, int
 
     // 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_);
+        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)
     {
@@ -1366,7 +1399,7 @@ void RegisterNavigationLibrary(Context* context)
     NavigationMesh::RegisterObject(context);
     OffMeshConnection::RegisterObject(context);
     CrowdAgent::RegisterObject(context);
-    DetourCrowdManager::RegisterObject(context);
+    CrowdManager::RegisterObject(context);
     DynamicNavigationMesh::RegisterObject(context);
     Obstacle::RegisterObject(context);
     NavArea::RegisterObject(context);

+ 52 - 22
Source/Atomic/Navigation/NavigationMesh.h

@@ -23,22 +23,20 @@
 #pragma once
 
 #include "../Container/ArrayPtr.h"
-#include "../Math/BoundingBox.h"
-#include "../Scene/Component.h"
 #include "../Container/HashSet.h"
+#include "../Math/BoundingBox.h"
 #include "../Math/Matrix3x4.h"
+#include "../Scene/Component.h"
+
+#ifdef DT_POLYREF64
+typedef uint64_t dtPolyRef;
+#else
+typedef unsigned int dtPolyRef;
+#endif
 
 class dtNavMesh;
 class dtNavMeshQuery;
 class dtQueryFilter;
-struct dtNavMeshCreateParams;
-class rcContext;
-struct rcHeightfield;
-struct rcCompactHeightfield;
-struct rcContourSet;
-struct rcPolyMesh;
-struct rcPolyMeshDetail;
-struct rcHeightFieldLayerSet;
 
 namespace Atomic
 {
@@ -71,7 +69,8 @@ struct NavigationGeometryInfo
 class ATOMIC_API NavigationMesh : public Component
 {
     OBJECT(NavigationMesh);
-    friend class DetourCrowdManager;
+
+    friend class CrowdManager;
 
 public:
     /// Construct.
@@ -118,68 +117,98 @@ public:
     virtual bool Build();
     /// Rebuild part of the navigation mesh contained by the world-space bounding box. Return true if successful.
     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);
+    /// Find the nearest point on the navigation mesh to a given point. Extents specifies how far out from the specified point to check along each axis.
+    Vector3 FindNearestPoint
+        (const Vector3& point, const Vector3& extents = Vector3::ONE, const dtQueryFilter* filter = 0, dtPolyRef* nearestRef = 0);
     /// Try to move along the surface from one point to another.
-    Vector3 MoveAlongSurface(const Vector3& start, const Vector3& end, const Vector3& extents=Vector3::ONE, int maxVisited=3);
+    Vector3 MoveAlongSurface(const Vector3& start, const Vector3& end, const Vector3& extents = Vector3::ONE, int maxVisited = 3,
+        const dtQueryFilter* filter = 0);
     /// Find a path between world space points. Return non-empty list of points if successful. Extents specifies how far off the navigation mesh the points can be.
-    void FindPath(PODVector<Vector3>& dest, const Vector3& start, const Vector3& end, const Vector3& extents = Vector3::ONE);
+    void FindPath(PODVector<Vector3>& dest, const Vector3& start, const Vector3& end, const Vector3& extents = Vector3::ONE,
+        const dtQueryFilter* filter = 0);
     /// Return a random point on the navigation mesh.
-    Vector3 GetRandomPoint();
+    Vector3 GetRandomPoint(const dtQueryFilter* filter = 0, dtPolyRef* randomRef = 0);
     /// Return a random point on the navigation mesh within a circle. The circle radius is only a guideline and in practice the returned point may be further away.
-    Vector3 GetRandomPointInCircle(const Vector3& center, float radius, const Vector3& extents = Vector3::ONE);
+    Vector3 GetRandomPointInCircle
+        (const Vector3& center, float radius, const Vector3& extents = Vector3::ONE, const dtQueryFilter* filter = 0,
+            dtPolyRef* randomRef = 0);
     /// Return distance to wall from a point. Maximum search radius must be specified.
-    float GetDistanceToWall(const Vector3& point, float radius, const Vector3& extents = Vector3::ONE);
+    float GetDistanceToWall
+        (const Vector3& point, float radius, const Vector3& extents = Vector3::ONE, const dtQueryFilter* filter = 0,
+            Vector3* hitPos = 0, Vector3* hitNormal = 0);
     /// Perform a walkability raycast on the navigation mesh between start and end and return the point where a wall was hit, or the end point if no walls.
-    Vector3 Raycast(const Vector3& start, const Vector3& end, const Vector3& extents = Vector3::ONE);
+    Vector3 Raycast
+        (const Vector3& start, const Vector3& end, const Vector3& extents = Vector3::ONE, const dtQueryFilter* filter = 0,
+            Vector3* hitNormal = 0);
     /// Add debug geometry to the debug renderer.
     void DrawDebugGeometry(bool depthTest);
 
     /// 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.
     float GetCellSize() const { return cellSize_; }
+
     /// Return cell height.
     float GetCellHeight() const { return cellHeight_; }
+
     /// Return navigation agent height.
     float GetAgentHeight() const { return agentHeight_; }
+
     /// Return navigation agent radius.
     float GetAgentRadius() const { return agentRadius_; }
+
     /// Return navigation agent max vertical climb.
     float GetAgentMaxClimb() const { return agentMaxClimb_; }
+
     /// Return navigation agent max slope.
     float GetAgentMaxSlope() const { return agentMaxSlope_; }
+
     /// Return region minimum size.
     float GetRegionMinSize() const { return regionMinSize_; }
+
     /// Return region merge size.
     float GetRegionMergeSize() const { return regionMergeSize_; }
+
     /// Return edge max length.
     float GetEdgeMaxLength() const { return edgeMaxLength_; }
+
     /// Return edge max error.
     float GetEdgeMaxError() const { return edgeMaxError_; }
+
     /// Return detail sampling distance.
     float GetDetailSampleDistance() const { return detailSampleDistance_; }
+
     /// Return detail sampling maximum error.
     float GetDetailSampleMaxError() const { return detailSampleMaxError_; }
+
     /// Return navigation mesh bounding box padding.
     const Vector3& GetPadding() const { return padding_; }
+
     /// Get the current cost of an area
     float GetAreaCost(unsigned areaID) const;
+
     /// Return whether has been initialized with valid navigation data.
     bool IsInitialized() const { return navMesh_ != 0; }
+
     /// Return local space bounding box of the navigation mesh.
     const BoundingBox& GetBoundingBox() const { return boundingBox_; }
+
     /// Return world space bounding box of the navigation mesh.
     BoundingBox GetWorldBoundingBox() const;
+
     /// Return number of tiles.
     IntVector2 GetNumTiles() const { return IntVector2(numTilesX_, numTilesZ_); }
 
     /// Set the partition type used for polygon generation.
     void SetPartitionType(NavmeshPartitionType aType);
+
     /// Return Partition Type.
     NavmeshPartitionType GetPartitionType() const { return partitionType_; }
 
@@ -190,11 +219,13 @@ public:
 
     /// 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_; }
 
@@ -202,7 +233,8 @@ 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);
+    void
+        CollectGeometries(Vector<NavigationGeometryInfo>& geometryList, Node* node, HashSet<Node*>& processedNodes, bool recursive);
     /// Get geometry data within a bounding box.
     void GetTileGeometry(NavBuildData* build, Vector<NavigationGeometryInfo>& geometryList, BoundingBox& box);
     /// Add a triangle mesh to the geometry data.
@@ -263,8 +295,6 @@ protected:
     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_;
 
     /// Debug draw OffMeshConnection components.
     bool drawOffMeshConnections_;

+ 2 - 3
Source/Atomic/Navigation/Obstacle.cpp

@@ -22,12 +22,11 @@
 
 #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/DynamicNavigationMesh.h"
+#include "../Navigation/Obstacle.h"
 #include "../Navigation/NavigationEvents.h"
 #include "../Scene/Scene.h"
 

+ 4 - 0
Source/Atomic/Navigation/Obstacle.h

@@ -34,6 +34,7 @@ class DynamicNavigationMesh;
 class ATOMIC_API Obstacle : public Component
 {
     OBJECT(Obstacle)
+
     friend class DynamicNavigationMesh;
 
 public:
@@ -50,10 +51,13 @@ public:
 
     /// Get the height of this obstacle.
     float GetHeight() const { return height_; }
+
     /// Set the height of this obstacle.
     void SetHeight(float);
+
     /// Get the blocking radius of this obstacle.
     float GetRadius() const { return radius_; }
+
     /// Set the blocking radius of this obstacle.
     void SetRadius(float);
 

+ 4 - 0
Source/Atomic/Navigation/OffMeshConnection.h

@@ -60,12 +60,16 @@ public:
 
     /// Return endpoint node.
     Node* GetEndPoint() const;
+
     /// Return radius.
     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_; }
 

Some files were not shown because too many files changed in this diff