Add streaming mode to C++ samples 15_Navigation and 39_CrowdNavigation . Fix using outdated navigation mesh data for streaming.

This commit is contained in:
Eugene Kozlov 2017-08-07 23:34:10 +03:00
parent 20d2133f13
commit f84e9d1392
6 changed files with 235 additions and 29 deletions

View File

@ -48,7 +48,9 @@ URHO3D_DEFINE_APPLICATION_MAIN(Navigation)
Navigation::Navigation(Context* context) :
Sample(context),
drawDebug_(false)
drawDebug_(false),
useStreaming_(false),
streamingDistance_(2)
{
}
@ -141,6 +143,8 @@ void Navigation::CreateScene()
// Create a NavigationMesh component to the scene root
NavigationMesh* navMesh = scene_->CreateComponent<NavigationMesh>();
// Set small tiles to show navigation mesh streaming
navMesh->SetTileSize(32);
// Create a Navigable component to the scene root. This tags all of the geometry in the scene as being part of the
// navigation mesh. By default this is recursive, but the recursion could be turned off from Navigable
scene_->CreateComponent<Navigable>();
@ -185,6 +189,7 @@ void Navigation::CreateUI()
"Use WASD keys to move, RMB to rotate view\n"
"LMB to set destination, SHIFT+LMB to teleport\n"
"MMB or O key to add or remove obstacles\n"
"Tab to toggle navigation mesh streaming\n"
"Space to toggle debug geometry"
);
instructionText->SetFont(cache->GetResource<Font>("Fonts/Anonymous Pro.ttf"), 15);
@ -299,7 +304,7 @@ void Navigation::AddOrRemoveObject()
Vector3 hitPos;
Drawable* hitDrawable;
if (Raycast(250.0f, hitPos, hitDrawable))
if (!useStreaming_ && Raycast(250.0f, hitPos, hitDrawable))
{
// The part of the navigation mesh we must update, which is the world bounding box of the associated
// drawable component
@ -390,6 +395,68 @@ void Navigation::FollowPath(float timeStep)
}
}
void Navigation::ToggleStreaming(bool enabled)
{
NavigationMesh* navMesh = scene_->GetComponent<NavigationMesh>();
if (enabled)
{
int maxTiles = (2 * streamingDistance_ + 1) * (2 * streamingDistance_ + 1);
BoundingBox boundingBox = navMesh->GetBoundingBox();
SaveNavigationData();
navMesh->Allocate(boundingBox, maxTiles);
}
else
navMesh->Build();
}
void Navigation::UpdateStreaming()
{
// Center the navigation mesh at the jack
NavigationMesh* navMesh = scene_->GetComponent<NavigationMesh>();
const IntVector2 jackTile = navMesh->GetTileIndex(jackNode_->GetWorldPosition());
const IntVector2 numTiles = navMesh->GetNumTiles();
const IntVector2 beginTile = VectorMax(IntVector2::ZERO, jackTile - IntVector2::ONE * streamingDistance_);
const IntVector2 endTile = VectorMin(jackTile + IntVector2::ONE * streamingDistance_, numTiles - IntVector2::ONE);
// Remove tiles
for (HashSet<IntVector2>::Iterator i = addedTiles_.Begin(); i != addedTiles_.End();)
{
const IntVector2 tileIdx = *i;
if (beginTile.x_ <= tileIdx.x_ && tileIdx.x_ <= endTile.x_ && beginTile.y_ <= tileIdx.y_ && tileIdx.y_ <= endTile.y_)
++i;
else
{
navMesh->RemoveTile(tileIdx);
i = addedTiles_.Erase(i);
}
}
// 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);
if (!navMesh->HasTile(tileIdx) && tileData_.Contains(tileIdx))
{
addedTiles_.Insert(tileIdx);
navMesh->AddTile(tileData_[tileIdx]);
}
}
}
void Navigation::SaveNavigationData()
{
NavigationMesh* navMesh = scene_->GetComponent<NavigationMesh>();
tileData_.Clear();
const IntVector2 numTiles = navMesh->GetNumTiles();
for (int z = 0; z < numTiles.y_; ++z)
for (int x = 0; x <= numTiles.x_; ++x)
{
const IntVector2 tileIdx = IntVector2(x, z);
tileData_[tileIdx] = navMesh->GetTileData(tileIdx);
}
}
void Navigation::HandleUpdate(StringHash eventType, VariantMap& eventData)
{
using namespace Update;
@ -402,6 +469,16 @@ void Navigation::HandleUpdate(StringHash eventType, VariantMap& eventData)
// Make Jack follow the Detour path
FollowPath(timeStep);
// Update streaming
Input* input = GetSubsystem<Input>();
if (input->GetKeyPress(KEY_TAB))
{
useStreaming_ = !useStreaming_;
ToggleStreaming(useStreaming_);
}
if (useStreaming_)
UpdateStreaming();
}
void Navigation::HandlePostRenderUpdate(StringHash eventType, VariantMap& eventData)

