Browse Source

Update Navigation AS API. Add streaming mode to AS sample 39_CrowdNavigation. Disable bounding box padding on NavigationMesh::Allocate. Send E_NAVIGATION_MESH_REBUILT on NavigationMesh::Allocate.

Eugene Kozlov 8 years ago
parent
commit
f7573971f4

+ 2 - 0
Source/Urho3D/AngelScript/MathAPI.cpp

@@ -86,6 +86,7 @@ static void RegisterMathFunctions(asIScriptEngine* engine)
     engine->RegisterGlobalFunction("bool IsPowerOfTwo(uint)", asFUNCTION(IsPowerOfTwo), asCALL_CDECL);
     engine->RegisterGlobalFunction("bool IsPowerOfTwo(uint)", asFUNCTION(IsPowerOfTwo), asCALL_CDECL);
     engine->RegisterGlobalFunction("uint NextPowerOfTwo(uint)", asFUNCTION(NextPowerOfTwo), asCALL_CDECL);
     engine->RegisterGlobalFunction("uint NextPowerOfTwo(uint)", asFUNCTION(NextPowerOfTwo), asCALL_CDECL);
     engine->RegisterGlobalFunction("uint CountSetBits(uint)", asFUNCTION(CountSetBits), asCALL_CDECL);
     engine->RegisterGlobalFunction("uint CountSetBits(uint)", asFUNCTION(CountSetBits), asCALL_CDECL);
+    engine->RegisterGlobalFunction("uint IntegerLog2(uint)", asFUNCTION(IntegerLog2), asCALL_CDECL);
     engine->RegisterGlobalFunction("uint SDBMHash(uint, uint8)", asFUNCTION(SDBMHash), asCALL_CDECL);
     engine->RegisterGlobalFunction("uint SDBMHash(uint, uint8)", asFUNCTION(SDBMHash), asCALL_CDECL);
     engine->RegisterGlobalFunction("float Random()", asFUNCTIONPR(Random, (), float), asCALL_CDECL);
     engine->RegisterGlobalFunction("float Random()", asFUNCTIONPR(Random, (), float), asCALL_CDECL);
     engine->RegisterGlobalFunction("float Random(float)", asFUNCTIONPR(Random, (float), float), asCALL_CDECL);
     engine->RegisterGlobalFunction("float Random(float)", asFUNCTIONPR(Random, (float), float), asCALL_CDECL);
@@ -1226,6 +1227,7 @@ static void RegisterVolumes(asIScriptEngine* engine)
     engine->RegisterObjectMethod("BoundingBox", "BoundingBox Transformed(const Matrix3&in) const", asMETHODPR(BoundingBox, Transformed, (const Matrix3&) const, BoundingBox), asCALL_THISCALL);
     engine->RegisterObjectMethod("BoundingBox", "BoundingBox Transformed(const Matrix3&in) const", asMETHODPR(BoundingBox, Transformed, (const Matrix3&) const, BoundingBox), asCALL_THISCALL);
     engine->RegisterObjectMethod("BoundingBox", "BoundingBox Transformed(const Matrix3x4&in) const", asMETHODPR(BoundingBox, Transformed, (const Matrix3x4&) const, BoundingBox), asCALL_THISCALL);
     engine->RegisterObjectMethod("BoundingBox", "BoundingBox Transformed(const Matrix3x4&in) const", asMETHODPR(BoundingBox, Transformed, (const Matrix3x4&) const, BoundingBox), asCALL_THISCALL);
     engine->RegisterObjectMethod("BoundingBox", "Rect Projected(const Matrix4&in) const", asMETHODPR(BoundingBox, Projected, (const Matrix4&) const, Rect), asCALL_THISCALL);
     engine->RegisterObjectMethod("BoundingBox", "Rect Projected(const Matrix4&in) const", asMETHODPR(BoundingBox, Projected, (const Matrix4&) const, Rect), asCALL_THISCALL);
