From 67a1713e479e33a39a858cd026fc9fb81abff6d1 Mon Sep 17 00:00:00 2001 From: ikpil Date: Sat, 9 Sep 2023 23:57:07 +0900 Subject: [PATCH] crowd profiling model + view --- .../DtCrowdAgentConfig.cs} | 6 +- .../Tools/CrowdProfilingSampleTool.cs | 389 ++---------------- .../Tools/CrowdProfilingToolConfig.cs | 16 + .../Tools/RcCrowdProfilingTool.cs | 345 +++++++++++++++- .../Tools/RcCrowdTool.cs | 20 +- 5 files changed, 414 insertions(+), 362 deletions(-) rename src/{DotRecast.Recast.Toolset/Tools/CrowdConfig.cs => DotRecast.Detour.Crowd/DtCrowdAgentConfig.cs} (91%) create mode 100644 src/DotRecast.Recast.Toolset/Tools/CrowdProfilingToolConfig.cs diff --git a/src/DotRecast.Recast.Toolset/Tools/CrowdConfig.cs b/src/DotRecast.Detour.Crowd/DtCrowdAgentConfig.cs similarity index 91% rename from src/DotRecast.Recast.Toolset/Tools/CrowdConfig.cs rename to src/DotRecast.Detour.Crowd/DtCrowdAgentConfig.cs index 3b1c540..f25e522 100644 --- a/src/DotRecast.Recast.Toolset/Tools/CrowdConfig.cs +++ b/src/DotRecast.Detour.Crowd/DtCrowdAgentConfig.cs @@ -1,8 +1,6 @@ -using DotRecast.Detour.Crowd; - -namespace DotRecast.Recast.Toolset.Tools +namespace DotRecast.Detour.Crowd { - public class CrowdConfig + public class DtCrowdAgentConfig { public int expandOptions = 1; public bool anticipateTurns = true; diff --git a/src/DotRecast.Recast.Demo/Tools/CrowdProfilingSampleTool.cs b/src/DotRecast.Recast.Demo/Tools/CrowdProfilingSampleTool.cs index 34792a3..17aac84 100644 --- a/src/DotRecast.Recast.Demo/Tools/CrowdProfilingSampleTool.cs +++ b/src/DotRecast.Recast.Demo/Tools/CrowdProfilingSampleTool.cs @@ -40,25 +40,8 @@ public class CrowdProfilingSampleTool : ISampleTool private DemoSample _sample; private DtNavMesh m_nav; - private readonly CrowdConfig _option = new CrowdConfig(); - private RcCrowdProfilingTool _tool; + private readonly 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 _polyPoints = new(); - private long crowdUpdateTime; public CrowdProfilingSampleTool() { @@ -67,58 +50,64 @@ public class CrowdProfilingSampleTool : ISampleTool public void Layout() { - var prevOptimizeVis = _option.optimizeVis; - var prevOptimizeTopo = _option.optimizeTopo; - var prevAnticipateTurns = _option.anticipateTurns; - var prevObstacleAvoidance = _option.obstacleAvoidance; - var prevSeparation = _option.separation; - var prevObstacleAvoidanceType = _option.obstacleAvoidanceType; - var prevSeparationWeight = _option.separationWeight; + var cfg = _tool.GetCrowdConfig(); + var prevOptimizeVis = cfg.optimizeVis; + var prevOptimizeTopo = cfg.optimizeTopo; + var prevAnticipateTurns = cfg.anticipateTurns; + var prevObstacleAvoidance = cfg.obstacleAvoidance; + var prevSeparation = cfg.separation; + var prevObstacleAvoidanceType = cfg.obstacleAvoidanceType; + var prevSeparationWeight = cfg.separationWeight; ImGui.Text("Options"); ImGui.Separator(); - ImGui.Checkbox("Optimize Visibility", ref _option.optimizeVis); - ImGui.Checkbox("Optimize Topology", ref _option.optimizeTopo); - ImGui.Checkbox("Anticipate Turns", ref _option.anticipateTurns); - ImGui.Checkbox("Obstacle Avoidance", ref _option.obstacleAvoidance); - ImGui.SliderInt("Avoidance Quality", ref _option.obstacleAvoidanceType, 0, 3); - ImGui.Checkbox("Separation", ref _option.separation); - ImGui.SliderFloat("Separation Weight", ref _option.separationWeight, 0f, 20f, "%.2f"); + ImGui.Checkbox("Optimize Visibility", ref cfg.optimizeVis); + ImGui.Checkbox("Optimize Topology", ref cfg.optimizeTopo); + ImGui.Checkbox("Anticipate Turns", ref cfg.anticipateTurns); + ImGui.Checkbox("Obstacle Avoidance", ref cfg.obstacleAvoidance); + ImGui.SliderInt("Avoidance Quality", ref cfg.obstacleAvoidanceType, 0, 3); + ImGui.Checkbox("Separation", ref cfg.separation); + ImGui.SliderFloat("Separation Weight", ref cfg.separationWeight, 0f, 20f, "%.2f"); ImGui.NewLine(); - if (prevOptimizeVis != _option.optimizeVis || prevOptimizeTopo != _option.optimizeTopo - || prevAnticipateTurns != _option.anticipateTurns - || prevObstacleAvoidance != _option.obstacleAvoidance - || prevSeparation != _option.separation - || prevObstacleAvoidanceType != _option.obstacleAvoidanceType - || !prevSeparationWeight.Equals(_option.separationWeight)) + if (prevOptimizeVis != cfg.optimizeVis || prevOptimizeTopo != cfg.optimizeTopo + || prevAnticipateTurns != cfg.anticipateTurns + || prevObstacleAvoidance != cfg.obstacleAvoidance + || prevSeparation != cfg.separation + || prevObstacleAvoidanceType != cfg.obstacleAvoidanceType + || !prevSeparationWeight.Equals(cfg.separationWeight)) { - UpdateAgentParams(); + _tool.UpdateAgentParams(); } + var toolCfg = _tool.GetToolConfig(); + ImGui.Text("Simulation Options"); ImGui.Separator(); - ImGui.SliderInt("Agents", ref agents, 0, 10000); - ImGui.SliderInt("Random Seed", ref randomSeed, 0, 1024); - ImGui.SliderInt("Number of Zones", ref numberOfZones, 0, 10); - ImGui.SliderFloat("Zone Radius", ref zoneRadius, 0, 100, "%.0f"); - ImGui.SliderFloat("Mobs %", ref percentMobs, 0, 100, "%.0f"); - ImGui.SliderFloat("Travellers %", ref percentTravellers, 0, 100, "%.0f"); + ImGui.SliderInt("Agents", ref toolCfg.agents, 0, 10000); + ImGui.SliderInt("Random Seed", ref toolCfg.randomSeed, 0, 1024); + ImGui.SliderInt("Number of Zones", ref toolCfg.numberOfZones, 0, 10); + ImGui.SliderFloat("Zone Radius", ref toolCfg.zoneRadius, 0, 100, "%.0f"); + ImGui.SliderFloat("Mobs %", ref toolCfg.percentMobs, 0, 100, "%.0f"); + ImGui.SliderFloat("Travellers %", ref toolCfg.percentTravellers, 0, 100, "%.0f"); ImGui.NewLine(); ImGui.Text("Crowd Options"); ImGui.Separator(); - ImGui.SliderInt("Path Queue Size", ref pathQueueSize, 0, 1024); - ImGui.SliderInt("Max Iterations", ref maxIterations, 0, 4000); + ImGui.SliderInt("Path Queue Size", ref toolCfg.pathQueueSize, 0, 1024); + ImGui.SliderInt("Max Iterations", ref toolCfg.maxIterations, 0, 4000); ImGui.NewLine(); 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.Separator(); + + var crowd = _tool.GetCrowd(); if (crowd != null) { 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($"Update Time: {crowdUpdateTime} ms"); + ImGui.Text($"Update Time: {_tool.GetCrowdUpdateTime()} ms"); } } @@ -137,6 +126,8 @@ public class CrowdProfilingSampleTool : ISampleTool { RecastDebugDraw dd = renderer.GetDebugDraw(); dd.DepthMask(false); + + var crowd = _tool.GetCrowd(); if (crowd != null) { foreach (DtCrowdAgent ag in crowd.GetActiveAgents()) @@ -197,27 +188,10 @@ public class CrowdProfilingSampleTool : ISampleTool if (navMesh != null && 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() { return _tool; @@ -229,293 +203,14 @@ public class CrowdProfilingSampleTool : ISampleTool //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 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) { - Update(dt); + _tool.Update(dt); } public void HandleClickRay(RcVec3f start, RcVec3f direction, bool shift) { //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); - } - } - } } \ No newline at end of file diff --git a/src/DotRecast.Recast.Toolset/Tools/CrowdProfilingToolConfig.cs b/src/DotRecast.Recast.Toolset/Tools/CrowdProfilingToolConfig.cs new file mode 100644 index 0000000..ac9e6fb --- /dev/null +++ b/src/DotRecast.Recast.Toolset/Tools/CrowdProfilingToolConfig.cs @@ -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; + } +} \ No newline at end of file diff --git a/src/DotRecast.Recast.Toolset/Tools/RcCrowdProfilingTool.cs b/src/DotRecast.Recast.Toolset/Tools/RcCrowdProfilingTool.cs index 52ada9f..98c7d66 100644 --- a/src/DotRecast.Recast.Toolset/Tools/RcCrowdProfilingTool.cs +++ b/src/DotRecast.Recast.Toolset/Tools/RcCrowdProfilingTool.cs @@ -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 { + private CrowdProfilingToolConfig _cfg; + + private DtCrowdConfig _crowdCfg; + private DtCrowd crowd; + private readonly DtCrowdAgentConfig _agCfg; + + private DtNavMesh navMesh; + + private FRand rnd; + private readonly List _polyPoints; + private long crowdUpdateTime; + + public RcCrowdProfilingTool() + { + _cfg = new CrowdProfilingToolConfig(); + _agCfg = new DtCrowdAgentConfig(); + _polyPoints = new List(); + } + public string GetName() { 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 potentialTargets = new List(); + 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; + } } } \ No newline at end of file diff --git a/src/DotRecast.Recast.Toolset/Tools/RcCrowdTool.cs b/src/DotRecast.Recast.Toolset/Tools/RcCrowdTool.cs index 7053b48..f0af2a0 100644 --- a/src/DotRecast.Recast.Toolset/Tools/RcCrowdTool.cs +++ b/src/DotRecast.Recast.Toolset/Tools/RcCrowdTool.cs @@ -10,7 +10,7 @@ namespace DotRecast.Recast.Toolset.Tools { public class RcCrowdTool : IRcToolable { - private readonly CrowdConfig _cfg; + private readonly DtCrowdAgentConfig _agCfg; private DtCrowd crowd; private readonly DtCrowdAgentDebugInfo _agentDebug; private long crowdUpdateTime; @@ -20,7 +20,7 @@ namespace DotRecast.Recast.Toolset.Tools public RcCrowdTool() { - _cfg = new CrowdConfig(); + _agCfg = new DtCrowdAgentConfig(); _agentDebug = new DtCrowdAgentDebugInfo(); _agentDebug.vod = new DtObstacleAvoidanceDebugData(2048); _trails = new Dictionary(); @@ -32,9 +32,9 @@ namespace DotRecast.Recast.Toolset.Tools return "Crowd Control"; } - public CrowdConfig GetCrowdConfig() + public DtCrowdAgentConfig GetCrowdConfig() { - return _cfg; + return _agCfg; } public DtCrowdAgentDebugInfo GetCrowdAgentDebugInfo() @@ -119,9 +119,9 @@ namespace DotRecast.Recast.Toolset.Tools agOption.obstacleAvoidanceType = ag.option.obstacleAvoidanceType; agOption.queryFilterType = ag.option.queryFilterType; agOption.userData = ag.option.userData; - agOption.updateFlags = _cfg.GetUpdateFlags(); - agOption.obstacleAvoidanceType = _cfg.obstacleAvoidanceType; - agOption.separationWeight = _cfg.separationWeight; + agOption.updateFlags = _agCfg.GetUpdateFlags(); + agOption.obstacleAvoidanceType = _agCfg.obstacleAvoidanceType; + agOption.separationWeight = _agCfg.separationWeight; crowd.UpdateAgentParameters(ag, agOption); } } @@ -206,9 +206,9 @@ namespace DotRecast.Recast.Toolset.Tools ap.maxSpeed = agentMaxSpeed; ap.collisionQueryRange = ap.radius * 12.0f; ap.pathOptimizationRange = ap.radius * 30.0f; - ap.updateFlags = _cfg.GetUpdateFlags(); - ap.obstacleAvoidanceType = _cfg.obstacleAvoidanceType; - ap.separationWeight = _cfg.separationWeight; + ap.updateFlags = _agCfg.GetUpdateFlags(); + ap.obstacleAvoidanceType = _agCfg.obstacleAvoidanceType; + ap.separationWeight = _agCfg.separationWeight; return ap; }