View File

@ -146,6 +146,12 @@ private:
bool Raycast(float maxDistance, Vector3& hitPos, Drawable*& hitDrawable);
/// Make Jack follow the Detour path.
void FollowPath(float timeStep);
/// Toggle navigation mesh streaming.
void ToggleStreaming(bool enabled);
/// Update navigation mesh streaming.
void UpdateStreaming();
/// Save navigation data for streaming.
void SaveNavigationData();
/// Handle the logic update event.
void HandleUpdate(StringHash eventType, VariantMap& eventData);
/// Handle the post-render update event.
@ -159,4 +165,12 @@ private:
SharedPtr<Node> jackNode_;
/// Flag for drawing debug geometry.
bool drawDebug_;
/// Flag for using navigation mesh streaming.
bool useStreaming_;
/// Streaming distance.
int streamingDistance_;
/// Tile data.
HashMap<IntVector2, PODVector<unsigned char> > tileData_;
/// Added tiles.
HashSet<IntVector2> addedTiles_;
};

View File

@ -55,6 +55,7 @@ URHO3D_DEFINE_APPLICATION_MAIN(CrowdNavigation)
CrowdNavigation::CrowdNavigation(Context* context) :
Sample(context),
streamingDistance_(2),
drawDebug_(false)
{
}
@ -135,6 +136,8 @@ void CrowdNavigation::CreateScene()
// Create a DynamicNavigationMesh component to the scene root
DynamicNavigationMesh* navMesh = scene_->CreateComponent<DynamicNavigationMesh>();
// Set small tiles to show navigation mesh streaming
navMesh->SetTileSize(32);
// Enable drawing debug geometry for obstacles and off-mesh connections
navMesh->SetDrawObstacles(true);
navMesh->SetDrawOffMeshConnections(true);
@ -213,6 +216,7 @@ void CrowdNavigation::CreateUI()
"LMB to set destination, SHIFT+LMB to spawn a Jack\n"
"MMB or O key to add obstacles or remove obstacles/agents\n"
"F5 to save scene, F7 to load\n"
"Tab to toggle navigation mesh streaming\n"
"Space to toggle debug geometry\n"
"F12 to toggle this instruction text"
);
@ -473,6 +477,78 @@ void CrowdNavigation::MoveCamera(float timeStep)
}
}
void CrowdNavigation::ToggleStreaming(bool enabled)
{
DynamicNavigationMesh* navMesh = scene_->GetComponent<DynamicNavigationMesh>();
if (enabled)
{
int maxTiles = (2 * streamingDistance_ + 1) * (2 * streamingDistance_ + 1);
BoundingBox boundingBox = navMesh->GetBoundingBox();
SaveNavigationData();
navMesh->Allocate(boundingBox, maxTiles);
}
else
navMesh->Build();
}
void CrowdNavigation::UpdateStreaming()
{
// Center the navigation mesh at the crowd of jacks
Vector3 averageJackPosition;
if (Node* jackGroup = scene_->GetChild("Jacks"))
{
const unsigned numJacks = jackGroup->GetNumChildren();
for (unsigned i = 0; i < numJacks; ++i)
averageJackPosition += jackGroup->GetChild(i)->GetWorldPosition();
averageJackPosition /= (float)numJacks;
}
// Compute currently loaded area
DynamicNavigationMesh* navMesh = scene_->GetComponent<DynamicNavigationMesh>();
const IntVector2 jackTile = navMesh->GetTileIndex(averageJackPosition);
const IntVector2 numTiles = navMesh->GetNumTiles();
const IntVector2 beginTile = VectorMax(IntVector2::ZERO, jackTile - IntVector2::ONE * streamingDistance_);
const IntVector2 endTile = VectorMin(jackTile + IntVector2::ONE * streamingDistance_, numTiles - IntVector2::ONE);
// Remove tiles
for (HashSet<IntVector2>::Iterator i = addedTiles_.Begin(); i != addedTiles_.End();)
{
const IntVector2 tileIdx = *i;
if (beginTile.x_ <= tileIdx.x_ && tileIdx.x_ <= endTile.x_ && beginTile.y_ <= tileIdx.y_ && tileIdx.y_ <= endTile.y_)
++i;
else
{
navMesh->RemoveTile(tileIdx);
i = addedTiles_.Erase(i);
}
}
// 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);
if (!navMesh->HasTile(tileIdx) && tileData_.Contains(tileIdx))
{
addedTiles_.Insert(tileIdx);
navMesh->AddTile(tileData_[tileIdx]);
}
}
}
void CrowdNavigation::SaveNavigationData()
{
DynamicNavigationMesh* navMesh = scene_->GetComponent<DynamicNavigationMesh>();
tileData_.Clear();
const IntVector2 numTiles = navMesh->GetNumTiles();
for (int z = 0; z < numTiles.y_; ++z)
for (int x = 0; x <= numTiles.x_; ++x)
{
const IntVector2 tileIdx = IntVector2(x, z);
tileData_[tileIdx] = navMesh->GetTileData(tileIdx);
}
}
void CrowdNavigation::HandleUpdate(StringHash eventType, VariantMap& eventData)
{
using namespace Update;
@ -482,6 +558,17 @@ void CrowdNavigation::HandleUpdate(StringHash eventType, VariantMap& eventData)
// Move the camera, scale movement with time step
MoveCamera(timeStep);
// Update streaming
Input* input = GetSubsystem<Input>();
if (input->GetKeyPress(KEY_TAB))
{
useStreaming_ = !useStreaming_;
ToggleStreaming(useStreaming_);
}
if (useStreaming_)
UpdateStreaming();
}
void CrowdNavigation::HandlePostRenderUpdate(StringHash eventType, VariantMap& eventData)