+    engine->RegisterObjectMethod("BoundingBox", "float DistanceToPoint(const Vector3&in) const", asMETHOD(BoundingBox, DistanceToPoint), asCALL_THISCALL);
     engine->RegisterObjectMethod("BoundingBox", "String ToString() const", asMETHOD(BoundingBox, ToString), asCALL_THISCALL);
     engine->RegisterObjectMethod("BoundingBox", "String ToString() const", asMETHOD(BoundingBox, ToString), asCALL_THISCALL);
     engine->RegisterObjectMethod("BoundingBox", "bool Defined() const", asMETHOD(BoundingBox, Defined), asCALL_THISCALL);
     engine->RegisterObjectMethod("BoundingBox", "bool Defined() const", asMETHOD(BoundingBox, Defined), asCALL_THISCALL);
     engine->RegisterObjectMethod("BoundingBox", "Vector3 get_center() const", asMETHOD(BoundingBox, Center), asCALL_THISCALL);
     engine->RegisterObjectMethod("BoundingBox", "Vector3 get_center() const", asMETHOD(BoundingBox, Center), asCALL_THISCALL);

+ 21 - 0
Source/Urho3D/AngelScript/NavigationAPI.cpp

@@ -72,6 +72,18 @@ static Vector3 NavigationMeshMoveAlongSurface(const Vector3& start, const Vector
     return ptr->MoveAlongSurface(start, end, extents, maxVisited);
     return ptr->MoveAlongSurface(start, end, extents, maxVisited);
 }
 }
 
 
