crowd profiling model + view

This commit is contained in:
ikpil 2023-09-09 23:57:07 +09:00
parent 95af26e884
commit 67a1713e47
5 changed files with 414 additions and 362 deletions

View File

@ -1,8 +1,6 @@
using DotRecast.Detour.Crowd; namespace DotRecast.Detour.Crowd
namespace DotRecast.Recast.Toolset.Tools
{ {
public class CrowdConfig public class DtCrowdAgentConfig
{ {
public int expandOptions = 1; public int expandOptions = 1;
public bool anticipateTurns = true; public bool anticipateTurns = true;

View File

@ -40,25 +40,8 @@ public class CrowdProfilingSampleTool : ISampleTool
private DemoSample _sample; private DemoSample _sample;
private DtNavMesh m_nav; private DtNavMesh m_nav;
private readonly CrowdConfig _option = new CrowdConfig(); private readonly RcCrowdProfilingTool _tool;
private RcCrowdProfilingTool _tool;
private int expandSimOptions = 1;
private int expandCrowdOptions = 1;
private int agents = 1000;
private int randomSeed = 270;
private int numberOfZones = 4;
private float zoneRadius = 20f;
private float percentMobs = 80f;
private float percentTravellers = 15f;
private int pathQueueSize = 32;
private int maxIterations = 300;
private DtCrowd crowd;
private DtNavMesh navMesh;
private DtCrowdConfig config;
private FRand rnd;
private readonly List<DtPolyPoint> _polyPoints = new();
private long crowdUpdateTime;
public CrowdProfilingSampleTool() public CrowdProfilingSampleTool()
{ {
@ -67,58 +50,64 @@ public class CrowdProfilingSampleTool : ISampleTool
public void Layout() public void Layout()
{ {
var prevOptimizeVis = _option.optimizeVis; var cfg = _tool.GetCrowdConfig();
var prevOptimizeTopo = _option.optimizeTopo; var prevOptimizeVis = cfg.optimizeVis;
var prevAnticipateTurns = _option.anticipateTurns; var prevOptimizeTopo = cfg.optimizeTopo;
var prevObstacleAvoidance = _option.obstacleAvoidance; var prevAnticipateTurns = cfg.anticipateTurns;
var prevSeparation = _option.separation; var prevObstacleAvoidance = cfg.obstacleAvoidance;
var prevObstacleAvoidanceType = _option.obstacleAvoidanceType; var prevSeparation = cfg.separation;
var prevSeparationWeight = _option.separationWeight; var prevObstacleAvoidanceType = cfg.obstacleAvoidanceType;
var prevSeparationWeight = cfg.separationWeight;
ImGui.Text("Options"); ImGui.Text("Options");
ImGui.Separator(); ImGui.Separator();
ImGui.Checkbox("Optimize Visibility", ref _option.optimizeVis); ImGui.Checkbox("Optimize Visibility", ref cfg.optimizeVis);
ImGui.Checkbox("Optimize Topology", ref _option.optimizeTopo); ImGui.Checkbox("Optimize Topology", ref cfg.optimizeTopo);
ImGui.Checkbox("Anticipate Turns", ref _option.anticipateTurns); ImGui.Checkbox("Anticipate Turns", ref cfg.anticipateTurns);
ImGui.Checkbox("Obstacle Avoidance", ref _option.obstacleAvoidance); ImGui.Checkbox("Obstacle Avoidance", ref cfg.obstacleAvoidance);
ImGui.SliderInt("Avoidance Quality", ref _option.obstacleAvoidanceType, 0, 3); ImGui.SliderInt("Avoidance Quality", ref cfg.obstacleAvoidanceType, 0, 3);
ImGui.Checkbox("Separation", ref _option.separation); ImGui.Checkbox("Separation", ref cfg.separation);
ImGui.SliderFloat("Separation Weight", ref _option.separationWeight, 0f, 20f, "%.2f"); ImGui.SliderFloat("Separation Weight", ref cfg.separationWeight, 0f, 20f, "%.2f");
ImGui.NewLine(); ImGui.NewLine();
if (prevOptimizeVis != _option.optimizeVis || prevOptimizeTopo != _option.optimizeTopo if (prevOptimizeVis != cfg.optimizeVis || prevOptimizeTopo != cfg.optimizeTopo
|| prevAnticipateTurns != _option.anticipateTurns || prevAnticipateTurns != cfg.anticipateTurns
|| prevObstacleAvoidance != _option.obstacleAvoidance || prevObstacleAvoidance != cfg.obstacleAvoidance
|| prevSeparation != _option.separation || prevSeparation != cfg.separation
|| prevObstacleAvoidanceType != _option.obstacleAvoidanceType || prevObstacleAvoidanceType != cfg.obstacleAvoidanceType
|| !prevSeparationWeight.Equals(_option.separationWeight)) || !prevSeparationWeight.Equals(cfg.separationWeight))
{ {
UpdateAgentParams(); _tool.UpdateAgentParams();
} }
var toolCfg = _tool.GetToolConfig();
ImGui.Text("Simulation Options"); ImGui.Text("Simulation Options");
ImGui.Separator(); ImGui.Separator();
ImGui.SliderInt("Agents", ref agents, 0, 10000); ImGui.SliderInt("Agents", ref toolCfg.agents, 0, 10000);
ImGui.SliderInt("Random Seed", ref randomSeed, 0, 1024); ImGui.SliderInt("Random Seed", ref toolCfg.randomSeed, 0, 1024);
ImGui.SliderInt("Number of Zones", ref numberOfZones, 0, 10); ImGui.SliderInt("Number of Zones", ref toolCfg.numberOfZones, 0, 10);
ImGui.SliderFloat("Zone Radius", ref zoneRadius, 0, 100, "%.0f"); ImGui.SliderFloat("Zone Radius", ref toolCfg.zoneRadius, 0, 100, "%.0f");
ImGui.SliderFloat("Mobs %", ref percentMobs, 0, 100, "%.0f"); ImGui.SliderFloat("Mobs %", ref toolCfg.percentMobs, 0, 100, "%.0f");
ImGui.SliderFloat("Travellers %", ref percentTravellers, 0, 100, "%.0f"); ImGui.SliderFloat("Travellers %", ref toolCfg.percentTravellers, 0, 100, "%.0f");
ImGui.NewLine(); ImGui.NewLine();
ImGui.Text("Crowd Options"); ImGui.Text("Crowd Options");
ImGui.Separator(); ImGui.Separator();
ImGui.SliderInt("Path Queue Size", ref pathQueueSize, 0, 1024); ImGui.SliderInt("Path Queue Size", ref toolCfg.pathQueueSize, 0, 1024);
ImGui.SliderInt("Max Iterations", ref maxIterations, 0, 4000); ImGui.SliderInt("Max Iterations", ref toolCfg.maxIterations, 0, 4000);
ImGui.NewLine(); ImGui.NewLine();
if (ImGui.Button("Start Crowd Profiling")) if (ImGui.Button("Start Crowd Profiling"))
{ {
StartProfiling(); var settings = _sample.GetSettings();
_tool.StartProfiling(settings.agentRadius, settings.agentHeight, settings.agentMaxAcceleration, settings.agentMaxSpeed);
} }
ImGui.Text("Times"); ImGui.Text("Times");
ImGui.Separator(); ImGui.Separator();
var crowd = _tool.GetCrowd();
if (crowd != null) if (crowd != null)
{ {
ImGui.Text($"Max time to enqueue request: {crowd.Telemetry().MaxTimeToEnqueueRequest()} s"); ImGui.Text($"Max time to enqueue request: {crowd.Telemetry().MaxTimeToEnqueueRequest()} s");
@ -129,7 +118,7 @@ public class CrowdProfilingSampleTool : ISampleTool
ImGui.Text($"{rtt.Key}: {rtt.Micros} us"); ImGui.Text($"{rtt.Key}: {rtt.Micros} us");
} }
ImGui.Text($"Update Time: {crowdUpdateTime} ms"); ImGui.Text($"Update Time: {_tool.GetCrowdUpdateTime()} ms");
} }
} }
@ -137,6 +126,8 @@ public class CrowdProfilingSampleTool : ISampleTool
{ {
RecastDebugDraw dd = renderer.GetDebugDraw(); RecastDebugDraw dd = renderer.GetDebugDraw();
dd.DepthMask(false); dd.DepthMask(false);
var crowd = _tool.GetCrowd();
if (crowd != null) if (crowd != null)
{ {
foreach (DtCrowdAgent ag in crowd.GetActiveAgents()) foreach (DtCrowdAgent ag in crowd.GetActiveAgents())
@ -197,27 +188,10 @@ public class CrowdProfilingSampleTool : ISampleTool
if (navMesh != null && m_nav != navMesh) if (navMesh != null && m_nav != navMesh)
{ {
m_nav = navMesh; m_nav = navMesh;
Setup(settings.agentRadius, m_nav); _tool.Setup(settings.agentRadius, m_nav);
} }
} }
private DtCrowdAgentParams GetAgentParams()
{
var settings = _sample.GetSettings();
DtCrowdAgentParams ap = new DtCrowdAgentParams();
ap.radius = settings.agentRadius;
ap.height = settings.agentHeight;
ap.maxAcceleration = settings.agentMaxAcceleration;
ap.maxSpeed = settings.agentMaxSpeed;
ap.collisionQueryRange = ap.radius * 12.0f;
ap.pathOptimizationRange = ap.radius * 30.0f;
ap.updateFlags = _option.GetUpdateFlags();
ap.obstacleAvoidanceType = _option.obstacleAvoidanceType;
ap.separationWeight = _option.separationWeight;
return ap;
}
public IRcToolable GetTool() public IRcToolable GetTool()
{ {
return _tool; return _tool;
@ -229,293 +203,14 @@ public class CrowdProfilingSampleTool : ISampleTool
//throw new NotImplementedException(); //throw new NotImplementedException();
} }
private DtStatus GetMobPosition(DtNavMeshQuery navquery, IDtQueryFilter filter, out RcVec3f randomPt)
{
return navquery.FindRandomPoint(filter, rnd, out var randomRef, out randomPt);
}
private DtStatus GetVillagerPosition(DtNavMeshQuery navquery, IDtQueryFilter filter, out RcVec3f randomPt)
{
randomPt = RcVec3f.Zero;
if (0 >= _polyPoints.Count)
return DtStatus.DT_FAILURE;
int zone = (int)(rnd.Next() * _polyPoints.Count);
return navquery.FindRandomPointWithinCircle(_polyPoints[zone].refs, _polyPoints[zone].pt, zoneRadius, filter, rnd,
out var randomRef, out randomPt);
}
private void CreateZones()
{
_polyPoints.Clear();
IDtQueryFilter filter = new DtQueryDefaultFilter();
DtNavMeshQuery navquery = new DtNavMeshQuery(navMesh);
for (int i = 0; i < numberOfZones; i++)
{
float zoneSeparation = zoneRadius * zoneRadius * 16;
for (int k = 0; k < 100; k++)
{
var status = navquery.FindRandomPoint(filter, rnd, out var randomRef, out var randomPt);
if (status.Succeeded())
{
bool valid = true;
foreach (var zone in _polyPoints)
{
if (RcVec3f.DistSqr(zone.pt, randomPt) < zoneSeparation)
{
valid = false;
break;
}
}
if (valid)
{
_polyPoints.Add(new DtPolyPoint(randomRef, randomPt));
break;
}
}
}
}
}
private void CreateCrowd()
{
crowd = new DtCrowd(config, navMesh, __ => new DtQueryDefaultFilter(SampleAreaModifications.SAMPLE_POLYFLAGS_ALL,
SampleAreaModifications.SAMPLE_POLYFLAGS_DISABLED, new float[] { 1f, 10f, 1f, 1f, 2f, 1.5f }));
DtObstacleAvoidanceParams option = new DtObstacleAvoidanceParams(crowd.GetObstacleAvoidanceParams(0));
// Low (11)
option.velBias = 0.5f;
option.adaptiveDivs = 5;
option.adaptiveRings = 2;
option.adaptiveDepth = 1;
crowd.SetObstacleAvoidanceParams(0, option);
// Medium (22)
option.velBias = 0.5f;
option.adaptiveDivs = 5;
option.adaptiveRings = 2;
option.adaptiveDepth = 2;
crowd.SetObstacleAvoidanceParams(1, option);
// Good (45)
option.velBias = 0.5f;
option.adaptiveDivs = 7;
option.adaptiveRings = 2;
option.adaptiveDepth = 3;
crowd.SetObstacleAvoidanceParams(2, option);
// High (66)
option.velBias = 0.5f;
option.adaptiveDivs = 7;
option.adaptiveRings = 3;
option.adaptiveDepth = 3;
crowd.SetObstacleAvoidanceParams(3, option);
}
public void StartProfiling()
{
if (null == navMesh)
return;
rnd = new FRand(randomSeed);
CreateCrowd();
CreateZones();
DtNavMeshQuery navquery = new DtNavMeshQuery(navMesh);
IDtQueryFilter filter = new DtQueryDefaultFilter();
for (int i = 0; i < agents; i++)
{
float tr = rnd.Next();
CrowdAgentType type = CrowdAgentType.MOB;
float mobsPcnt = percentMobs / 100f;
if (tr > mobsPcnt)
{
tr = rnd.Next();
float travellerPcnt = percentTravellers / 100f;
if (tr > travellerPcnt)
{
type = CrowdAgentType.VILLAGER;
}
else
{
type = CrowdAgentType.TRAVELLER;
}
}
var status = DtStatus.DT_FAILURE;
var randomPt = RcVec3f.Zero;
switch (type)
{
case CrowdAgentType.MOB:
status = GetMobPosition(navquery, filter, out randomPt);
break;
case CrowdAgentType.VILLAGER:
status = GetVillagerPosition(navquery, filter, out randomPt);
break;
case CrowdAgentType.TRAVELLER:
status = GetVillagerPosition(navquery, filter, out randomPt);
break;
}
if (status.Succeeded())
{
AddAgent(randomPt, type);
}
}
}
public void Update(float dt)
{
long startTime = RcFrequency.Ticks;
if (crowd != null)
{
crowd.Config().pathQueueSize = pathQueueSize;
crowd.Config().maxFindPathIterations = maxIterations;
crowd.Update(dt, null);
}
long endTime = RcFrequency.Ticks;
if (crowd != null)
{
DtNavMeshQuery navquery = new DtNavMeshQuery(navMesh);
IDtQueryFilter filter = new DtQueryDefaultFilter();
foreach (DtCrowdAgent ag in crowd.GetActiveAgents())
{
if (NeedsNewTarget(ag))
{
CrowdAgentData crowAgentData = (CrowdAgentData)ag.option.userData;
switch (crowAgentData.type)
{
case CrowdAgentType.MOB:
MoveMob(navquery, filter, ag, crowAgentData);
break;
case CrowdAgentType.VILLAGER:
MoveVillager(navquery, filter, ag, crowAgentData);
break;
case CrowdAgentType.TRAVELLER:
MoveTraveller(navquery, filter, ag, crowAgentData);
break;
}
}
}
}
crowdUpdateTime = (endTime - startTime) / TimeSpan.TicksPerMillisecond;
}
private void MoveMob(DtNavMeshQuery navquery, IDtQueryFilter filter, DtCrowdAgent ag, CrowdAgentData crowAgentData)
{
// Move somewhere
var status = navquery.FindNearestPoly(ag.npos, crowd.GetQueryExtents(), filter, out var nearestRef, out var nearestPt, out var _);
if (status.Succeeded())
{
status = navquery.FindRandomPointAroundCircle(nearestRef, crowAgentData.home, zoneRadius * 2f, filter, rnd,
out var randomRef, out var randomPt);
if (status.Succeeded())
{
crowd.RequestMoveTarget(ag, randomRef, randomPt);
}
}
}
private void MoveVillager(DtNavMeshQuery navquery, IDtQueryFilter filter, DtCrowdAgent ag, CrowdAgentData crowAgentData)
{
// Move somewhere close
var status = navquery.FindNearestPoly(ag.npos, crowd.GetQueryExtents(), filter, out var nearestRef, out var nearestPt, out var _);
if (status.Succeeded())
{
status = navquery.FindRandomPointAroundCircle(nearestRef, crowAgentData.home, zoneRadius * 0.2f, filter, rnd,
out var randomRef, out var randomPt);
if (status.Succeeded())
{
crowd.RequestMoveTarget(ag, randomRef, randomPt);
}
}
}
private void MoveTraveller(DtNavMeshQuery navquery, IDtQueryFilter filter, DtCrowdAgent ag, CrowdAgentData crowAgentData)
{
// Move to another zone
List<DtPolyPoint> potentialTargets = new();
foreach (var zone in _polyPoints)
{
if (RcVec3f.DistSqr(zone.pt, ag.npos) > zoneRadius * zoneRadius)
{
potentialTargets.Add(zone);
}
}
if (0 < potentialTargets.Count)
{
potentialTargets.Shuffle();
crowd.RequestMoveTarget(ag, potentialTargets[0].refs, potentialTargets[0].pt);
}
}
private bool NeedsNewTarget(DtCrowdAgent ag)
{
if (ag.targetState == DtMoveRequestState.DT_CROWDAGENT_TARGET_NONE
|| ag.targetState == DtMoveRequestState.DT_CROWDAGENT_TARGET_FAILED)
{
return true;
}
if (ag.targetState == DtMoveRequestState.DT_CROWDAGENT_TARGET_VALID)
{
float dx = ag.targetPos.x - ag.npos.x;
float dy = ag.targetPos.y - ag.npos.y;
float dz = ag.targetPos.z - ag.npos.z;
return dx * dx + dy * dy + dz * dz < 0.3f;
}
return false;
}
public void Setup(float maxAgentRadius, DtNavMesh nav)
{
navMesh = nav;
if (nav != null)
{
config = new DtCrowdConfig(maxAgentRadius);
}
}
public void HandleUpdate(float dt) public void HandleUpdate(float dt)
{ {
Update(dt); _tool.Update(dt);
} }
public void HandleClickRay(RcVec3f start, RcVec3f direction, bool shift) public void HandleClickRay(RcVec3f start, RcVec3f direction, bool shift)
{ {
//throw new NotImplementedException(); //throw new NotImplementedException();
} }
private DtCrowdAgent AddAgent(RcVec3f p, CrowdAgentType type)
{
DtCrowdAgentParams ap = GetAgentParams();
ap.userData = new CrowdAgentData(type, p);
return crowd.AddAgent(p, ap);
}
private void UpdateAgentParams()
{
if (crowd != null)
{
foreach (DtCrowdAgent ag in crowd.GetActiveAgents())
{
DtCrowdAgentParams option = new DtCrowdAgentParams();
option.radius = ag.option.radius;
option.height = ag.option.height;
option.maxAcceleration = ag.option.maxAcceleration;
option.maxSpeed = ag.option.maxSpeed;
option.collisionQueryRange = ag.option.collisionQueryRange;
option.pathOptimizationRange = ag.option.pathOptimizationRange;
option.queryFilterType = ag.option.queryFilterType;
option.userData = ag.option.userData;
option.updateFlags = _option.GetUpdateFlags();
option.obstacleAvoidanceType = _option.obstacleAvoidanceType;
option.separationWeight = _option.separationWeight;
crowd.UpdateAgentParameters(ag, option);
}
}
}
} }

View File

@ -0,0 +1,16 @@
namespace DotRecast.Recast.Toolset.Tools
{
public class CrowdProfilingToolConfig
{
public int expandSimOptions = 1;
public int expandCrowdOptions = 1;
public int agents = 1000;
public int randomSeed = 270;
public int numberOfZones = 4;
public float zoneRadius = 20f;
public float percentMobs = 80f;
public float percentTravellers = 15f;
public int pathQueueSize = 32;
public int maxIterations = 300;
}
}

View File

@ -1,10 +1,353 @@
namespace DotRecast.Recast.Toolset.Tools using System;
using System.Collections.Generic;
using DotRecast.Core;
using DotRecast.Detour;
using DotRecast.Detour.Crowd;
using DotRecast.Recast.Toolset.Builder;
namespace DotRecast.Recast.Toolset.Tools
{ {
public class RcCrowdProfilingTool : IRcToolable public class RcCrowdProfilingTool : IRcToolable
{ {
private CrowdProfilingToolConfig _cfg;
private DtCrowdConfig _crowdCfg;
private DtCrowd crowd;
private readonly DtCrowdAgentConfig _agCfg;
private DtNavMesh navMesh;
private FRand rnd;
private readonly List<DtPolyPoint> _polyPoints;
private long crowdUpdateTime;
public RcCrowdProfilingTool()
{
_cfg = new CrowdProfilingToolConfig();
_agCfg = new DtCrowdAgentConfig();
_polyPoints = new List<DtPolyPoint>();
}
public string GetName() public string GetName()
{ {
return "Crowd Profiling"; return "Crowd Profiling";
} }
public CrowdProfilingToolConfig GetToolConfig()
{
return _cfg;
}
public DtCrowdAgentConfig GetCrowdConfig()
{
return _agCfg;
}
public DtCrowd GetCrowd()
{
return crowd;
}
public void Setup(float maxAgentRadius, DtNavMesh nav)
{
navMesh = nav;
if (nav != null)
{
_crowdCfg = new DtCrowdConfig(maxAgentRadius);
}
}
private DtCrowdAgentParams GetAgentParams(float agentRadius, float agentHeight, float agentMaxAcceleration, float agentMaxSpeed)
{
DtCrowdAgentParams ap = new DtCrowdAgentParams();
ap.radius = agentRadius;
ap.height = agentHeight;
ap.maxAcceleration = agentMaxAcceleration;
ap.maxSpeed = agentMaxSpeed;
ap.collisionQueryRange = ap.radius * 12.0f;
ap.pathOptimizationRange = ap.radius * 30.0f;
ap.updateFlags = _agCfg.GetUpdateFlags();
ap.obstacleAvoidanceType = _agCfg.obstacleAvoidanceType;
ap.separationWeight = _agCfg.separationWeight;
return ap;
}
private DtStatus GetMobPosition(DtNavMeshQuery navquery, IDtQueryFilter filter, out RcVec3f randomPt)
{
return navquery.FindRandomPoint(filter, rnd, out var randomRef, out randomPt);
}
private DtStatus GetVillagerPosition(DtNavMeshQuery navquery, IDtQueryFilter filter, out RcVec3f randomPt)
{
randomPt = RcVec3f.Zero;
if (0 >= _polyPoints.Count)
return DtStatus.DT_FAILURE;
int zone = (int)(rnd.Next() * _polyPoints.Count);
return navquery.FindRandomPointWithinCircle(_polyPoints[zone].refs, _polyPoints[zone].pt, _cfg.zoneRadius, filter, rnd,
out var randomRef, out randomPt);
}
private void CreateZones()
{
_polyPoints.Clear();
IDtQueryFilter filter = new DtQueryDefaultFilter();
DtNavMeshQuery navquery = new DtNavMeshQuery(navMesh);
for (int i = 0; i < _cfg.numberOfZones; i++)
{
float zoneSeparation = _cfg.zoneRadius * _cfg.zoneRadius * 16;
for (int k = 0; k < 100; k++)
{
var status = navquery.FindRandomPoint(filter, rnd, out var randomRef, out var randomPt);
if (status.Succeeded())
{
bool valid = true;
foreach (var zone in _polyPoints)
{
if (RcVec3f.DistSqr(zone.pt, randomPt) < zoneSeparation)
{
valid = false;
break;
}
}
if (valid)
{
_polyPoints.Add(new DtPolyPoint(randomRef, randomPt));
break;
}
}
}
}
}
private void CreateCrowd()
{
crowd = new DtCrowd(_crowdCfg, navMesh, __ => new DtQueryDefaultFilter(
SampleAreaModifications.SAMPLE_POLYFLAGS_ALL,
SampleAreaModifications.SAMPLE_POLYFLAGS_DISABLED,
new float[] { 1f, 10f, 1f, 1f, 2f, 1.5f })
);
DtObstacleAvoidanceParams option = new DtObstacleAvoidanceParams(crowd.GetObstacleAvoidanceParams(0));
// Low (11)
option.velBias = 0.5f;
option.adaptiveDivs = 5;
option.adaptiveRings = 2;
option.adaptiveDepth = 1;
crowd.SetObstacleAvoidanceParams(0, option);
// Medium (22)
option.velBias = 0.5f;
option.adaptiveDivs = 5;
option.adaptiveRings = 2;
option.adaptiveDepth = 2;
crowd.SetObstacleAvoidanceParams(1, option);
// Good (45)
option.velBias = 0.5f;
option.adaptiveDivs = 7;
option.adaptiveRings = 2;
option.adaptiveDepth = 3;
crowd.SetObstacleAvoidanceParams(2, option);
// High (66)
option.velBias = 0.5f;
option.adaptiveDivs = 7;
option.adaptiveRings = 3;
option.adaptiveDepth = 3;
crowd.SetObstacleAvoidanceParams(3, option);
}
public void StartProfiling(float agentRadius, float agentHeight, float agentMaxAcceleration, float agentMaxSpeed)
{
if (null == navMesh)
return;
rnd = new FRand(_cfg.randomSeed);
CreateCrowd();
CreateZones();
DtNavMeshQuery navquery = new DtNavMeshQuery(navMesh);
IDtQueryFilter filter = new DtQueryDefaultFilter();
for (int i = 0; i < _cfg.agents; i++)
{
float tr = rnd.Next();
CrowdAgentType type = CrowdAgentType.MOB;
float mobsPcnt = _cfg.percentMobs / 100f;
if (tr > mobsPcnt)
{
tr = rnd.Next();
float travellerPcnt = _cfg.percentTravellers / 100f;
if (tr > travellerPcnt)
{
type = CrowdAgentType.VILLAGER;
}
else
{
type = CrowdAgentType.TRAVELLER;
}
}
var status = DtStatus.DT_FAILURE;
var randomPt = RcVec3f.Zero;
switch (type)
{
case CrowdAgentType.MOB:
status = GetMobPosition(navquery, filter, out randomPt);
break;
case CrowdAgentType.VILLAGER:
status = GetVillagerPosition(navquery, filter, out randomPt);
break;
case CrowdAgentType.TRAVELLER:
status = GetVillagerPosition(navquery, filter, out randomPt);
break;
}
if (status.Succeeded())
{
AddAgent(randomPt, type, agentRadius, agentHeight, agentMaxAcceleration, agentMaxSpeed);
}
}
}
public void Update(float dt)
{
long startTime = RcFrequency.Ticks;
if (crowd != null)
{
crowd.Config().pathQueueSize = _cfg.pathQueueSize;
crowd.Config().maxFindPathIterations = _cfg.maxIterations;
crowd.Update(dt, null);
}
long endTime = RcFrequency.Ticks;
if (crowd != null)
{
DtNavMeshQuery navquery = new DtNavMeshQuery(navMesh);
IDtQueryFilter filter = new DtQueryDefaultFilter();
foreach (DtCrowdAgent ag in crowd.GetActiveAgents())
{
if (NeedsNewTarget(ag))
{
CrowdAgentData crowAgentData = (CrowdAgentData)ag.option.userData;
switch (crowAgentData.type)
{
case CrowdAgentType.MOB:
MoveMob(navquery, filter, ag, crowAgentData);
break;
case CrowdAgentType.VILLAGER:
MoveVillager(navquery, filter, ag, crowAgentData);
break;
case CrowdAgentType.TRAVELLER:
MoveTraveller(navquery, filter, ag, crowAgentData);
break;
}
}
}
}
crowdUpdateTime = (endTime - startTime) / TimeSpan.TicksPerMillisecond;
}
private void MoveMob(DtNavMeshQuery navquery, IDtQueryFilter filter, DtCrowdAgent ag, CrowdAgentData crowAgentData)
{
// Move somewhere
var status = navquery.FindNearestPoly(ag.npos, crowd.GetQueryExtents(), filter, out var nearestRef, out var nearestPt, out var _);
if (status.Succeeded())
{
status = navquery.FindRandomPointAroundCircle(nearestRef, crowAgentData.home, _cfg.zoneRadius * 2f, filter, rnd,
out var randomRef, out var randomPt);
if (status.Succeeded())
{
crowd.RequestMoveTarget(ag, randomRef, randomPt);
}
}
}
private void MoveVillager(DtNavMeshQuery navquery, IDtQueryFilter filter, DtCrowdAgent ag, CrowdAgentData crowAgentData)
{
// Move somewhere close
var status = navquery.FindNearestPoly(ag.npos, crowd.GetQueryExtents(), filter, out var nearestRef, out var nearestPt, out var _);
if (status.Succeeded())
{
status = navquery.FindRandomPointAroundCircle(nearestRef, crowAgentData.home, _cfg.zoneRadius * 0.2f, filter, rnd,
out var randomRef, out var randomPt);
if (status.Succeeded())
{
crowd.RequestMoveTarget(ag, randomRef, randomPt);
}
}
}
private void MoveTraveller(DtNavMeshQuery navquery, IDtQueryFilter filter, DtCrowdAgent ag, CrowdAgentData crowAgentData)
{
// Move to another zone
List<DtPolyPoint> potentialTargets = new List<DtPolyPoint>();
foreach (var zone in _polyPoints)
{
if (RcVec3f.DistSqr(zone.pt, ag.npos) > _cfg.zoneRadius * _cfg.zoneRadius)
{
potentialTargets.Add(zone);
}
}
if (0 < potentialTargets.Count)
{
potentialTargets.Shuffle();
crowd.RequestMoveTarget(ag, potentialTargets[0].refs, potentialTargets[0].pt);
}
}
private bool NeedsNewTarget(DtCrowdAgent ag)
{
if (ag.targetState == DtMoveRequestState.DT_CROWDAGENT_TARGET_NONE
|| ag.targetState == DtMoveRequestState.DT_CROWDAGENT_TARGET_FAILED)
{
return true;
}
if (ag.targetState == DtMoveRequestState.DT_CROWDAGENT_TARGET_VALID)
{
float dx = ag.targetPos.x - ag.npos.x;
float dy = ag.targetPos.y - ag.npos.y;
float dz = ag.targetPos.z - ag.npos.z;
return dx * dx + dy * dy + dz * dz < 0.3f;
}
return false;
}
private DtCrowdAgent AddAgent(RcVec3f p, CrowdAgentType type, float agentRadius, float agentHeight, float agentMaxAcceleration, float agentMaxSpeed)
{
DtCrowdAgentParams ap = GetAgentParams(agentRadius, agentHeight, agentMaxAcceleration, agentMaxSpeed);
ap.userData = new CrowdAgentData(type, p);
return crowd.AddAgent(p, ap);
}
public void UpdateAgentParams()
{
if (crowd != null)
{
foreach (DtCrowdAgent ag in crowd.GetActiveAgents())
{
DtCrowdAgentParams option = new DtCrowdAgentParams();
option.radius = ag.option.radius;
option.height = ag.option.height;
option.maxAcceleration = ag.option.maxAcceleration;
option.maxSpeed = ag.option.maxSpeed;
option.collisionQueryRange = ag.option.collisionQueryRange;
option.pathOptimizationRange = ag.option.pathOptimizationRange;
option.queryFilterType = ag.option.queryFilterType;
option.userData = ag.option.userData;
option.updateFlags = _agCfg.GetUpdateFlags();
option.obstacleAvoidanceType = _agCfg.obstacleAvoidanceType;
option.separationWeight = _agCfg.separationWeight;
crowd.UpdateAgentParameters(ag, option);
}
}
}
public long GetCrowdUpdateTime()
{
return crowdUpdateTime;
}
} }
} }

View File

@ -10,7 +10,7 @@ namespace DotRecast.Recast.Toolset.Tools
{ {
public class RcCrowdTool : IRcToolable public class RcCrowdTool : IRcToolable
{ {
private readonly CrowdConfig _cfg; private readonly DtCrowdAgentConfig _agCfg;
private DtCrowd crowd; private DtCrowd crowd;
private readonly DtCrowdAgentDebugInfo _agentDebug; private readonly DtCrowdAgentDebugInfo _agentDebug;
private long crowdUpdateTime; private long crowdUpdateTime;
@ -20,7 +20,7 @@ namespace DotRecast.Recast.Toolset.Tools
public RcCrowdTool() public RcCrowdTool()
{ {
_cfg = new CrowdConfig(); _agCfg = new DtCrowdAgentConfig();
_agentDebug = new DtCrowdAgentDebugInfo(); _agentDebug = new DtCrowdAgentDebugInfo();
_agentDebug.vod = new DtObstacleAvoidanceDebugData(2048); _agentDebug.vod = new DtObstacleAvoidanceDebugData(2048);
_trails = new Dictionary<long, CrowdAgentTrail>(); _trails = new Dictionary<long, CrowdAgentTrail>();
@ -32,9 +32,9 @@ namespace DotRecast.Recast.Toolset.Tools
return "Crowd Control"; return "Crowd Control";
} }
public CrowdConfig GetCrowdConfig() public DtCrowdAgentConfig GetCrowdConfig()
{ {
return _cfg; return _agCfg;
} }
public DtCrowdAgentDebugInfo GetCrowdAgentDebugInfo() public DtCrowdAgentDebugInfo GetCrowdAgentDebugInfo()
@ -119,9 +119,9 @@ namespace DotRecast.Recast.Toolset.Tools
agOption.obstacleAvoidanceType = ag.option.obstacleAvoidanceType; agOption.obstacleAvoidanceType = ag.option.obstacleAvoidanceType;
agOption.queryFilterType = ag.option.queryFilterType; agOption.queryFilterType = ag.option.queryFilterType;
agOption.userData = ag.option.userData; agOption.userData = ag.option.userData;
agOption.updateFlags = _cfg.GetUpdateFlags(); agOption.updateFlags = _agCfg.GetUpdateFlags();
agOption.obstacleAvoidanceType = _cfg.obstacleAvoidanceType; agOption.obstacleAvoidanceType = _agCfg.obstacleAvoidanceType;
agOption.separationWeight = _cfg.separationWeight; agOption.separationWeight = _agCfg.separationWeight;
crowd.UpdateAgentParameters(ag, agOption); crowd.UpdateAgentParameters(ag, agOption);
} }
} }
@ -206,9 +206,9 @@ namespace DotRecast.Recast.Toolset.Tools
ap.maxSpeed = agentMaxSpeed; ap.maxSpeed = agentMaxSpeed;
ap.collisionQueryRange = ap.radius * 12.0f; ap.collisionQueryRange = ap.radius * 12.0f;
ap.pathOptimizationRange = ap.radius * 30.0f; ap.pathOptimizationRange = ap.radius * 30.0f;
ap.updateFlags = _cfg.GetUpdateFlags(); ap.updateFlags = _agCfg.GetUpdateFlags();
ap.obstacleAvoidanceType = _cfg.obstacleAvoidanceType; ap.obstacleAvoidanceType = _agCfg.obstacleAvoidanceType;
ap.separationWeight = _cfg.separationWeight; ap.separationWeight = _agCfg.separationWeight;
return ap; return ap;
} }