View File

@ -152,6 +152,12 @@ private:
void CreateMovingBarrels(DynamicNavigationMesh* navMesh);
/// Utility function to raycast to the cursor position. Return true if hit.
bool Raycast(float maxDistance, Vector3& hitPos, Drawable*& hitDrawable);
/// Toggle navigation mesh streaming.
void ToggleStreaming(bool enabled);
/// Update navigation mesh streaming.
void UpdateStreaming();
/// Save navigation data for streaming.
void SaveNavigationData();
/// Handle the logic update event.
void HandleUpdate(StringHash eventType, VariantMap& eventData);
/// Handle the post-render update event.
@ -163,6 +169,14 @@ private:
/// Handle crowd agent formation.
void HandleCrowdAgentFormation(StringHash eventType, VariantMap& eventData);
/// Flag for using navigation mesh streaming.
bool useStreaming_;
/// Streaming distance.
int streamingDistance_;
/// Tile data.
HashMap<IntVector2, PODVector<unsigned char> > tileData_;
/// Added tiles.
HashSet<IntVector2> addedTiles_;
/// Flag for drawing debug geometry.
bool drawDebug_;
};

View File

@ -119,15 +119,6 @@ void CreateScene()
// physics geometry from the scene nodes, as it often is simpler, but if it can not find any (like in this example)
// it will use renderable geometry instead
navMesh.Build();
// 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 the camera. Limit far clip distance to match the fog
cameraNode = scene_.CreateChild("Camera");
@ -273,7 +264,7 @@ void AddOrRemoveObject()
Vector3 hitPos;
Drawable@ hitDrawable;
if (Raycast(250.0f, hitPos, hitDrawable))
if (!useStreaming && Raycast(250.0f, hitPos, hitDrawable))
{
// The part of the navigation mesh we must update, which is the world bounding box of the associated
// drawable component
@ -359,20 +350,21 @@ void FollowPath(float timeStep)
}
}
void SwitchStreaming(bool enabled)
void ToggleStreaming(bool enabled)
{
NavigationMesh@ navMesh = scene_.GetComponent("NavigationMesh");
if (enabled)
{
int maxTiles = (2 * STREAMING_DISTANCE + 1) * (2 * STREAMING_DISTANCE + 1);
BoundingBox boundingBox = navMesh.boundingBox;
SaveNavigationData();
navMesh.Allocate(boundingBox, maxTiles);
}
else
navMesh.Build();
}
void StreamNavMesh()
void UpdateStreaming()
{
NavigationMesh@ navMesh = scene_.GetComponent("NavigationMesh");
@ -409,6 +401,21 @@ void StreamNavMesh()
}
}
void SaveNavigationData()
{
NavigationMesh@ navMesh = scene_.GetComponent("NavigationMesh");
navigationTilesData.Clear();
navigationTilesIdx.Clear();
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);
}
}
void HandleUpdate(StringHash eventType, VariantMap& eventData)
{
// Take the frame time step, which is stored as a float
@ -424,10 +431,10 @@ void HandleUpdate(StringHash eventType, VariantMap& eventData)
if (input.keyPress[KEY_TAB])
{
useStreaming = !useStreaming;
SwitchStreaming(useStreaming);
ToggleStreaming(useStreaming);
}
if (useStreaming)
StreamNavMesh();
UpdateStreaming();
}
void HandlePostRenderUpdate(StringHash eventType, VariantMap& eventData)