+static VectorBuffer NavigationMeshGetTileData(const IntVector2& tile, const NavigationMesh* ptr)
+{
+    VectorBuffer buffer;
+    buffer.SetData(ptr->GetTileData(tile));
+    return buffer;
+}
+
+static bool NavigationMeshAddTile(const VectorBuffer& tileData, NavigationMesh* ptr)
+{
+    return ptr->AddTile(tileData.GetBuffer());
+}
+
 static Vector3 NavigationMeshGetRandomPoint(NavigationMesh* ptr)
 static Vector3 NavigationMeshGetRandomPoint(NavigationMesh* ptr)
 {
 {
     return ptr->GetRandomPoint();
     return ptr->GetRandomPoint();
@@ -104,8 +116,17 @@ static Vector3 CrowdManagerRandomPointInCircle(const Vector3& center, float radi
 
 
 template<class T> static void RegisterNavMeshBase(asIScriptEngine* engine, const char* name)
 template<class T> static void RegisterNavMeshBase(asIScriptEngine* engine, const char* name)
 {
 {
+    engine->RegisterObjectMethod(name, "bool Allocate(const BoundingBox&in, uint)", asMETHOD(T, Allocate), asCALL_THISCALL);
     engine->RegisterObjectMethod(name, "bool Build()", asMETHODPR(T, Build, (), bool), asCALL_THISCALL);
     engine->RegisterObjectMethod(name, "bool Build()", asMETHODPR(T, Build, (), bool), asCALL_THISCALL);
     engine->RegisterObjectMethod(name, "bool Build(const BoundingBox&in)", asMETHODPR(T, Build, (const BoundingBox&), bool), asCALL_THISCALL);
     engine->RegisterObjectMethod(name, "bool Build(const BoundingBox&in)", asMETHODPR(T, Build, (const BoundingBox&), bool), asCALL_THISCALL);
+    engine->RegisterObjectMethod(name, "bool Build(const IntVector2&, const IntVector2&)", asMETHODPR(T, Build, (const IntVector2&, const IntVector2&), bool), asCALL_THISCALL);
+    engine->RegisterObjectMethod(name, "VectorBuffer GetTileData(const IntVector2&) const", asFUNCTION(NavigationMeshGetTileData), asCALL_CDECL_OBJLAST);
+    engine->RegisterObjectMethod(name, "bool AddTile(const VectorBuffer&in) const", asFUNCTION(NavigationMeshAddTile), asCALL_CDECL_OBJLAST);
+    engine->RegisterObjectMethod(name, "void RemoveTile(const IntVector2&)", asMETHOD(T, RemoveTile), asCALL_THISCALL);
+    engine->RegisterObjectMethod(name, "void RemoveAllTiles()", asMETHOD(T, RemoveAllTiles), asCALL_THISCALL);
+    engine->RegisterObjectMethod(name, "bool HasTile(const IntVector2&) const", asMETHOD(T, HasTile), asCALL_THISCALL);
+    engine->RegisterObjectMethod(name, "BoundingBox GetTileBoudningBox(const IntVector2&) const", asMETHOD(T, GetTileBoudningBox), asCALL_THISCALL);
+    engine->RegisterObjectMethod(name, "IntVector2 GetTileIndex(const Vector3&) const", asMETHOD(T, GetTileIndex), asCALL_THISCALL);
     engine->RegisterObjectMethod(name, "void SetAreaCost(uint, float)", asMETHOD(T, SetAreaCost), asCALL_THISCALL);
     engine->RegisterObjectMethod(name, "void SetAreaCost(uint, float)", asMETHOD(T, SetAreaCost), asCALL_THISCALL);
     engine->RegisterObjectMethod(name, "float GetAreaCost(uint) const", asMETHOD(T, GetAreaCost), asCALL_THISCALL);
     engine->RegisterObjectMethod(name, "float GetAreaCost(uint) const", asMETHOD(T, GetAreaCost), asCALL_THISCALL);
     engine->RegisterObjectMethod(name, "Vector3 FindNearestPoint(const Vector3&in, const Vector3&in extents = Vector3(1.0, 1.0, 1.0))", asFUNCTION(NavigationMeshFindNearestPoint), asCALL_CDECL_OBJLAST);
     engine->RegisterObjectMethod(name, "Vector3 FindNearestPoint(const Vector3&in, const Vector3&in extents = Vector3(1.0, 1.0, 1.0))", asFUNCTION(NavigationMeshFindNearestPoint), asCALL_CDECL_OBJLAST);

+ 9 - 4
Source/Urho3D/Navigation/DynamicNavigationMesh.cpp

@@ -250,10 +250,6 @@ bool DynamicNavigationMesh::Allocate(const BoundingBox& boundingBox, unsigned ma
         URHO3D_LOGWARNING("Navigation mesh root node has scaling. Agent parameters may not work as intended");
         URHO3D_LOGWARNING("Navigation mesh root node has scaling. Agent parameters may not work as intended");
 
 
     boundingBox_ = boundingBox.Transformed(node_->GetWorldTransform().Inverse());
     boundingBox_ = boundingBox.Transformed(node_->GetWorldTransform().Inverse());
-    // Expand bounding box by padding
-    boundingBox_.min_ -= padding_;
-    boundingBox_.max_ += padding_;
-
     maxTiles = NextPowerOfTwo(maxTiles);
     maxTiles = NextPowerOfTwo(maxTiles);
 
 
     // Calculate number of tiles
     // Calculate number of tiles
@@ -330,6 +326,14 @@ bool DynamicNavigationMesh::Allocate(const BoundingBox& boundingBox, unsigned ma
             AddObstacle(obs);
             AddObstacle(obs);
     }
     }
 
 
+    // Send a notification event to concerned parties that we've been fully rebuilt
+    {
+        using namespace NavigationMeshRebuilt;
+        VariantMap& buildEventParams = GetContext()->GetEventDataMap();
+        buildEventParams[P_NODE] = node_;
+        buildEventParams[P_MESH] = this;
+        SendEvent(E_NAVIGATION_MESH_REBUILT, buildEventParams);
+    }
     return true;
     return true;
 }
 }
 
 
@@ -718,6 +722,7 @@ void DynamicNavigationMesh::SetNavigationDataAttr(const PODVector<unsigned char>
     }
     }
 
 
     ReadTiles(buffer, true);
     ReadTiles(buffer, true);
+    // \todo Shall we send E_NAVIGATION_MESH_REBUILT here?
 }
 }
 
 
 PODVector<unsigned char> DynamicNavigationMesh::GetNavigationDataAttr() const
 PODVector<unsigned char> DynamicNavigationMesh::GetNavigationDataAttr() const

