Josh Engebretson 10 years ago
parent
commit
3e996f9c13

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

@@ -1,5 +1,5 @@
 //
-// Copyright (c) 2008-2014 the Urho3D project.
+// Copyright (c) 2008-2015 the Urho3D project.
 //
 // Permission is hereby granted, free of charge, to any person obtaining a copy
 // of this software and associated documentation files (the "Software"), to deal
@@ -123,7 +123,9 @@ NavigationMesh::NavigationMesh(Context* context) :
     numTilesX_(0),
     numTilesZ_(0),
     partitionType_(NAVMESH_PARTITION_WATERSHED),
-    keepInterResults_(false)
+    keepInterResults_(false),
+    drawOffMeshConnections_(false),
+    drawNavAreas_(false)
 {
 }
 
@@ -158,6 +160,8 @@ void NavigationMesh::RegisterObject(Context* context)
     ACCESSOR_ATTRIBUTE("Bounding Box Padding", GetPadding, SetPadding, Vector3, Vector3::ONE, AM_DEFAULT);
     MIXED_ACCESSOR_ATTRIBUTE("Navigation Data", GetNavigationDataAttr, SetNavigationDataAttr, PODVector<unsigned char>, Variant::emptyBuffer, AM_FILE | AM_NOEDIT);
     ENUM_ACCESSOR_ATTRIBUTE("Partition Type", GetPartitionType, SetPartitionType, NavmeshPartitionType, navmeshPartitionTypeNames, NAVMESH_PARTITION_WATERSHED, AM_DEFAULT);
+    ACCESSOR_ATTRIBUTE("Draw OffMeshConnections", GetDrawOffMeshConnections, SetDrawOffMeshConnections, bool, false, AM_DEFAULT);
+    ACCESSOR_ATTRIBUTE("Draw NavAreas", GetDrawNavAreas, SetDrawNavAreas, bool, false, AM_DEFAULT);
 }
 
 void NavigationMesh::DrawDebugGeometry(DebugRenderer* debug, bool depthTest)
@@ -195,6 +199,36 @@ void NavigationMesh::DrawDebugGeometry(DebugRenderer* debug, bool depthTest)
             }
         }
     }
+
+    Scene* scene = GetScene();
+    if (scene)
+    {
+        // Draw OffMeshConnection components
+        if (drawOffMeshConnections_)
+        {
+            PODVector<Node*> connections;
+            scene->GetChildrenWithComponent<OffMeshConnection>(connections, true);
+            for (unsigned i = 0; i < connections.Size(); ++i)
+            {
+                OffMeshConnection* connection = connections[i]->GetComponent<OffMeshConnection>();
+                if (connection && connection->IsEnabledEffective())
+                    connection->DrawDebugGeometry(debug, depthTest);
+            }
+        }
+
+        // Draw NavArea components
+        if (drawNavAreas_)
+        {
+            PODVector<Node*> areas;
+            scene->GetChildrenWithComponent<NavArea>(areas, true);
+            for (unsigned i = 0; i < areas.Size(); ++i)
+            {
+                NavArea* area = areas[i]->GetComponent<NavArea>();
+                if (area && area->IsEnabledEffective())
+                    area->DrawDebugGeometry(debug, depthTest);
+            }
+        }
+    }
 }
 
 void NavigationMesh::SetMeshName(const String& newName)