View File

@ -114,15 +114,6 @@ void CreateScene()
// physics geometry from the scene nodes, as it often is simpler, but if it can not find any (like in this example)
// it will use renderable geometry instead
navMesh.Build();
// 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.
// Note that OffMeshConnections must be added before building the navMesh, but as we are adding Obstacles next, tiles will be automatically rebuilt.
@ -433,20 +424,21 @@ void MoveCamera(float timeStep)
}
}
void SwitchStreaming(bool enabled)
void ToggleStreaming(bool enabled)
{
DynamicNavigationMesh@ navMesh = scene_.GetComponent("DynamicNavigationMesh");
if (enabled)
{
int maxTiles = (2 * STREAMING_DISTANCE + 1) * (2 * STREAMING_DISTANCE + 1);
BoundingBox boundingBox = navMesh.boundingBox;
SaveNavigationData();
navMesh.Allocate(boundingBox, maxTiles);
}
else
navMesh.Build();
}
void StreamNavMesh()
void UpdateStreaming()
{
DynamicNavigationMesh@ navMesh = scene_.GetComponent("DynamicNavigationMesh");
@ -493,6 +485,21 @@ void StreamNavMesh()
}
}
void SaveNavigationData()
{
DynamicNavigationMesh@ navMesh = scene_.GetComponent("DynamicNavigationMesh");
navigationTilesData.Clear();
navigationTilesIdx.Clear();
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);
}
}
void HandleUpdate(StringHash eventType, VariantMap& eventData)
{
// Take the frame time step, which is stored as a float
@ -505,10 +512,10 @@ void HandleUpdate(StringHash eventType, VariantMap& eventData)
if (input.keyPress[KEY_TAB])
{
useStreaming = !useStreaming;
SwitchStreaming(useStreaming);
ToggleStreaming(useStreaming);
}
if (useStreaming)
StreamNavMesh();
UpdateStreaming();
}
void HandlePostRenderUpdate(StringHash eventType, VariantMap& eventData)