+ 1 - 1
Source/Urho3D/Navigation/DynamicNavigationMesh.h

@@ -54,7 +54,7 @@ public:
     /// Register with engine context.
     /// Register with engine context.
     static void RegisterObject(Context*);
     static void RegisterObject(Context*);
 
 
-    /// Allocate the navigation mesh without building any tiles. Return true if successful.
+    /// Allocate the navigation mesh without building any tiles. Bounding box is not padded. Return true if successful.
     virtual bool Allocate(const BoundingBox& boundingBox, unsigned maxTiles);
     virtual bool Allocate(const BoundingBox& boundingBox, unsigned maxTiles);
     /// Build/rebuild the entire navigation mesh.
     /// Build/rebuild the entire navigation mesh.
     virtual bool Build();
     virtual bool Build();

+ 9 - 4
Source/Urho3D/Navigation/NavigationMesh.cpp

@@ -334,10 +334,6 @@ bool NavigationMesh::Allocate(const BoundingBox& boundingBox, unsigned maxTiles)
         URHO3D_LOGWARNING("Navigation mesh root node has scaling. Agent parameters may not work as intended");
         URHO3D_LOGWARNING("Navigation mesh root node has scaling. Agent parameters may not work as intended");
 
 
     boundingBox_ = boundingBox.Transformed(node_->GetWorldTransform().Inverse());
     boundingBox_ = boundingBox.Transformed(node_->GetWorldTransform().Inverse());
-    // Expand bounding box by padding
-    boundingBox_.min_ -= padding_;
-    boundingBox_.max_ += padding_;
-
     maxTiles = NextPowerOfTwo(maxTiles);
     maxTiles = NextPowerOfTwo(maxTiles);
 
 
     // Calculate number of tiles
     // Calculate number of tiles
@@ -374,6 +370,14 @@ bool NavigationMesh::Allocate(const BoundingBox& boundingBox, unsigned maxTiles)
 
 
     URHO3D_LOGDEBUG("Allocated empty navigation mesh with max " + String(maxTiles) + " tiles");
     URHO3D_LOGDEBUG("Allocated empty navigation mesh with max " + String(maxTiles) + " tiles");
 
 
+    // Send a notification event to concerned parties that we've been fully rebuilt
+    {
+        using namespace NavigationMeshRebuilt;
+        VariantMap& buildEventParams = GetContext()->GetEventDataMap();
+        buildEventParams[P_NODE] = node_;
+        buildEventParams[P_MESH] = this;
+        SendEvent(E_NAVIGATION_MESH_REBUILT, buildEventParams);
+    }
     return true;
     return true;
 }
 }
 
 
@@ -917,6 +921,7 @@ void NavigationMesh::SetNavigationDataAttr(const PODVector<unsigned char>& value
     }
     }
 
 
     URHO3D_LOGDEBUG("Created navigation mesh with " + String(numTiles) + " tiles from serialized data");
     URHO3D_LOGDEBUG("Created navigation mesh with " + String(numTiles) + " tiles from serialized data");
+    // \todo Shall we send E_NAVIGATION_MESH_REBUILT here?
 }
 }
 
 
 PODVector<unsigned char> NavigationMesh::GetNavigationDataAttr() const
 PODVector<unsigned char> NavigationMesh::GetNavigationDataAttr() const

+ 1 - 1
Source/Urho3D/Navigation/NavigationMesh.h

@@ -134,7 +134,7 @@ public:
     void SetPadding(const Vector3& padding);
     void SetPadding(const Vector3& padding);
     /// Set the cost of an area.
     /// Set the cost of an area.
     void SetAreaCost(unsigned areaID, float cost);
     void SetAreaCost(unsigned areaID, float cost);