@@ -499,7 +533,7 @@ void NavigationMesh::FindPath(PODVector<Vector3>& dest, const Vector3& start, co
 
     Vector3 localStart = inverse * start;
     Vector3 localEnd = inverse * end;
-    
+
     dtPolyRef startRef;
     dtPolyRef endRef;
     navMeshQuery_->findNearestPoly(&localStart.x_, &extents.x_, queryFilter_, &startRef, 0);
@@ -803,6 +837,9 @@ void NavigationMesh::CollectGeometries(Vector<NavigationGeometryInfo>& geometryL
     // Make sure nodes are not included twice
     if (processedNodes.Contains(node))
         return;
+    // Exclude obstacles from consideration
+    if (node->HasComponent<Obstacle>())
+        return;
     processedNodes.Insert(node);
 
     Matrix3x4 inverse = node_->GetWorldTransform().Inverse();
@@ -1118,7 +1155,7 @@ bool NavigationMesh::BuildTile(Vector<NavigationGeometryInfo>& geometryList, int
     rcRasterizeTriangles(build.ctx_, &build.vertices_[0].x_, build.vertices_.Size(), &build.indices_[0],
         triAreas.Get(), numTriangles, *build.heightField_, cfg.walkableClimb);
     rcFilterLowHangingWalkableObstacles(build.ctx_, cfg.walkableClimb, *build.heightField_);
-    
+
     rcFilterWalkableLowHeightSpans(build.ctx_, cfg.walkableHeight, *build.heightField_);
     rcFilterLedgeSpans(build.ctx_, cfg.walkableHeight, cfg.walkableClimb, *build.heightField_);
 

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

@@ -1,5 +1,5 @@
 //
-// Copyright (c) 2008-2014 the Urho3D project.
+// Copyright (c) 2008-2015 the Urho3D project.
 //
 // Permission is hereby granted, free of charge, to any person obtaining a copy
 // of this software and associated documentation files (the "Software"), to deal
@@ -45,8 +45,8 @@ namespace Atomic
 
 enum NavmeshPartitionType
 {
-    NAVMESH_PARTITION_WATERSHED,
-    NAVMESH_PARTITION_MONOTONE,
+    NAVMESH_PARTITION_WATERSHED = 0,
+    NAVMESH_PARTITION_MONOTONE
 };
 
 class Geometry;
@@ -188,6 +188,16 @@ public:
     /// Return navigation data attribute.
     virtual PODVector<unsigned char> GetNavigationDataAttr() const;
 
+    /// Draw debug geometry for OffMeshConnection components.
+    void SetDrawOffMeshConnections(bool enable) { drawOffMeshConnections_ = enable; }
+    /// Return whether to draw OffMeshConnection components.
+    bool GetDrawOffMeshConnections() const { return drawOffMeshConnections_; }
+
+    /// Draw debug geometry for NavArea components.
+    void SetDrawNavAreas(bool enable) { drawNavAreas_ = enable; }
+    /// Return whether to draw NavArea components.
+    bool GetDrawNavAreas() const { return drawNavAreas_; }
+
 protected:
     /// Collect geometry from under Navigable components.
     void CollectGeometries(Vector<NavigationGeometryInfo>& geometryList);
@@ -255,6 +265,11 @@ protected:
     bool keepInterResults_;
     /// Internal build resources for creating the navmesh.
     HashMap<Pair<int, int>, NavBuildData*> builds_;
+
+    /// Debug draw OffMeshConnection components.
+    bool drawOffMeshConnections_;
+    /// Debug draw NavArea components.
+    bool drawNavAreas_;
 };
 
 /// Register Navigation library objects.

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

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

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

@@ -1,5 +1,5 @@
 //
-// Copyright (c) 2008-2014 the Urho3D project.
+// Copyright (c) 2008-2015 the Urho3D project.
 //
 // Permission is hereby granted, free of charge, to any person obtaining a copy
 // of this software and associated documentation files (the "Software"), to deal
@@ -35,14 +35,16 @@ extern const char* NAVIGATION_CATEGORY;
 
 static const float DEFAULT_RADIUS = 1.0f;
 static const unsigned DEFAULT_MASK_FLAG = 1;
-static const unsigned DEFAULT_AREA = 0;
+static const unsigned DEFAULT_AREA = 1;
 
 OffMeshConnection::OffMeshConnection(Context* context) :
     Component(context),
     endPointID_(0),
     radius_(DEFAULT_RADIUS),
     bidirectional_(true),
-    endPointDirty_(false)
+    endPointDirty_(false),
+    mask_(DEFAULT_MASK_FLAG),
+    areaId_(DEFAULT_AREA)
 {
 }
 
@@ -53,7 +55,7 @@ OffMeshConnection::~OffMeshConnection()
 void OffMeshConnection::RegisterObject(Context* context)
 {
     context->RegisterFactory<OffMeshConnection>(NAVIGATION_CATEGORY);
-    
+
     ACCESSOR_ATTRIBUTE("Is Enabled", IsEnabled, SetEnabled, bool, true, AM_DEFAULT);
     ATTRIBUTE("Endpoint NodeID", int, endPointID_, 0, AM_DEFAULT | AM_NODEID);
     ATTRIBUTE("Radius", float, radius_, DEFAULT_RADIUS, AM_DEFAULT);
@@ -65,7 +67,7 @@ void OffMeshConnection::RegisterObject(Context* context)
 void OffMeshConnection::OnSetAttribute(const AttributeInfo& attr, const Variant& src)
 {
     Serializable::OnSetAttribute(attr, src);
-    
+
     if (attr.offset_ == offsetof(OffMeshConnection, endPointID_))
         endPointDirty_ = true;
 }
@@ -84,10 +86,10 @@ void OffMeshConnection::DrawDebugGeometry(DebugRenderer* debug, bool depthTest)
 {
     if (!node_ || !endPoint_)
         return;
-    
+
     Vector3 start = node_->GetWorldPosition();
     Vector3 end = endPoint_->GetWorldPosition();
-    
+
     debug->AddSphere(Sphere(start, radius_), Color::WHITE, depthTest);
     debug->AddSphere(Sphere(end, radius_), Color::WHITE, depthTest);
     debug->AddLine(start, end, Color::WHITE, depthTest);

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

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

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

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

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

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

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

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