-    /// Allocate the navigation mesh without building any tiles. Return true if successful.
+    /// Allocate the navigation mesh without building any tiles. Bounding box is not padded. Return true if successful.
     virtual bool Allocate(const BoundingBox& boundingBox, unsigned maxTiles);
     virtual bool Allocate(const BoundingBox& boundingBox, unsigned maxTiles);
     /// Rebuild the navigation mesh. Return true if successful.
     /// Rebuild the navigation mesh. Return true if successful.
     virtual bool Build();
     virtual bool Build();

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

@@ -13,6 +13,13 @@
 
 
 const String INSTRUCTION("instructionText");
 const String INSTRUCTION("instructionText");
 
 
+bool useStreaming = false;
+// Used for streaming only
+const int STREAMING_DISTANCE = 2;
+Array<VectorBuffer> navigationTilesData;
+Array<IntVector2> navigationTilesIdx;
+Array<IntVector2> addedTiles;
+
 void Start()
 void Start()
 {
 {
     // Execute the common startup for samples
     // Execute the common startup for samples
@@ -88,6 +95,8 @@ void CreateScene()
 
 
     // Create a DynamicNavigationMesh component to the scene root
     // Create a DynamicNavigationMesh component to the scene root
     DynamicNavigationMesh@ navMesh = scene_.CreateComponent("DynamicNavigationMesh");
     DynamicNavigationMesh@ navMesh = scene_.CreateComponent("DynamicNavigationMesh");
+    // Set small tiles to show navigation mesh streaming
+    navMesh.tileSize = 32;
     // Enable drawing debug geometry for obstacles and off-mesh connections
     // Enable drawing debug geometry for obstacles and off-mesh connections
     navMesh.drawObstacles = true;
     navMesh.drawObstacles = true;
     navMesh.drawOffMeshConnections = true;
     navMesh.drawOffMeshConnections = true;
@@ -105,6 +114,15 @@ void CreateScene()
     // physics geometry from the scene nodes, as it often is simpler, but if it can not find any (like in this example)
     // physics geometry from the scene nodes, as it often is simpler, but if it can not find any (like in this example)
     // it will use renderable geometry instead
     // it will use renderable geometry instead
     navMesh.Build();
     navMesh.Build();
+    // Save navigation data (used for streaming only).
+    IntVector2 numTiles = navMesh.numTiles;
+    for (int z = 0; z < numTiles.y; ++z)
+        for (int x = 0; x < numTiles.x; ++x)
+        {
+            IntVector2 idx(x, z);
+            navigationTilesData.Push(navMesh.GetTileData(idx));
+            navigationTilesIdx.Push(idx);
+        }
 
 
     // Create an off-mesh connection to each box to make it climbable (tiny boxes are skipped). A connection is built from 2 nodes.
     // Create an off-mesh connection to each box to make it climbable (tiny boxes are skipped). A connection is built from 2 nodes.
     // Note that OffMeshConnections must be added before building the navMesh, but as we are adding Obstacles next, tiles will be automatically rebuilt.
     // Note that OffMeshConnections must be added before building the navMesh, but as we are adding Obstacles next, tiles will be automatically rebuilt.
@@ -161,6 +179,7 @@ void CreateUI()
         "LMB to set destination, SHIFT+LMB to spawn a Jack\n"
         "LMB to set destination, SHIFT+LMB to spawn a Jack\n"
         "MMB or O key to add obstacles or remove obstacles/agents\n"
         "MMB or O key to add obstacles or remove obstacles/agents\n"
         "F5 to save scene, F7 to load\n"
         "F5 to save scene, F7 to load\n"
+        "Tab to toggle navigation mesh streaming\n"
         "Space to toggle debug geometry\n"
         "Space to toggle debug geometry\n"
         "F12 to toggle this instruction text";
         "F12 to toggle this instruction text";
     instructionText.SetFont(cache.GetResource("Font", "Fonts/Anonymous Pro.ttf"), 15);
     instructionText.SetFont(cache.GetResource("Font", "Fonts/Anonymous Pro.ttf"), 15);
@@ -414,6 +433,66 @@ void MoveCamera(float timeStep)
     }
     }
 }
 }
 
 
+void SwitchStreaming(bool enabled)
+{
+    DynamicNavigationMesh@ navMesh = scene_.GetComponent("DynamicNavigationMesh");
+    if (enabled)
+    {
+        int maxTiles = (2 * STREAMING_DISTANCE + 1) * (2 * STREAMING_DISTANCE + 1);
+        BoundingBox boundingBox = navMesh.boundingBox;
+        navMesh.Allocate(boundingBox, maxTiles);
+    }
+    else
+        navMesh.Build();
+}
+
+void StreamNavMesh()
+{
+    DynamicNavigationMesh@ navMesh = scene_.GetComponent("DynamicNavigationMesh");
+
+    // Center the navigation mesh at the crowd of jacks
+    Vector3 averageJackPosition;
+    Node@ jackGroup = scene_.GetChild("Jacks");
+    if (jackGroup !is null)
+    {
+        for (uint i = 0; i < jackGroup.numChildren; ++i)
+            averageJackPosition += jackGroup.children[i].worldPosition;
+        averageJackPosition /= jackGroup.numChildren;
+    }
+
+    // Compute currently loaded area
+    IntVector2 jackTile = navMesh.GetTileIndex(averageJackPosition);
+    IntVector2 beginTile = VectorMax(IntVector2(0, 0), jackTile - IntVector2(1, 1) * STREAMING_DISTANCE);
+    IntVector2 endTile = VectorMin(jackTile + IntVector2(1, 1) * STREAMING_DISTANCE, navMesh.numTiles - IntVector2(1, 1));
+
+    // Remove tiles
+    for (uint i = 0; i < addedTiles.length;)
+    {
+        IntVector2 tileIdx = addedTiles[i];
+        if (beginTile.x <= tileIdx.x && tileIdx.x <= endTile.x && beginTile.y <= tileIdx.y && tileIdx.y <= endTile.y)
+            ++i;
+        else
+        {
+            addedTiles.Erase(i);
+            navMesh.RemoveTile(tileIdx);
+        }
+    }
+
+    // Add tiles
+    for (int z = beginTile.y; z <= endTile.y; ++z)
+        for (int x = beginTile.x; x <= endTile.x; ++x)
+        {
+            const IntVector2 tileIdx(x, z);
+            int tileDataIdx = navigationTilesIdx.Find(tileIdx);
+            if (!navMesh.HasTile(tileIdx) && tileDataIdx != -1)
+            {
+                addedTiles.Push(tileIdx);
+                navMesh.AddTile(navigationTilesData[tileDataIdx]);
+                Print("Add tile " + tileIdx.ToString());
+            }
+        }
+}
+
 void HandleUpdate(StringHash eventType, VariantMap& eventData)
 void HandleUpdate(StringHash eventType, VariantMap& eventData)
 {
 {
     // Take the frame time step, which is stored as a float
     // Take the frame time step, which is stored as a float
@@ -421,6 +500,15 @@ void HandleUpdate(StringHash eventType, VariantMap& eventData)
 
 
     // Move the camera, scale movement with time step
     // Move the camera, scale movement with time step
     MoveCamera(timeStep);
     MoveCamera(timeStep);
+
+    // Update streaming
+    if (input.keyPress[KEY_TAB])
+    {
+        useStreaming = !useStreaming;
+        SwitchStreaming(useStreaming);
+    }
+    if (useStreaming)
+        StreamNavMesh();
 }
 }
 
 
 void HandlePostRenderUpdate(StringHash eventType, VariantMap& eventData)
 void HandlePostRenderUpdate(StringHash eventType, VariantMap& eventData)