Compare commits

...

163 Commits

Author SHA1 Message Date
ikpil b81a5923a3 benchmark arrays 2024-02-24 20:36:33 +09:00
ikpil 3fbfb968d0 test RcRentedArray 2024-02-24 20:00:51 +09:00
ikpil 3c4723c907 added npath in MergeCorridor functions 2024-02-23 01:02:23 +09:00
ikpil 7836b78bb4 added DtCrowdConst.MAX_PATH_RESULT = 256 2024-02-23 00:52:23 +09:00
ikpil 5b6fddcedc coverlet.collector 6.0.1 2024-02-22 01:25:19 +09:00
wreng 609508c94f Support split special case 2024-02-22 00:33:06 +09:00
wreng c47cc79552 CyclicBuffers SIMD 2024-02-22 00:33:06 +09:00
wreng fa837a84ed Replaced foreach with for to avoid enumerator allocations 2024-02-22 00:20:54 +09:00
Ikpil 18d2d8821e
README.md align 2024-02-21 13:48:57 +09:00
Ikpil 790adb52a5
README.md update 2024-02-21 06:52:08 +09:00
ikpil 56923f4704 for mobile 2024-02-21 01:28:32 +09:00
ikpil 4bed2f8c82 for mobile README 2024-02-21 01:26:21 +09:00
ikpil 4f271a7b21 update README 2024-02-21 01:21:57 +09:00
ikpil 6034bfa28a Added struct RcScopedTimer to avoid allocations 2024-02-20 02:02:05 +09:00
ikpil c908daa8c3 RcCyclicBuffer<T> optimizations @wrenge 2024-02-20 01:53:50 +09:00
wreng 097a365528 CyclicBuffer optimizations 2024-02-20 01:17:34 +09:00
ikpil 804cb275a7 Added struct DtCrowdScopedTimer to avoid allocations in scoped timer calls @wrenge
- https://github.com/ikpil/DotRecast/pull/54
2024-02-20 01:10:54 +09:00
wreng 3158dfc29c Avoid allocation in ScopedTimer call 2024-02-20 00:52:31 +09:00
ikpil 01b3bcf771 added RcCylicBuffer<long> extensions 2024-02-19 00:36:44 +09:00
ikpil ad504fe217 Changed from List<T> to RcCyclicBuffer in DtCrowdTelemetry execution timing sampling @wrenge
- https://github.com/ikpil/DotRecast/issues/51
2024-02-18 00:43:29 +09:00
wreng a359686171 Replaced list with cyclic buffer 2024-02-18 00:43:29 +09:00
ikpil 653a9e794b for nuget readme 2024-02-17 22:49:01 +09:00
ikpil d0bec3714e added corridor maxpath for SOH issue
- https://github.com/ikpil/DotRecast/issues/41
2024-02-16 00:26:23 +09:00
ikpil 41ab26c03f rename _ext to _agentPlacementHalfExtents in DtCrowd 2024-02-15 00:36:58 +09:00
ikpil 742b7f7db8 reposition DT_CROWDAGENT_MAX_NEIGHBOURS, DT_CROWDAGENT_MAX_CORNERS 2024-02-15 00:25:55 +09:00
ikpil bd4825dcd5 reposition DtCrwdAgent class comment 2024-02-14 01:48:33 +09:00
ikpil aae0884f5a reposition MIN_TARGET_DIST 2024-02-14 01:40:43 +09:00
ikpil a5b3344c1f replace DtPathCorridor comment 2024-02-14 01:34:16 +09:00
ikpil 389632def9 update changelog 2024-02-13 13:53:43 +09:00
ikpil f0eb976ba0 change index -> ptr 2024-02-13 13:53:43 +09:00
ikpil b26847d90f change GetNodeMap to AsEnumerable in DtNodePool 2024-02-13 13:53:43 +09:00
ikpil 3ce4f59002 rese DtNode in DtNodePool 2024-02-13 13:53:43 +09:00
ikpil 73bb475ef7 added menu bar in Demo 2024-02-11 15:39:34 +09:00
ikpil a65e5a3125 added DtNodeQueue unittest 2024-02-09 17:56:16 +09:00
ikpil 4320019b02 Update CHANGELOG.md in Unreleased section 2024-02-08 00:24:00 +09:00
ikpil 1db7fed92d fix: RcSortedQueue.Remove can not use binary-search 2024-02-08 00:22:32 +09:00
ikpil 45e4426df6 Changed RcSortedQueue.Remove() function to use binary search 2024-02-08 00:03:06 +09:00
ikpil a1b730da7d Update Microsoft.NET.Test.Sdk 17.8.0 to 17.9.0 2024-02-07 22:54:52 +09:00
ikpil 9ef3c4ba30 replace comment 2024-02-06 00:50:06 +09:00
ikpil af72cf3cc8 replace comment in DtNode 2024-02-05 13:56:39 +09:00
ikpil 0909818379 replace sampling time in CrowdAgentProfilingSampleTool 2024-02-04 21:20:36 +09:00
ikpil c4bed04c19 update CHANGELOG.md 2024-02-04 13:49:09 +09:00
ikpil 003ba1aa74 update CHANGELOG.,d 2024-02-04 13:36:35 +09:00
ikpil 2d3ca5b321 added WangHash for DtNodePool 2024-02-04 13:24:26 +09:00
ikpil 94ed3d646c typo 2024-02-04 12:42:34 +09:00
ikpil 2dc8593c78 Added avg, min, max, sampling updated times in CrowdAgentProfilingTool 2024-02-04 12:14:41 +09:00
ikpil 6b033ab058 add release note url in nuget package 2024-02-03 22:53:17 +09:00
ikpil 88059fcdcf added DtNodePool all tests 2024-02-03 01:23:57 +09:00
ikpil 8ae375ef4c upgrade NUnit.Analyzers 4.0.1 2024-02-03 01:19:14 +09:00
ikpil bf83597c1e added DtNodePool.GetNode, FindNode, FindNodes tests 2024-02-02 00:29:58 +09:00
ikpil cc9adf7f53 update changelog 2024-02-01 23:18:01 +09:00
ikpil daa43c5f9f update changelog 2024-02-01 23:03:08 +09:00
ikpil 713411105d add changelog 2024-02-01 23:02:58 +09:00
ikpil c61c2ba875 update package description 2024-02-01 22:47:12 +09:00
ikpil dbb03988f5 fix: DtRaycastHit SOH issue reslove 2024-02-01 13:59:22 +09:00
ikpil aaff85b229 fix: Change DtRaycastHit class to struct to resolve SOH issue. step2 2024-02-01 13:59:22 +09:00
ikpil 5bba84883e clean up comments in DtRaycastHit 2024-01-31 00:13:23 +09:00
ikpil 41df518a08 fix: SOH issue step1 (#41)
- https://github.com/ikpil/DotRecast/issues/41
2024-01-30 00:40:20 +09:00
ikpil 5073f657f9 upgrade NUnit.Analyzers 4.0.0 2024-01-30 00:29:04 +09:00
ikpil 6573a56099 update BuildingAndIntegrating.md 2024-01-24 13:51:41 +09:00
ikpil f3eab3e30f fix: workflow dotnet-version matrix 2024-01-21 20:22:20 +09:00
ikpil ec41fea35a fix: update dotnet workflow for target framework 2024-01-21 20:19:50 +09:00
ikpil 96e8b12772 fix: random size of an array for testing could be 0 2024-01-21 19:59:46 +09:00
ikpil 0d4344dabb remove Parallelizable in UnitTest 2024-01-21 19:27:58 +09:00
ikpil 4cb4f16096 fix 2024-01-21 19:10:54 +09:00
ikpil 675ca8ea4b added RcRentedArray (#41) 2024-01-21 19:06:01 +09:00
ikpil f1ecd37f4d again 2024-01-21 13:05:08 +09:00
ikpil 75eadc601d fix: changed stack memory test for unittest 2024-01-21 13:05:08 +09:00
ikpil 94ee6f9366 extended RcStackArray up to 512 elements (#41) 2024-01-21 13:05:08 +09:00
ikpil c53162f3e3 check aray interface 2024-01-21 13:05:08 +09:00
ikpil 07b50d9391 refactor: added RcStackArray4, RcStackArray8
- https://github.com/ikpil/DotRecast/issues/41
2024-01-21 13:05:08 +09:00
ikpil f2923a9d84 add badge - commit activity, open/closed issues 2024-01-20 01:25:12 +09:00
ikpil 17f13dd681 refactor: Remove unused List<int> overlaps in the MergeAndFilterLayerRegions function. 2024-01-20 00:57:09 +09:00
ikpil 66a3d73b8f refactor: add RcDirtyEntry 2024-01-19 00:08:01 +09:00
ikpil d7244bd0ff fix: invalid index 2024-01-18 23:39:32 +09:00
ikpil e0e31c19b9 refactor: int[3] -> RcLevelStackEntry 2024-01-18 23:39:32 +09:00
ikpil 78b29920cb add RcDirtyEntry, RcLevelStackEntry 2024-01-17 13:55:09 +09:00
ikpil b4d1adcd3e update readme link 2024-01-17 00:23:20 +09:00
ikpil c0db2fbc5f update build & intergrating 2024-01-17 00:23:20 +09:00
ikpil 27e4a0628a draft 2024-01-17 00:23:20 +09:00
ikpil 14f7cbf194 draft 2024-01-17 00:23:20 +09:00
ikpil f24577da76 create draft - BuildingAndIntergrating.md 2024-01-17 00:23:20 +09:00
ikpil 8e7c9394b3 typo 2024-01-16 01:09:28 +09:00
ikpil 34fccb99e3 update readme 2024-01-15 00:29:51 +09:00
ikpil 46b84d46c7 update readme - watch video 2024-01-15 00:28:16 +09:00
ikpil 347c67f5fe update readme 2024-01-15 00:17:05 +09:00
ikpil af5b70d7f4 small refactoring 2024-01-13 22:06:06 +09:00
ikpil f076d979ff update: readme - how to works 2024-01-12 02:54:17 +09:00
ikpil 8f8d6089d6 update youtube 2024-01-12 02:37:25 +09:00
ikpil 1119391c33 update youtube demo 2024-01-12 02:15:43 +09:00
ikpil 4c8c596450 bugfix: creating convex volume with two points(#34)
- Issue reporter @adambennett55
- https://github.com/ikpil/DotRecast/issues/34
2024-01-12 01:46:52 +09:00
ikpil 9de4b1ee40 update comments 2024-01-11 13:54:46 +09:00
ikpil a4ba159393 update readme 2024-01-11 01:09:39 +09:00
ikpil d617a41b54 update readme 2024-01-11 00:41:34 +09:00
ikpil 87ef05feb8 refactor: BuildCompactHeightfield 2024-01-11 00:30:46 +09:00
ikpil 278fb6cc1b refactor: 2 step loop -> 1 step loop 2024-01-10 13:47:42 +09:00
ikpil 85c3cfdcf6 refactor: update comments 2024-01-10 01:20:03 +09:00
ikpil ebca91e64d rename DtLayerSweepSpan to RcLayerSweepSpan 2024-01-09 14:09:01 +09:00
ikpil f51f2479ba update comments 2024-01-08 14:08:07 +09:00
ikpil 70fc2f6a8f update readme 2024-01-07 21:53:14 +09:00
ikpil 9dc7412dfd update youtube link 2024-01-07 21:48:03 +09:00
ikpil 2edabd3d44 rename RcTelemetry to RcContext 2024-01-07 13:00:38 +09:00
ikpil 648d7bd703 update comments 2024-01-07 12:53:28 +09:00
ikpil ae0f21c808 update readme 2024-01-05 13:53:47 +09:00
ikpil 507e3548ef refactor: aligned the parameter order of RasterizeTriangles() to match that of rcRasterizeTriangles() in the recastnavigation project. 2024-01-05 00:37:40 +09:00
ikpil 48a1e18101 refactor: made variable names identical to those in the DividePoly function of the recastnavigation project 2024-01-05 00:09:22 +09:00
ikpil cbdc670c6a refactor: align variable names to match those of the recastnavigation project. 2024-01-04 23:59:00 +09:00
ikpil 10bf4ce164 typo 2024-01-04 23:38:13 +09:00
ikpil fdb86134cf update readme 2024-01-04 00:19:27 +09:00
ikpil fffb10e5e3 update readme 2024-01-04 00:16:13 +09:00
ikpil be73850965 [Upstream] Cleanup filter code and improved documentation (https://github.com/recastnavigation/recastnavigation/pull/683)
- https://github.com/recastnavigation/recastnavigation/pull/683

This mostly just changes variable names and adds some comments to make the code more clear.

It also has a few small fixup changes to the unit tests.
2024-01-03 23:54:01 +09:00
ikpil 652b8d751a update badge 2024-01-01 23:42:37 +09:00
Ikpil 051cf6a036
Create FUNDING.yml 2024-01-01 19:10:54 +09:00
ikpil 9d01b5243f Code cleanup and small optimizations in RecastFilter.cpp rcFilterLedgeSpans (https://github.com/recastnavigation/recastnavigation/pull/672)
- 3e94c3b6fc

* Code cleanup and minor refactor in RecastFilter.cpp rcFilterLedgeSpans

Because span.smax is always > 0, bot > 0 as well, and (-walkableClimb - bot) is always < -walkableClimb. Furthermore, as long as minNeighborHeight < -walkableClimb' at least once, there is no need to continue the traversal.

* Code cleanup and minor refactor in RecastFilter.cpp rcFilterLedgeSpans

Because span.smax is always > 0, bot > 0 as well, and (-walkableClimb - bot) is always < -walkableClimb. Furthermore, as long as minNeighborHeight < -walkableClimb' at least once, there is no need to continue the traversal.

* Update RecastFilter.cpp

Revise Comment
2024-01-01 14:11:28 +09:00
ikpil 2d36b5f639 fixed null mesh data in tiles. 2023-12-31 17:12:45 +09:00
ikpil 69c1e795df small refactoring static readonly 2023-12-30 16:17:42 +09:00
ikpil b341a41fae typo public property getter -> readonly 2023-12-29 21:07:44 +09:00
ikpil 19088d31f2 feat - Added UI scaling feature based on monitor resolution in Demo 2023-12-26 20:39:30 +09:00
c0nd3v b1db6df033 Fix typo 2023-12-23 11:14:00 +09:00
c0nd3v 9c7f34c521 Fix updated struct version 2023-12-23 11:11:48 +09:00
c0nd3v e782208faa Allow Radius 0 in Demo
Recast C++ demo allows radius 0 and I have a use case for it. I think it should be allowed
2023-12-23 11:04:28 +09:00
ikpil 6474f234a3 [Upstream] Make detail mesh edge detection more robust (recastnavigation #657)
- https://github.com/recastnavigation/recastnavigation/pull/657

Instead of using a distance check which can fail at large magnitudes due
to low precision we can check whether the edges are actually on the
hull.
2023-12-22 22:21:57 +09:00
ikpil a3f426b186 update README.md 2023-12-14 13:34:30 +09:00
ikpil fa8369f209 upgrade Silk.NET 2.19.0 -> 2.20.0 2023-12-13 23:37:07 +09:00
ikpil 86e586c790 update README.md 2023-12-12 21:46:14 +09:00
ikpil 19f5b05d6a fix: 'RANDOM_POINTS_IN_CIRCLE' mode where random points accumulate in RecastDemo 2023-12-06 00:52:09 +09:00
ikpil b24d6492a8 typo 2023-12-06 00:47:05 +09:00
ikpil 29a7508520 upgrade: NUnit 4.0.0 -> 4.0.1 2023-12-03 13:31:16 +09:00
ikpil b6016fec0f typo readme 2023-12-02 13:08:44 +09:00
ikpil 330d356198 upgrade: Moq 4.20.69 -> 4.20.70 2023-11-29 14:07:31 +09:00
ikpil d7676913d7 upgrade: Serilog.Sinks.Console 5.0.0 -> 5.0.1 2023-11-29 14:07:01 +09:00
ikpil 9c6ddfffe0 [Upstream] 248275e - Fix: typo error (#153)
- 248275e07f
2023-11-29 00:03:59 +09:00
ikpil ea50e16a80 NUnit.Analyzers 3.9.0 -> 3.10.0 2023-11-28 23:55:59 +09:00
ikpil b887600f72 upgrade NUnit 4.0 2023-11-27 13:36:15 +09:00
ikpil 956517ad49 update github workflow 2023-11-21 23:53:18 +09:00
ikpil 4f569f6638 update visitor badge 2023-11-17 14:07:35 +09:00
ikpil 2f6c3335d8 support .net 8 in vscode 2023-11-16 23:25:55 +09:00
ikpil cbd4aeaab3 update codeql badge 2023-11-16 14:07:56 +09:00
Ikpil 7e472fae55
Pr/support net8 (#20)
* support net8
2023-11-16 14:05:19 +09:00
ikpil 43abc7d1e3 upgrade: silk.net 2.17.1 -> 2.19.0 2023-11-15 14:08:39 +09:00
ikpil cd509ad010 typo 2023-11-13 23:35:53 +09:00
ikpil 4c29fa0686 [Upstream] fix:rcBuildLayerRegions missing areaType (#470)
- ddaa361b08

It's already been modified, just align the code.
2023-11-13 23:35:43 +09:00
ikpil 8fcd8b5006 typo 2023-11-12 17:11:20 +09:00
ikpil e8eaeaa89c refactor: restore github tigger 2023-11-11 13:14:46 +09:00
ikpil 69c8c950d2 refactor: add type-safe array copy function 2023-11-11 13:08:52 +09:00
ikpil a50ba0b1b1 refactor: changed float[6] to RcVec3f[2] in DtOffMeshConnection class 2023-11-11 12:36:40 +09:00
ikpil 6f0fb2099a refactor: removed unnecessary trigger 2023-11-11 12:35:39 +09:00
ikpil 195c684fb2 upgrade: serilog 3.1.0 -> 3.1.1 2023-11-10 22:04:52 +09:00
ikpil 056408fed2 typo 2023-11-09 22:36:00 +09:00
ikpil b9ec4391f2 refactor: class NavMeshSetHeader -> struct NavMeshSetHeader 2023-11-09 22:19:43 +09:00
ikpil afabec2b7d upgrade: nuget 2023-11-09 22:11:35 +09:00
ikpil 54b04947de refactor: class NavMeshTileHeader -> struct NavMeshTileHeader 2023-11-09 22:09:27 +09:00
ikpil ac3f667bcc refactor: readonly struct DtPolyDetail 2023-11-09 21:57:38 +09:00
ikpil cd62e7f328 fix: workflow 2023-11-09 21:57:08 +09:00
ikpil 03f84b0c4b fix: tigger workflow 2023-11-07 22:49:22 +09:00
ikpil bb2041e253 refactor: added RcCompactSpanBuilder 2023-11-07 22:28:14 +09:00
ikpil 5aa482d35e refactor: readonly struct 2023-11-07 22:12:42 +09:00
ikpil 170b019517 upgrade: NUnit 3.13.3 -> 3.14.0 2023-11-07 13:42:42 +09:00
ikpil 5b2e9591c2 refactor: use ref readonly RcCompactCell c = ref ... 2023-11-02 00:11:54 +09:00
VaibhavWakde52 8ff17f43a2 Update README.md 2023-11-01 21:28:16 +09:00
ikpil 8f91328fd9 fix: fix bug where obstacle tile cache rebuild wasn't being reflected.
- https://github.com/ikpil/DotRecast/issues/14
2023-10-31 23:29:58 +09:00
ikpil 2245f70fcf refactor: revert this part due to a mix of C# and C++ style in array and interface usage 2023-10-31 23:06:42 +09:00
Gabriel Alexandre 99cbba7586 Migrated some small types to structs 2023-10-30 23:40:46 +09:00
197 changed files with 8180 additions and 2250 deletions

3
.github/FUNDING.yml vendored Normal file
View File

@ -0,0 +1,3 @@
# These are supported funding model platforms
github: ikpil

88
.github/workflows/codeql.yml vendored Normal file
View File

@ -0,0 +1,88 @@
# For most projects, this workflow file will not need changing; you simply need
# to commit it to your repository.
#
# You may wish to alter this file to override the set of languages analyzed,
# or to provide custom queries or build logic.
#
# ******** NOTE ********
# We have attempted to detect the languages in your repository. Please check
# the `language` matrix defined below to confirm you have the correct set of
# supported CodeQL languages.
#
name: "CodeQL"
on:
push:
branches: [ "main" ]
pull_request:
# The branches below must be a subset of the branches above
branches: [ "main" ]
schedule:
- cron: '17 1 * * 1'
jobs:
analyze:
name: Analyze
# Runner size impacts CodeQL analysis time. To learn more, please see:
# - https://gh.io/recommended-hardware-resources-for-running-codeql
# - https://gh.io/supported-runners-and-hardware-resources
# - https://gh.io/using-larger-runners
# Consider using larger runners for possible analysis time improvements.
runs-on: ${{ (matrix.language == 'swift' && 'macos-latest') || 'ubuntu-latest' }}
timeout-minutes: ${{ (matrix.language == 'swift' && 120) || 360 }}
permissions:
actions: read
contents: read
security-events: write
strategy:
fail-fast: false
matrix:
language: [ 'csharp' ]
# CodeQL supports [ 'c-cpp', 'csharp', 'go', 'java-kotlin', 'javascript-typescript', 'python', 'ruby', 'swift' ]
# Use only 'java-kotlin' to analyze code written in Java, Kotlin or both
# Use only 'javascript-typescript' to analyze code written in JavaScript, TypeScript or both
# Learn more about CodeQL language support at https://aka.ms/codeql-docs/language-support
steps:
- name: Check out
uses: actions/checkout@v4
- name: Set up .NET 8.0
uses: actions/setup-dotnet@v3
with:
dotnet-version: 8.x
# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
uses: github/codeql-action/init@v2
with:
languages: ${{ matrix.language }}
queries: security-extended
# If you wish to specify custom queries, you can do so here or in a config file.
# By default, queries listed here will override any specified in a config file.
# Prefix the list here with "+" to use these queries and those in the config file.
# For more details on CodeQL's query packs, refer to: https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs
# queries: security-extended,security-and-quality
# Autobuild attempts to build any compiled languages (C/C++, C#, Go, Java, or Swift).
# If this step fails, then you should remove it and run the build manually (see below)
- name: Autobuild
uses: github/codeql-action/autobuild@v2
# Command-line programs to run using the OS shell.
# 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun
# If the Autobuild fails above, remove it and uncomment the following three lines.
# modify them (or add more) to build your code if your project, please refer to the EXAMPLE below for guidance.
# - run: |
# echo "Run, Build Application using script"
# ./location_of_script_within_repo/buildscript.sh
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v2
with:
category: "/language:${{matrix.language}}"

View File

@ -5,12 +5,22 @@ name: .NET
on:
push:
pull_request:
branches: [ main ]
branches:
- 'main'
- 'pr/**'
paths:
- '**/*.cs'
- '**/*.csproj'
- '**/*.sln'
- '**.cs'
- '**.csproj'
- '**.sln'
- '**.yml'
pull_request:
branches:
- 'pr/**'
paths:
- '**.cs'
- '**.csproj'
- '**.sln'
- '**.yml'
jobs:
build-and-test:
@ -18,24 +28,24 @@ jobs:
runs-on: ${{ matrix.os }}
strategy:
matrix:
dotnet-version: [ '6.0.x', '7.0.x' ]
dotnet-version: [ '6', '7', '8' ]
os: [ windows-latest, ubuntu-latest, macos-latest ]
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
with:
fetch-depth: 0 # Get all history to allow automatic versioning using MinVer
- name: Setup .NET
uses: actions/setup-dotnet@v3
with:
dotnet-version: ${{ matrix.dotnet-version }}
dotnet-version: ${{ matrix.dotnet-version }}.x
- name: Restore dependencies
run: dotnet restore
- name: Build
run: dotnet build -c Release --no-restore
run: dotnet build -c Release --no-restore --framework net${{ matrix.dotnet-version }}.0
- name: Test
run: dotnet test -c Release --no-build --verbosity normal
run: dotnet test -c Release --no-build --verbosity normal --framework net${{ matrix.dotnet-version }}.0

View File

@ -28,14 +28,14 @@ jobs:
run: echo ok
- name: Checkout
uses: actions/checkout@v3
uses: actions/checkout@v4
with:
fetch-depth: 0 # Get all history to allow automatic versioning using MinVer
- name: Setup Dotnet
uses: actions/setup-dotnet@v3
with:
dotnet-version: '7.x'
dotnet-version: '8.x'
- name: restore dependencies
run: dotnet restore

View File

@ -10,14 +10,14 @@ jobs:
steps:
- name: Checkout
uses: actions/checkout@v3
uses: actions/checkout@v4
with:
fetch-depth: 0 # Get all history to allow automatic versioning using MinVer
- name: Setup Dotnet
uses: actions/setup-dotnet@v3
with:
dotnet-version: '7.x'
dotnet-version: '8.x'
- name: restore dependencies
run: dotnet restore
@ -26,7 +26,7 @@ jobs:
run: dotnet build -c Release --no-restore
- name: publish
run: dotnet publish src/DotRecast.Recast.Demo -c Release --framework net7.0 --no-restore --no-self-contained --output working-temp
run: dotnet publish src/DotRecast.Recast.Demo -c Release --framework net8.0 --no-restore --no-self-contained --output working-temp
- name: version
id: version

2
.vscode/launch.json vendored
View File

@ -9,7 +9,7 @@
"type": "coreclr",
"request": "launch",
"preLaunchTask": "build",
"program": "${workspaceFolder}/src/DotRecast.Recast.Demo/bin/Debug/net7.0/DotRecast.Recast.Demo.dll",
"program": "${workspaceFolder}/src/DotRecast.Recast.Demo/bin/Debug/net8.0/DotRecast.Recast.Demo.dll",
"args": [],
"cwd": "${workspaceFolder}/src/DotRecast.Recast.Demo",
"console": "internalConsole",

97
BuildingAndIntegrating.md Normal file
View File

@ -0,0 +1,97 @@

## 🔨 Build
- Building requires only .NET 8 SDK.
### 🔨 Building with Command Prompt
```shell
dotnet build -c Release
```
### 🔨 Building with an IDE
1. Open IDE: Launch your C# IDE (e.g., Visual Studio).
2. Open Solution: Go to the "File" menu and select "Open Solution."
3. Build: In the IDE menu, select "Build" > "Build Solution" or click the "Build" icon on the toolbar.
## ▶️ Run
- To verify the run for all modules, run [DotRecast.Recast.Demo](https://github.com/ikpil/DotRecast/tree/main/src/DotRecast.Recast.Demo/DotRecast.Recast.Demo.csproj)
- on windows requirement : install to [Microsoft Visual C++ Redistributable Package](https://learn.microsoft.com/en-us/cpp/windows/latest-supported-vc-redist)
### ▶️ Running With Command Prompt
```shell
dotnet run --project src/DotRecast.Recast.Demo --framework net8.0 -c Release
```
### ▶️ Running With IDE (ex. Visual Studio 2022 or Rider ...)
1. Open your C# IDE (like Visual Studio).
2. Go to "File" in the menu.
3. Choose "Open Project" or "Open Solution."
4. Find and select [DotRecast.sln](DotRecast.sln), then click "Open."
5. Run to [DotRecast.Recast.Demo](https://github.com/ikpil/DotRecast/tree/main/src/DotRecast.Recast.Demo/DotRecast.Recast.Demo.csproj)
## 🧪 Running Unit Test
- [DotRecast.Core.Test](https://github.com/ikpil/DotRecast/tree/main/test/DotRecast.Core.Test) : ...
- [DotRecast.Recast.Test](https://github.com/ikpil/DotRecast/tree/main/test/DotRecast.Recast.Test) : ...
- [DotRecast.Detour.Test](https://github.com/ikpil/DotRecast/tree/main/test/DotRecast.Detour.Test) : ...
- [DotRecast.Detour.TileCache.Test](https://github.com/ikpil/DotRecast/tree/main/test/DotRecast.Detour.TileCache.Test) : ...
- [DotRecast.Detour.Crowd.Test](https://github.com/ikpil/DotRecast/tree/main/test/DotRecast.Detour.Crowd.Test) : ...
- [DotRecast.Detour.Dynamic.Test](https://github.com/ikpil/DotRecast/tree/main/test/DotRecast.Detour.Dynamic.Test) : ...
- [DotRecast.Detour.Extras.Test](https://github.com/ikpil/DotRecast/tree/main/test/DotRecast.Detour.Extras.Test) : ...
### 🧪 Testing With Command Prompt
```shell
dotnet test --framework net8.0 -c Release
```
### 🧪 Testing With IDE
- Refer to the manual for your IDE.
## 🛠️ Integration
There are a few ways to integrate [DotRecast.Recast](https://github.com/ikpil/DotRecast/tree/main/src/DotRecast.Recast) and [DotRecast.Detour](https://github.com/ikpil/DotRecast/tree/main/src/DotRecast.Detour) into your project.
Source integration is the most popular and most flexible, and is what the project was designed for from the beginning.
### 🛠️ Source Integration
It is recommended to add the source directories
[DotRecast.Core](https://github.com/ikpil/DotRecast/tree/main/src/DotRecast.Core),
[DotRecast.Recast](https://github.com/ikpil/DotRecast/tree/main/src/DotRecast.Recast),
[DotRecast.Detour](https://github.com/ikpil/DotRecast/tree/main/src/DotRecast.Detour),
[DotRecast.Detour.Crowd](https://github.com/ikpil/DotRecast/tree/main/src/DotRecast.Detour.Crowd),
[DotRecast.Detour.TileCache](https://github.com/ikpil/DotRecast/tree/main/src/DotRecast.Detour.TileCache)
and directly into your project depending on which parts of the project you need.
For example your level building tool could include
[DotRecast.Core](https://github.com/ikpil/DotRecast/tree/main/src/DotRecast.Core),
[DotRecast.Recast](https://github.com/ikpil/DotRecast/tree/main/src/DotRecast.Recast),
[DotRecast.Detour](https://github.com/ikpil/DotRecast/tree/main/src/DotRecast.Detour)
and your game runtime could just include
[DotRecast.Detour](https://github.com/ikpil/DotRecast/tree/main/src/DotRecast.Detour)
- [DotRecast.Core](https://github.com/ikpil/DotRecast/tree/main/src/DotRecast.Core) : Core Utils
- [DotRecast.Recast](https://github.com/ikpil/DotRecast/tree/main/src/DotRecast.Recast) : Core navmesh building system.
- [DotRecast.Detour](https://github.com/ikpil/DotRecast/tree/main/src/DotRecast.Detour) : Runtime navmesh interface and query system.
- [DotRecast.Detour.TileCache](https://github.com/ikpil/DotRecast/tree/main/src/DotRecast.Detour.TileCache) : Runtime movement, obstacle avoidance, and crowd simulation systems.
- [DotRecast.Detour.Crowd](https://github.com/ikpil/DotRecast/tree/main/src/DotRecast.Detour.Crowd) : Runtime navmesh dynamic obstacle and re-baking system.
- [DotRecast.Detour.Dynamic](https://github.com/ikpil/DotRecast/tree/main/src/DotRecast.Detour.Dynamic) : robust support for dynamic nav meshes combining pre-built voxels with dynamic objects which can be freely added and removed
- [DotRecast.Detour.Extras](https://github.com/ikpil/DotRecast/tree/main/src/DotRecast.Detour.Extras) : simple tool to import navmeshes created with [A* Pathfinding Project](https://arongranberg.com/astar/)
### 🛠️ Installation through Nuget
- Nuget link : [DotRecast.Core](https://www.nuget.org/packages/DotRecast.Core)
- Nuget link : [DotRecast.Recast](https://www.nuget.org/packages/DotRecast.Recast)
- Nuget link : [DotRecast.Detour](https://www.nuget.org/packages/DotRecast.Detour)
- Nuget link : [DotRecast.Detour.TileCache](https://www.nuget.org/packages/DotRecast.Detour.TileCache)
- Nuget link : [DotRecast.Detour.Crowd](https://www.nuget.org/packages/DotRecast.Detour.Crowd)
- Nuget link : [DotRecast.Detour.Dynamic](https://www.nuget.org/packages/DotRecast.Detour.Dynamic)
- Nuget link : [DotRecast.Detour.Extras](https://www.nuget.org/packages/DotRecast.Detour.Extras)
- Nuget link : [DotRecast.Recast.Toolset](https://www.nuget.org/packages/DotRecast.Recast.Toolset)
- Nuget link : [DotRecast.Recast.Demo](https://www.nuget.org/packages/DotRecast.Recast.Demo)

82
CHANGELOG.md Normal file
View File

@ -0,0 +1,82 @@
# Changelog
All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
## [Unreleased] - yyyy-mm-dd
### Added
- Added RcCircularBuffer<T> [@ikpil](https://github.com/ikpil)
- Added struct DtCrowdScopedTimer to avoid allocations in scoped timer calls. [@wrenge](https://github.com/wrenge)
- Added struct RcScopedTimer to avoid allocations in RcContext scoped timer [@ikpil](https://github.com/ikpil)
### Fixed
### Changed
- Changed DtPathCorridor.Init(int maxPath) function to allow setting the maximum path [@ikpil](https://github.com/ikpil)
- Changed from List<T> to RcCyclicBuffer in DtCrowdTelemetry execution timing sampling [@wrenge](https://github.com/wrenge)
- RcCyclicBuffer<T> optimizations [@wrenge](https://github.com/wrenge)
### Removed
### Special Thanks
- [@Doprez](https://github.com/Doprez)
- [@Arctium](https://github.com/Arctium)
## [2024.1.3] - 2024-02-13
### Added
- Added DtNodeQueue UnitTest [@ikpil](https://github.com/ikpil)
- Added RcSortedQueue UnitTest [@ikpil](https://github.com/ikpil)
- Added IComparable interface to RcAtomicLong [@ikpil](https://github.com/ikpil)
- Added Menu bar in Demo [@ikpil](https://github.com/ikpil)
### Fixed
### Changed
- Update Microsoft.NET.Test.Sdk 17.8.0 to 17.9.0
- Enhanced ToString method of DtNode to provide more detailed information.
- Reuse DtNode in DtNodePool
### Removed
### Special Thanks
- [@Doprez](https://github.com/Doprez)
- [@Arctium](https://github.com/Arctium)
## [2024.1.2] - 2024-02-04
### Added
- Added DtNodePool tests [@ikpil](https://github.com/ikpil)
- Added WangHash() for DtNodePool [@ikpil](https://github.com/ikpil)
- Added avg, min, max, sampling updated times in CrowdAgentProfilingTool [@ikpil](https://github.com/ikpil)
### Fixed
- Fixed SOH issue in DtNavMeshQuery.Raycast [@ikpil](https://github.com/ikpil)
- Fixed SOH issue in DtProximityGrid.QueryItems [@ikpil](https://github.com/ikpil)
### Changed
- Upgrade NUnit.Analyzers 4.0.1
### Removed
### Special Thanks
- [@Doprez](https://github.com/Doprez)
- [@Arctium](https://github.com/Arctium)
## [2024.1.1] - 2024-01-05
### Fixed
- Fix typo ([#25](https://github.com/ikpil/DotRecast/pull/25)) [@c0nd3v](https://github.com/c0nd3v)
- Fix updated struct version ([#23](https://github.com/ikpil/DotRecast/pull/23)) [@c0nd3v](https://github.com/c0nd3v)
- Allow Radius 0 in Demo ([#22](https://github.com/ikpil/DotRecast/pull/22)) [@c0nd3v](https://github.com/c0nd3v)
### Changed
- [Upstream] Cleanup filter code and improved documentation ([#30](https://github.com/ikpil/DotRecast/pull/30)) [@ikpil](https://github.com/ikpil)
- [Upstream] Make detail mesh edge detection more robust ([#26](https://github.com/ikpil/DotRecast/pull/26)) [@ikpil](https://github.com/ikpil)
- [Upstream] 248275e - Fix: typo error (#153) ([#21](https://github.com/ikpil/DotRecast/pull/21)) [@ikpil](https://github.com/ikpil)
- Code cleanup and small optimizations in RecastFilter.cpp ([#29](https://github.com/ikpil/DotRecast/pull/29)) [@ikpil](https://github.com/ikpil)
- Added UI scaling feature based on monitor resolution in Demo ([#28](https://github.com/ikpil/DotRecast/pull/28)) [@ikpil](https://github.com/ikpil)

122
README.md
View File

@ -1,80 +1,94 @@
[![License: Zlib](https://img.shields.io/badge/License-Zlib-lightgrey.svg)](https://opensource.org/licenses/Zlib)
[![.NET](https://github.com/ikpil/DotRecast/actions/workflows/dotnet.yml/badge.svg)](https://github.com/ikpil/DotRecast/actions/workflows/dotnet.yml)
[![CodeQL](https://github.com/ikpil/DotRecast/actions/workflows/github-code-scanning/codeql/badge.svg)](https://github.com/ikpil/DotRecast/actions/workflows/github-code-scanning/codeql)
[![NuGet Version and Downloads count](https://buildstats.info/nuget/DotRecast.Detour)](https://www.nuget.org/packages/DotRecast.Detour)
![Repo Size](https://img.shields.io/github/repo-size/ikpil/DotRecast.svg?colorB=lightgray)
![Languages](https://img.shields.io/github/languages/top/ikpil/DotRecast)
<h1 align="center">DotRecast</h1>
<p align="center">
<i>DotRecast is C# Recast & Detour, a port of <a href="https://github.com/recastnavigation/recastnavigation">recastnavigation</a> and <a href="https://github.com/ppiastucki/recast4j">recast4j</a> to the C# language.</i>
</p>
<p align="center">
<i>If you'd like to support the project, we'd appreciate starring(⭐) our repos on Github for more visibility.</i>
</p>
# Screenshot
![screenshot](https://github.com/ikpil/DotRecast/assets/313821/8cf67832-1206-4b58-8c1f-7205210cbf22)
---
# Introduction
1. DotRecast is a port of C++'s [recastnavigation](https://github.com/recastnavigation/recastnavigation) and Java's [recast4j](https://github.com/ppiastucki/recast4j) to the C# language.
2. For game development, C# servers, C# project, and Unity3D are supported.
3. DotRecast consists of Recast and Detour, Crowd, Dynamic, Extras, TileCache, DemoTool, Demo
<p align="center">
<img alt="![GitHub License]" src="https://img.shields.io/github/license/ikpil/DotRecast?style=for-the-badge">
<img alt="Languages" src="https://img.shields.io/github/languages/top/ikpil/DotRecast?style=for-the-badge">
<img alt="GitHub repo size" src="https://img.shields.io/github/repo-size/ikpil/DotRecast?style=for-the-badge">
<a href="https://github.com/ikpil/DotRecast"><img alt="GitHub Repo stars" src="https://img.shields.io/github/stars/ikpil/DotRecast?style=for-the-badge&logo=github"></a>
<a href="https://github.com/ikpil/DotRecast/actions/workflows/dotnet.yml"><img alt="GitHub Actions Workflow Status" src="https://img.shields.io/github/actions/workflow/status/ikpil/DotRecast/dotnet.yml?style=for-the-badge&logo=github"></a>
<a href="https://github.com/ikpil/DotRecast/actions/workflows/codeql.yml"><img alt="GitHub Actions Workflow Status" src="https://img.shields.io/github/actions/workflow/status/ikpil/DotRecast/codeql.yml?style=for-the-badge&logo=github&label=CODEQL"></a>
<a href="https://github.com/ikpil/DotRecast/commits"><img alt="GitHub commit activity" src="https://img.shields.io/github/commit-activity/m/ikpil/DotRecast?style=for-the-badge&logo=github"></a>
<a href="https://github.com/ikpil/DotRecast/issues"><img alt="GitHub issues" src="https://img.shields.io/github/issues-raw/ikpil/DotRecast?style=for-the-badge&logo=github&color=44cc11"></a>
<a href="https://github.com/ikpil/DotRecast/issues"><img alt="GitHub closed issues" src="https://img.shields.io/github/issues-closed-raw/ikpil/DotRecast?style=for-the-badge&logo=github&color=a371f7"></a>
<a href="https://www.nuget.org/packages/DotRecast.Core"><img alt="NuGet Version" src="https://img.shields.io/nuget/vpre/DotRecast.Core?style=for-the-badge&logo=nuget"></a>
<a href="https://www.nuget.org/packages/DotRecast.Core"><img alt="NuGet Downloads" src="https://img.shields.io/nuget/dt/DotRecast.Core?style=for-the-badge&logo=nuget"></a>
<a href="https://visitorbadge.io/status?path=ikpil%2FDotRecast"><img alt="Visitors" src="https://api.visitorbadge.io/api/daily?path=ikpil%2FDotRecast&countColor=%23263759"></a>
<a href="https://github.com/sponsors/ikpil"><img alt="GitHub Sponsors" src="https://img.shields.io/github/sponsors/ikpil?style=for-the-badge&logo=GitHub-Sponsors&link=https%3A%2F%2Fgithub.com%2Fsponsors%2Fikpil"></a>
</p>
https://user-images.githubusercontent.com/313821/266782992-32a72a43-8f02-4214-8f1e-86b06952c8b7.mp4
---
## DotRecast.Recast
[![demo](https://user-images.githubusercontent.com/313821/266750582-8cf67832-1206-4b58-8c1f-7205210cbf22.gif)](https://youtu.be/zIFIgziKLhQ)
Recast is state of the art navigation mesh construction toolset for games.
Recast is...
* 🤖 **Automatic** - throw any level geometry at it and you will get a robust navmesh out
* 🏎️ **Fast** - swift turnaround times for level designers
* 🧘 **Flexible** - easily customize the navmesh generation and runtime navigation systems to suit your specific game's needs.
Recast constructs a navmesh through a multi-step rasterization process:
## 🚀 Features
1. First Recast voxelizes the input triangle mesh by rasterizing the triangles into a multi-layer heightfield.
2. Voxels in areas where the character would not be able to move are removed by applying simple voxel data filters.
3. The walkable areas described by the voxel grid are then divided into sets of 2D polygonal regions.
4. The navigation polygons are generated by triangulating and stiching together the generated 2d polygonal regions.
- 🤖 Automatic - Recast can generate a navmesh from any level geometry you throw at it
- 🏎️ Fast - swift turnaround times for level designers
- 🧘 Flexible - detailed customization options and modular design let you tailor functionality to your specific needs
- 🚫 Dependency-Free - building Recast & Detour only requires a .NET compiler
- 💪 Industry Standard - Recast powers AI navigation features in Unity, Unreal, Godot, O3DE and countless AAA and indie games and engines
## DotRecast.Detour
Recast Navigation is divided into multiple modules, each contained in its own folder:
Recast is accompanied by Detour, a path-finding and spatial reasoning toolkit. You can use any navigation mesh with Detour, but of course the data generated with Recast fits perfectly.
- [DotRecast.Core](https://github.com/ikpil/DotRecast/tree/main/src/DotRecast.Core) : Core utils
- [DotRecast.Recast](https://github.com/ikpil/DotRecast/tree/main/src/DotRecast.Recast) : Navmesh generation
- [DotRecast.Detour](https://github.com/ikpil/DotRecast/tree/main/src/DotRecast.Detour) : Runtime loading of navmesh data, pathfinding, navmesh queries
- [DotRecast.Detour.TileCache](https://github.com/ikpil/DotRecast/tree/main/src/DotRecast.Detour.TileCache) : Navmesh streaming. Useful for large levels and open-world games
- [DotRecast.Detour.Crowd](https://github.com/ikpil/DotRecast/tree/main/src/DotRecast.Detour.Crowd) : Agent movement, collision avoidance, and crowd simulation
- [DotRecast.Detour.Dynamic](https://github.com/ikpil/DotRecast/tree/main/src/DotRecast.Detour.Dynamic) : robust support for dynamic nav meshes combining pre-built voxels with dynamic objects which can be freely added and removed
- [DotRecast.Detour.Extras](https://github.com/ikpil/DotRecast/tree/main/src/DotRecast.Detour.Extras) : simple tool to import navmeshes created with [A* Pathfinding Project](https://arongranberg.com/astar/)
- [DotRecast.Recast.Toolset](https://github.com/ikpil/DotRecast/tree/main/src/DotRecast.Recast.Toolset) : all modules
- [DotRecast.Recast.Demo](https://github.com/ikpil/DotRecast/tree/main/src/DotRecast.Recast.Demo) : Standalone, comprehensive demo app showcasing all aspects of Recast & Detour's functionality
- [Tests](https://github.com/ikpil/DotRecast/tree/main/test) : Unit tests
Detour offers a simple static navmesh data representation which is suitable for many simple cases. It also provides a tiled navigation mesh representation, which allows you to stream of navigation data in and out as the player progresses through the world and regenerate sections of the navmesh data as the world changes.
## ⚡ Getting Started
## DotRecast.Recast.Demo
- To build or integrate into your own project, please check out [BuildingAndIntegrating.md](https://github.com/ikpil/DotRecast/tree/main/BuildingAndIntegrating.md)
- To create a NavMesh, please check out [RecastSoloMeshTest.cs](https://github.com/ikpil/DotRecast/tree/main/test/DotRecast.Recast.Test/RecastSoloMeshTest.cs)
- To test pathfinding, please check out [FindPathTest.cs](https://github.com/ikpil/DotRecast/tree/main/test/DotRecast.Detour.Test/FindPathTest.cs)
- To watch the demo play video, please check out [Demo Video](#-demo-video)
You can find a comprehensive demo project in the `DotRecast.Recast.Demo` folder. It's a kitchen sink demo showcasing all the functionality of the library. If you are new to Recast & Detour, check out [SoloNavMeshBuilder.cs](/src/DotRecast.Recast.Demo/Builder/SoloNavMeshBuilder.cs) to get started with building navmeshes and [TestNavmeshTool.cs](/src/DotRecast.Recast.Demo/Tools/TestNavmeshTool.cs) to see how Detour can be used to find paths.
## ⚙ How it Works
### Building DotRecast.Recast.Demo
Recast constructs a navmesh through a multi-step mesh rasterization process.
1. `DotRecast.Recast.Demo` uses [dotnet 7](https://dotnet.microsoft.com/) to build platform specific projects. Download it and make sure it's available on your path, or specify the path to it.
2. Open a command prompt, point it to a directory and clone DotRecast to it: `git clone https://github.com/ikpil/DotRecast.git`
3. Open `<DotRecastDir>\DotRecast.sln` with Visual Studio 2022 and build `DotRecast.Recast.Demo`
- Optionally, you can run using the `dotnet run` command with `DotRecast.Recast.Demo.csproj`
1. First Recast rasterizes the input triangle meshes into voxels.
2. Voxels in areas where agents would not be able to move are filtered and removed.
3. The walkable areas described by the voxel grid are then divided into sets of polygonal regions.
4. The navigation polygons are generated by re-triangulating the generated polygonal regions into a navmesh.
#### Windows
You can use Recast to build a single navmesh, or a tiled navmesh.
Single meshes are suitable for many simple, static cases and are easy to work with.
Tiled navmeshes are more complex to work with but better support larger, more dynamic environments. Tiled meshes enable advance Detour features like re-baking, heirarchical path-planning, and navmesh data-streaming.
- need to install [microsoft visual c++ redistributable package](https://learn.microsoft.com/en-us/cpp/windows/latest-supported-vc-redist)
## 📚 Documentation & Links
#### Linux & macOS & Windows
- DotRecast Links
- [DotRecast/issues](https://github.com/ikpil/DotRecast/issues)
- Navigate to the `DotRecast.Recast.Demo` folder and run `dotnet run`
- Official Links
- [recastnavigation/discussions](https://github.com/recastnavigation/recastnavigation/discussions)
- [recastnav.com](https://recastnav.com)
### Running Unit tests
## 🅾 License
#### With VS2022
DotRecast is licensed under ZLib license, see [LICENSE.txt](https://github.com/ikpil/DotRecast/tree/main/LICENSE.txt) for more information.
- In Visual Studio 2022 go to the test menu and press `Run All Tests`
## 📹 Demo Video
#### With CLI
[![demo](https://img.youtube.com/vi/zIFIgziKLhQ/0.jpg)](https://youtu.be/zIFIgziKLhQ)
- in the DotRecast folder open a command prompt and run `dotnet test`
[![demo](https://img.youtube.com/vi/CPvc19gNUEk/0.jpg)](https://youtu.be/CPvc19gNUEk)
## Integrating with your game or engine
[![demo](https://img.youtube.com/vi/pe5jpGUNPRg/0.jpg)](https://youtu.be/pe5jpGUNPRg)
It is recommended to add the source directories `DotRecast.Core`, `DotRecast.Detour.Crowd`, `DotRecast.Detour.Dynamic`, `DotRecast.Detour.TitleCache`, `DotRecast.Detour.Extras` and `DotRecast.Recast` into your own project depending on which parts of the project you need. For example your level building tool could include `DotRecast.Core`, `DotRecast.Recast`, and `DotRecast.Detour`, and your game runtime could just include `DotRecast.Detour`.
## Discuss
- Discuss Recast & Detour: http://groups.google.com/group/recastnavigation
- Development blog: http://digestingduck.blogspot.com/
## License
Recast & Detour is licensed under ZLib license, see `LICENSE.txt` for more information.

View File

@ -0,0 +1,274 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Net.Security;
namespace DotRecast.Core.Buffers
{
// https://github.com/joaoportela/CircularBuffer-CSharp/blob/master/CircularBuffer/CircularBuffer.cs
public class RcCyclicBuffer<T> : IEnumerable<T>
{
public struct Enumerator : IEnumerator<T>
{
private readonly RcCyclicBuffer<T> _cb;
private int _index;
private readonly int _size;
internal Enumerator(RcCyclicBuffer<T> cb)
{
_cb = cb;
_size = _cb._size;
_index = default;
Reset();
}
public bool MoveNext()
{
return ++_index < _size;
}
public void Reset()
{
_index = -1;
}
public T Current => _cb[_index];
object IEnumerator.Current => Current;
public void Dispose()
{
// This could be used to unlock write access to collection
}
}
private readonly T[] _buffer;
private int _start;
private int _end;
private int _size;
public RcCyclicBuffer(int capacity)
: this(capacity, new T[] { })
{
}
public RcCyclicBuffer(int capacity, T[] items)
{
if (capacity < 1)
{
throw new ArgumentException("RcCyclicBuffer cannot have negative or zero capacity.", nameof(capacity));
}
if (items == null)
{
throw new ArgumentNullException(nameof(items));
}
if (items.Length > capacity)
{
throw new ArgumentException("Too many items to fit RcCyclicBuffer", nameof(items));
}
_buffer = new T[capacity];
Array.Copy(items, _buffer, items.Length);
_size = items.Length;
_start = 0;
_end = _size == capacity ? 0 : _size;
}
public int Capacity => _buffer.Length;
public bool IsFull => Size == Capacity;
public bool IsEmpty => Size == 0;
public int Size => _size;
public T Front()
{
ThrowIfEmpty();
return _buffer[_start];
}
public T Back()
{
ThrowIfEmpty();
return _buffer[(_end != 0 ? _end : Capacity) - 1];
}
public T this[int index]
{
get
{
if (IsEmpty)
{
throw new IndexOutOfRangeException($"Cannot access index {index}. Buffer is empty");
}
if (index >= _size)
{
throw new IndexOutOfRangeException($"Cannot access index {index}. Buffer size is {_size}");
}
int actualIndex = InternalIndex(index);
return _buffer[actualIndex];
}
set
{
if (IsEmpty)
{
throw new IndexOutOfRangeException($"Cannot access index {index}. Buffer is empty");
}
if (index >= _size)
{
throw new IndexOutOfRangeException($"Cannot access index {index}. Buffer size is {_size}");
}
int actualIndex = InternalIndex(index);
_buffer[actualIndex] = value;
}
}
public void PushBack(T item)
{
if (IsFull)
{
_buffer[_end] = item;
Increment(ref _end);
_start = _end;
}
else
{
_buffer[_end] = item;
Increment(ref _end);
++_size;
}
}
public void PushFront(T item)
{
if (IsFull)
{
Decrement(ref _start);
_end = _start;
_buffer[_start] = item;
}
else
{
Decrement(ref _start);
_buffer[_start] = item;
++_size;
}
}
public void PopBack()
{
ThrowIfEmpty("Cannot take elements from an empty buffer.");
Decrement(ref _end);
_buffer[_end] = default(T);
--_size;
}
public void PopFront()
{
ThrowIfEmpty("Cannot take elements from an empty buffer.");
_buffer[_start] = default(T);
Increment(ref _start);
--_size;
}
public void Clear()
{
// to clear we just reset everything.
_start = 0;
_end = 0;
_size = 0;
Array.Clear(_buffer, 0, _buffer.Length);
}
public T[] ToArray()
{
T[] newArray = new T[Size];
CopyTo(newArray);
return newArray;
}
public void CopyTo(Span<T> destination)
{
var span1 = ArrayOne();
span1.CopyTo(destination);
ArrayTwo().CopyTo(destination[span1.Length..]);
}
private void ThrowIfEmpty(string message = "Cannot access an empty buffer.")
{
if (IsEmpty)
{
throw new InvalidOperationException(message);
}
}
private void Increment(ref int index)
{
if (++index == Capacity)
{
index = 0;
}
}
private void Decrement(ref int index)
{
if (index == 0)
{
index = Capacity;
}
index--;
}
private int InternalIndex(int index)
{
return _start + (index < (Capacity - _start)
? index
: index - Capacity);
}
internal Span<T> ArrayOne()
{
if (IsEmpty)
{
return new Span<T>(Array.Empty<T>());
}
if (_start < _end)
{
return new Span<T>(_buffer, _start, _end - _start);
}
return new Span<T>(_buffer, _start, _buffer.Length - _start);
}
internal Span<T> ArrayTwo()
{
if (IsEmpty)
{
return new Span<T>(Array.Empty<T>());
}
if (_start < _end)
{
return new Span<T>(_buffer, _end, 0);
}
return new Span<T>(_buffer, 0, _end);
}
public Enumerator GetEnumerator() => new Enumerator(this);
IEnumerator<T> IEnumerable<T>.GetEnumerator() => GetEnumerator();
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
}
}

View File

@ -0,0 +1,119 @@
using System;
using System.Numerics;
using System.Runtime.InteropServices;
namespace DotRecast.Core.Buffers
{
public static class RcCyclicBuffers
{
public static long Sum(this ReadOnlySpan<long> source)
{
var buffer = source;
var result = 0L;
if (Vector.IsHardwareAccelerated)
{
var vectors = MemoryMarshal.Cast<long, Vector<long>>(buffer);
var vecSum = Vector<long>.Zero;
foreach (var vec in vectors)
vecSum += vec;
result = Vector.Dot(vecSum, Vector<long>.One);
var remainder = source.Length % Vector<long>.Count;
buffer = buffer[^remainder..];
}
foreach (var val in buffer)
result += val;
return result;
}
public static double Average(this ReadOnlySpan<long> source)
{
if (0 >= source.Length)
return 0;
return source.Sum() / (double)source.Length;
}
private static long Min(this ReadOnlySpan<long> source)
{
var buffer = source;
var result = long.MaxValue;
if (Vector.IsHardwareAccelerated)
{
var vectors = MemoryMarshal.Cast<long, Vector<long>>(buffer);
var vecMin = Vector<long>.One * result;
foreach (var vec in vectors)
vecMin = Vector.Min(vecMin, vec);
for (int i = 0; i < Vector<long>.Count; i++)
result = Math.Min(result, vecMin[i]);
var remainder = source.Length % Vector<long>.Count;
buffer = buffer[^remainder..];
}
foreach (var val in buffer)
result = Math.Min(result, val);
return result;
}
private static long Max(this ReadOnlySpan<long> source)
{
var buffer = source;
var result = long.MinValue;
if (Vector.IsHardwareAccelerated)
{
var vectors = MemoryMarshal.Cast<long, Vector<long>>(buffer);
var vecMax = Vector<long>.One * result;
foreach (var vec in vectors)
vecMax = Vector.Max(vecMax, vec);
for (int i = 0; i < Vector<long>.Count; i++)
result = Math.Max(result, vecMax[i]);
var remainder = source.Length % Vector<long>.Count;
buffer = buffer[^remainder..];
}
foreach (var val in buffer)
result = Math.Max(result, val);
return result;
}
public static long Sum(this RcCyclicBuffer<long> source)
{
return Sum(source.ArrayOne()) + Sum(source.ArrayTwo());
}
public static double Average(this RcCyclicBuffer<long> source)
{
return Sum(source) / (double)source.Size;
}
public static long Min(this RcCyclicBuffer<long> source)
{
var firstHalf = source.ArrayOne();
var secondHalf = source.ArrayTwo();
var a = firstHalf.Length > 0 ? Min(firstHalf) : long.MaxValue;
var b = secondHalf.Length > 0 ? Min(secondHalf) : long.MaxValue;
return Math.Min(a, b);
}
public static long Max(this RcCyclicBuffer<long> source)
{
var firstHalf = source.ArrayOne();
var secondHalf = source.ArrayTwo();
var a = firstHalf.Length > 0 ? Max(firstHalf) : long.MinValue;
var b = secondHalf.Length > 0 ? Max(secondHalf) : long.MinValue;
return Math.Max(a, b);
}
}
}

View File

@ -0,0 +1,57 @@
using System;
using System.Buffers;
using System.Runtime.CompilerServices;
namespace DotRecast.Core.Buffers
{
public static class RcRentedArray
{
public static RcRentedArray<T> Rent<T>(int minimumLength)
{
var array = ArrayPool<T>.Shared.Rent(minimumLength);
return new RcRentedArray<T>(ArrayPool<T>.Shared, array, minimumLength);
}
}
public class RcRentedArray<T> : IDisposable
{
private ArrayPool<T> _owner;
private T[] _array;
public int Length { get; }
public bool IsDisposed => null == _owner || null == _array;
internal RcRentedArray(ArrayPool<T> owner, T[] array, int length)
{
_owner = owner;
_array = array;
Length = length;
}
public ref T this[int index]
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get
{
RcThrowHelper.ThrowExceptionIfIndexOutOfRange(index, Length);
return ref _array[index];
}
}
public T[] AsArray()
{
return _array;
}
public void Dispose()
{
if (null != _owner && null != _array)
{
_owner.Return(_array, true);
_owner = null;
_array = null;
}
}
}
}

View File

@ -38,7 +38,7 @@ namespace DotRecast.Core.Collections
public void CopyTo(T[] array, int arrayIndex)
{
var self = this;
Array.Copy(self._array!, 0, array, arrayIndex, self.Length);
RcArrays.Copy(self._array!, 0, array, arrayIndex, self.Length);
}
public void Add(T item)

View File

@ -41,7 +41,7 @@ namespace DotRecast.Core.Collections
}
var tmp = new T[items.Length];
Array.Copy(items, tmp, items.Length);
RcArrays.Copy(items, tmp, items.Length);
return new RcImmutableArray<T>(tmp);
}
}

View File

@ -27,12 +27,12 @@ namespace DotRecast.Core.Collections
{
private bool _dirty;
private readonly List<T> _items;
private readonly Comparison<T> _comparison;
private readonly Comparer<T> _comparer;
public RcSortedQueue(Comparison<T> comparison)
public RcSortedQueue(Comparison<T> comp)
{
_items = new List<T>();
_comparison = (x, y) => comparison.Invoke(x, y) * -1; // reverse
_comparer = Comparer<T>.Create((x, y) => comp.Invoke(x, y) * -1);
}
public int Count()
@ -40,16 +40,22 @@ namespace DotRecast.Core.Collections
return _items.Count;
}
public bool IsEmpty()
{
return 0 == _items.Count;
}
public void Clear()
{
_items.Clear();
_dirty = false;
}
private void Balance()
{
if (_dirty)
{
_items.Sort(_comparison); // reverse
_items.Sort(_comparer); // reverse
_dirty = false;
}
}
@ -57,35 +63,39 @@ namespace DotRecast.Core.Collections
public T Peek()
{
Balance();
return _items[_items.Count - 1];
return _items[^1];
}
public T Dequeue()
{
var node = Peek();
_items.Remove(node);
_items.RemoveAt(_items.Count - 1);
return node;
}
public void Enqueue(T item)
{
if (null == item)
return;
_items.Add(item);
_dirty = true;
}
public void Remove(T item)
public bool Remove(T item)
{
if (null == item)
return false;
//int idx = _items.BinarySearch(item, _comparer); // don't use this! Because reference types can be reused externally.
int idx = _items.FindLastIndex(x => item.Equals(x));
if (0 > idx)
return;
return false;
_items.RemoveAt(idx);
return true;
}
public bool IsEmpty()
{
return 0 == _items.Count;
}
public List<T> ToList()
{

View File

@ -0,0 +1,421 @@
using System;
using System.Runtime.CompilerServices;
namespace DotRecast.Core.Collections
{
public struct RcStackArray128<T>
{
public static RcStackArray128<T> Empty => new RcStackArray128<T>();
private const int Size = 128;
public int Length => Size;
public T V0;
public T V1;
public T V2;
public T V3;
public T V4;
public T V5;
public T V6;
public T V7;
public T V8;
public T V9;
public T V10;
public T V11;
public T V12;
public T V13;
public T V14;
public T V15;
public T V16;
public T V17;
public T V18;
public T V19;
public T V20;
public T V21;
public T V22;
public T V23;
public T V24;
public T V25;
public T V26;
public T V27;
public T V28;
public T V29;
public T V30;
public T V31;
public T V32;
public T V33;
public T V34;
public T V35;
public T V36;
public T V37;
public T V38;
public T V39;
public T V40;
public T V41;
public T V42;
public T V43;
public T V44;
public T V45;
public T V46;
public T V47;
public T V48;
public T V49;
public T V50;
public T V51;
public T V52;
public T V53;
public T V54;
public T V55;
public T V56;
public T V57;
public T V58;
public T V59;
public T V60;
public T V61;
public T V62;
public T V63;
public T V64;
public T V65;
public T V66;
public T V67;
public T V68;
public T V69;
public T V70;
public T V71;
public T V72;
public T V73;
public T V74;
public T V75;
public T V76;
public T V77;
public T V78;
public T V79;
public T V80;
public T V81;
public T V82;
public T V83;
public T V84;
public T V85;
public T V86;
public T V87;
public T V88;
public T V89;
public T V90;
public T V91;
public T V92;
public T V93;
public T V94;
public T V95;
public T V96;
public T V97;
public T V98;
public T V99;
public T V100;
public T V101;
public T V102;
public T V103;
public T V104;
public T V105;
public T V106;
public T V107;
public T V108;
public T V109;
public T V110;
public T V111;
public T V112;
public T V113;
public T V114;
public T V115;
public T V116;
public T V117;
public T V118;
public T V119;
public T V120;
public T V121;
public T V122;
public T V123;
public T V124;
public T V125;
public T V126;
public T V127;
public T this[int index]
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get
{
RcThrowHelper.ThrowExceptionIfIndexOutOfRange(index, Length);
return index switch
{
0 => V0,
1 => V1,
2 => V2,
3 => V3,
4 => V4,
5 => V5,
6 => V6,
7 => V7,
8 => V8,
9 => V9,
10 => V10,
11 => V11,
12 => V12,
13 => V13,
14 => V14,
15 => V15,
16 => V16,
17 => V17,
18 => V18,
19 => V19,
20 => V20,
21 => V21,
22 => V22,
23 => V23,
24 => V24,
25 => V25,
26 => V26,
27 => V27,
28 => V28,
29 => V29,
30 => V30,
31 => V31,
32 => V32,
33 => V33,
34 => V34,
35 => V35,
36 => V36,
37 => V37,
38 => V38,
39 => V39,
40 => V40,
41 => V41,
42 => V42,
43 => V43,
44 => V44,
45 => V45,
46 => V46,
47 => V47,
48 => V48,
49 => V49,
50 => V50,
51 => V51,
52 => V52,
53 => V53,
54 => V54,
55 => V55,
56 => V56,
57 => V57,
58 => V58,
59 => V59,
60 => V60,
61 => V61,
62 => V62,
63 => V63,
64 => V64,
65 => V65,
66 => V66,
67 => V67,
68 => V68,
69 => V69,
70 => V70,
71 => V71,
72 => V72,
73 => V73,
74 => V74,
75 => V75,
76 => V76,
77 => V77,
78 => V78,
79 => V79,
80 => V80,
81 => V81,
82 => V82,
83 => V83,
84 => V84,
85 => V85,
86 => V86,
87 => V87,
88 => V88,
89 => V89,
90 => V90,
91 => V91,
92 => V92,
93 => V93,
94 => V94,
95 => V95,
96 => V96,
97 => V97,
98 => V98,
99 => V99,
100 => V100,
101 => V101,
102 => V102,
103 => V103,
104 => V104,
105 => V105,
106 => V106,
107 => V107,
108 => V108,
109 => V109,
110 => V110,
111 => V111,
112 => V112,
113 => V113,
114 => V114,
115 => V115,
116 => V116,
117 => V117,
118 => V118,
119 => V119,
120 => V120,
121 => V121,
122 => V122,
123 => V123,
124 => V124,
125 => V125,
126 => V126,
127 => V127,
_ => throw new ArgumentOutOfRangeException(nameof(index), index, null)
};
}
set
{
RcThrowHelper.ThrowExceptionIfIndexOutOfRange(index, Length);
switch (index)
{
case 0: V0 = value; break;
case 1: V1 = value; break;
case 2: V2 = value; break;
case 3: V3 = value; break;
case 4: V4 = value; break;
case 5: V5 = value; break;
case 6: V6 = value; break;
case 7: V7 = value; break;
case 8: V8 = value; break;
case 9: V9 = value; break;
case 10: V10 = value; break;
case 11: V11 = value; break;
case 12: V12 = value; break;
case 13: V13 = value; break;
case 14: V14 = value; break;
case 15: V15 = value; break;
case 16: V16 = value; break;
case 17: V17 = value; break;
case 18: V18 = value; break;
case 19: V19 = value; break;
case 20: V20 = value; break;
case 21: V21 = value; break;
case 22: V22 = value; break;
case 23: V23 = value; break;
case 24: V24 = value; break;
case 25: V25 = value; break;
case 26: V26 = value; break;
case 27: V27 = value; break;
case 28: V28 = value; break;
case 29: V29 = value; break;
case 30: V30 = value; break;
case 31: V31 = value; break;
case 32 : V32 = value; break;
case 33 : V33 = value; break;
case 34 : V34 = value; break;
case 35 : V35 = value; break;
case 36 : V36 = value; break;
case 37 : V37 = value; break;
case 38 : V38 = value; break;
case 39 : V39 = value; break;
case 40 : V40 = value; break;
case 41 : V41 = value; break;
case 42 : V42 = value; break;
case 43 : V43 = value; break;
case 44 : V44 = value; break;
case 45 : V45 = value; break;
case 46 : V46 = value; break;
case 47 : V47 = value; break;
case 48 : V48 = value; break;
case 49 : V49 = value; break;
case 50 : V50 = value; break;
case 51 : V51 = value; break;
case 52 : V52 = value; break;
case 53 : V53 = value; break;
case 54 : V54 = value; break;
case 55 : V55 = value; break;
case 56 : V56 = value; break;
case 57 : V57 = value; break;
case 58 : V58 = value; break;
case 59 : V59 = value; break;
case 60 : V60 = value; break;
case 61 : V61 = value; break;
case 62 : V62 = value; break;
case 63 : V63 = value; break;
case 64 : V64 = value; break;
case 65 : V65 = value; break;
case 66 : V66 = value; break;
case 67 : V67 = value; break;
case 68 : V68 = value; break;
case 69 : V69 = value; break;
case 70 : V70 = value; break;
case 71 : V71 = value; break;
case 72 : V72 = value; break;
case 73 : V73 = value; break;
case 74 : V74 = value; break;
case 75 : V75 = value; break;
case 76 : V76 = value; break;
case 77 : V77 = value; break;
case 78 : V78 = value; break;
case 79 : V79 = value; break;
case 80 : V80 = value; break;
case 81 : V81 = value; break;
case 82 : V82 = value; break;
case 83 : V83 = value; break;
case 84 : V84 = value; break;
case 85 : V85 = value; break;
case 86 : V86 = value; break;
case 87 : V87 = value; break;
case 88 : V88 = value; break;
case 89 : V89 = value; break;
case 90 : V90 = value; break;
case 91 : V91 = value; break;
case 92 : V92 = value; break;
case 93 : V93 = value; break;
case 94 : V94 = value; break;
case 95 : V95 = value; break;
case 96 : V96 = value; break;
case 97 : V97 = value; break;
case 98 : V98 = value; break;
case 99 : V99 = value; break;
case 100 : V100 = value; break;
case 101 : V101 = value; break;
case 102 : V102 = value; break;
case 103 : V103 = value; break;
case 104 : V104 = value; break;
case 105 : V105 = value; break;
case 106 : V106 = value; break;
case 107 : V107 = value; break;
case 108 : V108 = value; break;
case 109 : V109 = value; break;
case 110 : V110 = value; break;
case 111 : V111 = value; break;
case 112 : V112 = value; break;
case 113 : V113 = value; break;
case 114 : V114 = value; break;
case 115 : V115 = value; break;
case 116 : V116 = value; break;
case 117 : V117 = value; break;
case 118 : V118 = value; break;
case 119 : V119 = value; break;
case 120 : V120 = value; break;
case 121 : V121 = value; break;
case 122 : V122 = value; break;
case 123 : V123 = value; break;
case 124 : V124 = value; break;
case 125 : V125 = value; break;
case 126 : V126 = value; break;
case 127 : V127 = value; break;
}
}
}
}
}

View File

@ -0,0 +1,85 @@
using System;
using System.Runtime.CompilerServices;
namespace DotRecast.Core.Collections
{
public struct RcStackArray16<T>
{
public static RcStackArray16<T> Empty => new RcStackArray16<T>();
private const int Size = 16;
public int Length => Size;
public T V0;
public T V1;
public T V2;
public T V3;
public T V4;
public T V5;
public T V6;
public T V7;
public T V8;
public T V9;
public T V10;
public T V11;
public T V12;
public T V13;
public T V14;
public T V15;
public T this[int index]
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get
{
RcThrowHelper.ThrowExceptionIfIndexOutOfRange(index, Length);
return index switch
{
0 => V0,
1 => V1,
2 => V2,
3 => V3,
4 => V4,
5 => V5,
6 => V6,
7 => V7,
8 => V8,
9 => V9,
10 => V10,
11 => V11,
12 => V12,
13 => V13,
14 => V14,
15 => V15,
_ => throw new IndexOutOfRangeException($"{index}")
};
}
set
{
RcThrowHelper.ThrowExceptionIfIndexOutOfRange(index, Length);
switch (index)
{
case 0: V0 = value; break;
case 1: V1 = value; break;
case 2: V2 = value; break;
case 3: V3 = value; break;
case 4: V4 = value; break;
case 5: V5 = value; break;
case 6: V6 = value; break;
case 7: V7 = value; break;
case 8: V8 = value; break;
case 9: V9 = value; break;
case 10: V10 = value; break;
case 11: V11 = value; break;
case 12: V12 = value; break;
case 13: V13 = value; break;
case 14: V14 = value; break;
case 15: V15 = value; break;
}
}
}
}
}

View File

@ -0,0 +1,43 @@
using System;
using System.Runtime.CompilerServices;
namespace DotRecast.Core.Collections
{
public struct RcStackArray2<T>
{
public static RcStackArray2<T> Empty => new RcStackArray2<T>();
private const int Size = 2;
public int Length => Size;
public T V0;
public T V1;
public T this[int index]
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get
{
RcThrowHelper.ThrowExceptionIfIndexOutOfRange(index, Length);
return index switch
{
0 => V0,
1 => V1,
_ => throw new IndexOutOfRangeException($"{index}")
};
}
set
{
RcThrowHelper.ThrowExceptionIfIndexOutOfRange(index, Length);
switch (index)
{
case 0: V0 = value; break;
case 1: V1 = value; break;
}
}
}
}
}

View File

@ -0,0 +1,805 @@
using System;
using System.Runtime.CompilerServices;
namespace DotRecast.Core.Collections
{
public struct RcStackArray256<T>
{
public static RcStackArray256<T> Empty => new RcStackArray256<T>();
private const int Size = 256;
public int Length => Size;
public T V0;
public T V1;
public T V2;
public T V3;
public T V4;
public T V5;
public T V6;
public T V7;
public T V8;
public T V9;
public T V10;
public T V11;
public T V12;
public T V13;
public T V14;
public T V15;
public T V16;
public T V17;
public T V18;
public T V19;
public T V20;
public T V21;
public T V22;
public T V23;
public T V24;
public T V25;
public T V26;
public T V27;
public T V28;
public T V29;
public T V30;
public T V31;
public T V32;
public T V33;
public T V34;
public T V35;
public T V36;
public T V37;
public T V38;
public T V39;
public T V40;
public T V41;
public T V42;
public T V43;
public T V44;
public T V45;
public T V46;
public T V47;
public T V48;
public T V49;
public T V50;
public T V51;
public T V52;
public T V53;
public T V54;
public T V55;
public T V56;
public T V57;
public T V58;
public T V59;
public T V60;
public T V61;
public T V62;
public T V63;
public T V64;
public T V65;
public T V66;
public T V67;
public T V68;
public T V69;
public T V70;
public T V71;
public T V72;
public T V73;
public T V74;
public T V75;
public T V76;
public T V77;
public T V78;
public T V79;
public T V80;
public T V81;
public T V82;
public T V83;
public T V84;
public T V85;
public T V86;
public T V87;
public T V88;
public T V89;
public T V90;
public T V91;
public T V92;
public T V93;
public T V94;
public T V95;
public T V96;
public T V97;
public T V98;
public T V99;
public T V100;
public T V101;
public T V102;
public T V103;
public T V104;
public T V105;
public T V106;
public T V107;
public T V108;
public T V109;
public T V110;
public T V111;
public T V112;
public T V113;
public T V114;
public T V115;
public T V116;
public T V117;
public T V118;
public T V119;
public T V120;
public T V121;
public T V122;
public T V123;
public T V124;
public T V125;
public T V126;
public T V127;
public T V128;
public T V129;
public T V130;
public T V131;
public T V132;
public T V133;
public T V134;
public T V135;
public T V136;
public T V137;
public T V138;
public T V139;
public T V140;
public T V141;
public T V142;
public T V143;
public T V144;
public T V145;
public T V146;
public T V147;
public T V148;
public T V149;
public T V150;
public T V151;
public T V152;
public T V153;
public T V154;
public T V155;
public T V156;
public T V157;
public T V158;
public T V159;
public T V160;
public T V161;
public T V162;
public T V163;
public T V164;
public T V165;
public T V166;
public T V167;
public T V168;
public T V169;
public T V170;
public T V171;
public T V172;
public T V173;
public T V174;
public T V175;
public T V176;
public T V177;
public T V178;
public T V179;
public T V180;
public T V181;
public T V182;
public T V183;
public T V184;
public T V185;
public T V186;
public T V187;
public T V188;
public T V189;
public T V190;
public T V191;
public T V192;
public T V193;
public T V194;
public T V195;
public T V196;
public T V197;
public T V198;
public T V199;
public T V200;
public T V201;
public T V202;
public T V203;
public T V204;
public T V205;
public T V206;
public T V207;
public T V208;
public T V209;
public T V210;
public T V211;
public T V212;
public T V213;
public T V214;
public T V215;
public T V216;
public T V217;
public T V218;
public T V219;
public T V220;
public T V221;
public T V222;
public T V223;
public T V224;
public T V225;
public T V226;
public T V227;
public T V228;
public T V229;
public T V230;
public T V231;
public T V232;
public T V233;
public T V234;
public T V235;
public T V236;
public T V237;
public T V238;
public T V239;
public T V240;
public T V241;
public T V242;
public T V243;
public T V244;
public T V245;
public T V246;
public T V247;
public T V248;
public T V249;
public T V250;
public T V251;
public T V252;
public T V253;
public T V254;
public T V255;
public T this[int index]
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get
{
RcThrowHelper.ThrowExceptionIfIndexOutOfRange(index, Length);
return index switch
{
0 => V0,
1 => V1,
2 => V2,
3 => V3,
4 => V4,
5 => V5,
6 => V6,
7 => V7,
8 => V8,
9 => V9,
10 => V10,
11 => V11,
12 => V12,
13 => V13,
14 => V14,
15 => V15,
16 => V16,
17 => V17,
18 => V18,
19 => V19,
20 => V20,
21 => V21,
22 => V22,
23 => V23,
24 => V24,
25 => V25,
26 => V26,
27 => V27,
28 => V28,
29 => V29,
30 => V30,
31 => V31,
32 => V32,
33 => V33,
34 => V34,
35 => V35,
36 => V36,
37 => V37,
38 => V38,
39 => V39,
40 => V40,
41 => V41,
42 => V42,
43 => V43,
44 => V44,
45 => V45,
46 => V46,
47 => V47,
48 => V48,
49 => V49,
50 => V50,
51 => V51,
52 => V52,
53 => V53,
54 => V54,
55 => V55,
56 => V56,
57 => V57,
58 => V58,
59 => V59,
60 => V60,
61 => V61,
62 => V62,
63 => V63,
64 => V64,
65 => V65,
66 => V66,
67 => V67,
68 => V68,
69 => V69,
70 => V70,
71 => V71,
72 => V72,
73 => V73,
74 => V74,
75 => V75,
76 => V76,
77 => V77,
78 => V78,
79 => V79,
80 => V80,
81 => V81,
82 => V82,
83 => V83,
84 => V84,
85 => V85,
86 => V86,
87 => V87,
88 => V88,
89 => V89,
90 => V90,
91 => V91,
92 => V92,
93 => V93,
94 => V94,
95 => V95,
96 => V96,
97 => V97,
98 => V98,
99 => V99,
100 => V100,
101 => V101,
102 => V102,
103 => V103,
104 => V104,
105 => V105,
106 => V106,
107 => V107,
108 => V108,
109 => V109,
110 => V110,
111 => V111,
112 => V112,
113 => V113,
114 => V114,
115 => V115,
116 => V116,
117 => V117,
118 => V118,
119 => V119,
120 => V120,
121 => V121,
122 => V122,
123 => V123,
124 => V124,
125 => V125,
126 => V126,
127 => V127,
128 => V128,
129 => V129,
130 => V130,
131 => V131,
132 => V132,
133 => V133,
134 => V134,
135 => V135,
136 => V136,
137 => V137,
138 => V138,
139 => V139,
140 => V140,
141 => V141,
142 => V142,
143 => V143,
144 => V144,
145 => V145,
146 => V146,
147 => V147,
148 => V148,
149 => V149,
150 => V150,
151 => V151,
152 => V152,
153 => V153,
154 => V154,
155 => V155,
156 => V156,
157 => V157,
158 => V158,
159 => V159,
160 => V160,
161 => V161,
162 => V162,
163 => V163,
164 => V164,
165 => V165,
166 => V166,
167 => V167,
168 => V168,
169 => V169,
170 => V170,
171 => V171,
172 => V172,
173 => V173,
174 => V174,
175 => V175,
176 => V176,
177 => V177,
178 => V178,
179 => V179,
180 => V180,
181 => V181,
182 => V182,
183 => V183,
184 => V184,
185 => V185,
186 => V186,
187 => V187,
188 => V188,
189 => V189,
190 => V190,
191 => V191,
192 => V192,
193 => V193,
194 => V194,
195 => V195,
196 => V196,
197 => V197,
198 => V198,
199 => V199,
200 => V200,
201 => V201,
202 => V202,
203 => V203,
204 => V204,
205 => V205,
206 => V206,
207 => V207,
208 => V208,
209 => V209,
210 => V210,
211 => V211,
212 => V212,
213 => V213,
214 => V214,
215 => V215,
216 => V216,
217 => V217,
218 => V218,
219 => V219,
220 => V220,
221 => V221,
222 => V222,
223 => V223,
224 => V224,
225 => V225,
226 => V226,
227 => V227,
228 => V228,
229 => V229,
230 => V230,
231 => V231,
232 => V232,
233 => V233,
234 => V234,
235 => V235,
236 => V236,
237 => V237,
238 => V238,
239 => V239,
240 => V240,
241 => V241,
242 => V242,
243 => V243,
244 => V244,
245 => V245,
246 => V246,
247 => V247,
248 => V248,
249 => V249,
250 => V250,
251 => V251,
252 => V252,
253 => V253,
254 => V254,
255 => V255,
_ => throw new ArgumentOutOfRangeException(nameof(index), index, null)
};
}
set
{
RcThrowHelper.ThrowExceptionIfIndexOutOfRange(index, Length);
switch (index)
{
case 0: V0 = value; break;
case 1: V1 = value; break;
case 2: V2 = value; break;
case 3: V3 = value; break;
case 4: V4 = value; break;
case 5: V5 = value; break;
case 6: V6 = value; break;
case 7: V7 = value; break;
case 8: V8 = value; break;
case 9: V9 = value; break;
case 10: V10 = value; break;
case 11: V11 = value; break;
case 12: V12 = value; break;
case 13: V13 = value; break;
case 14: V14 = value; break;
case 15: V15 = value; break;
case 16: V16 = value; break;
case 17: V17 = value; break;
case 18: V18 = value; break;
case 19: V19 = value; break;
case 20: V20 = value; break;
case 21: V21 = value; break;
case 22: V22 = value; break;
case 23: V23 = value; break;
case 24: V24 = value; break;
case 25: V25 = value; break;
case 26: V26 = value; break;
case 27: V27 = value; break;
case 28: V28 = value; break;
case 29: V29 = value; break;
case 30: V30 = value; break;
case 31: V31 = value; break;
case 32: V32 = value; break;
case 33: V33 = value; break;
case 34: V34 = value; break;
case 35: V35 = value; break;
case 36: V36 = value; break;
case 37: V37 = value; break;
case 38: V38 = value; break;
case 39: V39 = value; break;
case 40: V40 = value; break;
case 41: V41 = value; break;
case 42: V42 = value; break;
case 43: V43 = value; break;
case 44: V44 = value; break;
case 45: V45 = value; break;
case 46: V46 = value; break;
case 47: V47 = value; break;
case 48: V48 = value; break;
case 49: V49 = value; break;
case 50: V50 = value; break;
case 51: V51 = value; break;
case 52: V52 = value; break;
case 53: V53 = value; break;
case 54: V54 = value; break;
case 55: V55 = value; break;
case 56: V56 = value; break;
case 57: V57 = value; break;
case 58: V58 = value; break;
case 59: V59 = value; break;
case 60: V60 = value; break;
case 61: V61 = value; break;
case 62: V62 = value; break;
case 63: V63 = value; break;
case 64: V64 = value; break;
case 65: V65 = value; break;
case 66: V66 = value; break;
case 67: V67 = value; break;
case 68: V68 = value; break;
case 69: V69 = value; break;
case 70: V70 = value; break;
case 71: V71 = value; break;
case 72: V72 = value; break;
case 73: V73 = value; break;
case 74: V74 = value; break;
case 75: V75 = value; break;
case 76: V76 = value; break;
case 77: V77 = value; break;
case 78: V78 = value; break;
case 79: V79 = value; break;
case 80: V80 = value; break;
case 81: V81 = value; break;
case 82: V82 = value; break;
case 83: V83 = value; break;
case 84: V84 = value; break;
case 85: V85 = value; break;
case 86: V86 = value; break;
case 87: V87 = value; break;
case 88: V88 = value; break;
case 89: V89 = value; break;
case 90: V90 = value; break;
case 91: V91 = value; break;
case 92: V92 = value; break;
case 93: V93 = value; break;
case 94: V94 = value; break;
case 95: V95 = value; break;
case 96: V96 = value; break;
case 97: V97 = value; break;
case 98: V98 = value; break;
case 99: V99 = value; break;
case 100: V100 = value; break;
case 101: V101 = value; break;
case 102: V102 = value; break;
case 103: V103 = value; break;
case 104: V104 = value; break;
case 105: V105 = value; break;
case 106: V106 = value; break;
case 107: V107 = value; break;
case 108: V108 = value; break;
case 109: V109 = value; break;
case 110: V110 = value; break;
case 111: V111 = value; break;
case 112: V112 = value; break;
case 113: V113 = value; break;
case 114: V114 = value; break;
case 115: V115 = value; break;
case 116: V116 = value; break;
case 117: V117 = value; break;
case 118: V118 = value; break;
case 119: V119 = value; break;
case 120: V120 = value; break;
case 121: V121 = value; break;
case 122: V122 = value; break;
case 123: V123 = value; break;
case 124: V124 = value; break;
case 125: V125 = value; break;
case 126: V126 = value; break;
case 127: V127 = value; break;
case 128: V128 = value; break;
case 129: V129 = value; break;
case 130: V130 = value; break;
case 131: V131 = value; break;
case 132: V132 = value; break;
case 133: V133 = value; break;
case 134: V134 = value; break;
case 135: V135 = value; break;
case 136: V136 = value; break;
case 137: V137 = value; break;
case 138: V138 = value; break;
case 139: V139 = value; break;
case 140: V140 = value; break;
case 141: V141 = value; break;
case 142: V142 = value; break;
case 143: V143 = value; break;
case 144: V144 = value; break;
case 145: V145 = value; break;
case 146: V146 = value; break;
case 147: V147 = value; break;
case 148: V148 = value; break;
case 149: V149 = value; break;
case 150: V150 = value; break;
case 151: V151 = value; break;
case 152: V152 = value; break;
case 153: V153 = value; break;
case 154: V154 = value; break;
case 155: V155 = value; break;
case 156: V156 = value; break;
case 157: V157 = value; break;
case 158: V158 = value; break;
case 159: V159 = value; break;
case 160: V160 = value; break;
case 161: V161 = value; break;
case 162: V162 = value; break;
case 163: V163 = value; break;
case 164: V164 = value; break;
case 165: V165 = value; break;
case 166: V166 = value; break;
case 167: V167 = value; break;
case 168: V168 = value; break;
case 169: V169 = value; break;
case 170: V170 = value; break;
case 171: V171 = value; break;
case 172: V172 = value; break;
case 173: V173 = value; break;
case 174: V174 = value; break;
case 175: V175 = value; break;
case 176: V176 = value; break;
case 177: V177 = value; break;
case 178: V178 = value; break;
case 179: V179 = value; break;
case 180: V180 = value; break;
case 181: V181 = value; break;
case 182: V182 = value; break;
case 183: V183 = value; break;
case 184: V184 = value; break;
case 185: V185 = value; break;
case 186: V186 = value; break;
case 187: V187 = value; break;
case 188: V188 = value; break;
case 189: V189 = value; break;
case 190: V190 = value; break;
case 191: V191 = value; break;
case 192: V192 = value; break;
case 193: V193 = value; break;
case 194: V194 = value; break;
case 195: V195 = value; break;
case 196: V196 = value; break;
case 197: V197 = value; break;
case 198: V198 = value; break;
case 199: V199 = value; break;
case 200: V200 = value; break;
case 201: V201 = value; break;
case 202: V202 = value; break;
case 203: V203 = value; break;
case 204: V204 = value; break;
case 205: V205 = value; break;
case 206: V206 = value; break;
case 207: V207 = value; break;
case 208: V208 = value; break;
case 209: V209 = value; break;
case 210: V210 = value; break;
case 211: V211 = value; break;
case 212: V212 = value; break;
case 213: V213 = value; break;
case 214: V214 = value; break;
case 215: V215 = value; break;
case 216: V216 = value; break;
case 217: V217 = value; break;
case 218: V218 = value; break;
case 219: V219 = value; break;
case 220: V220 = value; break;
case 221: V221 = value; break;
case 222: V222 = value; break;
case 223: V223 = value; break;
case 224: V224 = value; break;
case 225: V225 = value; break;
case 226: V226 = value; break;
case 227: V227 = value; break;
case 228: V228 = value; break;
case 229: V229 = value; break;
case 230: V230 = value; break;
case 231: V231 = value; break;
case 232: V232 = value; break;
case 233: V233 = value; break;
case 234: V234 = value; break;
case 235: V235 = value; break;
case 236: V236 = value; break;
case 237: V237 = value; break;
case 238: V238 = value; break;
case 239: V239 = value; break;
case 240: V240 = value; break;
case 241: V241 = value; break;
case 242: V242 = value; break;
case 243: V243 = value; break;
case 244: V244 = value; break;
case 245: V245 = value; break;
case 246: V246 = value; break;
case 247: V247 = value; break;
case 248: V248 = value; break;
case 249: V249 = value; break;
case 250: V250 = value; break;
case 251: V251 = value; break;
case 252: V252 = value; break;
case 253: V253 = value; break;
case 254: V254 = value; break;
case 255: V255 = value; break;
}
}
}
}
}

View File

@ -0,0 +1,133 @@
using System;
using System.Runtime.CompilerServices;
namespace DotRecast.Core.Collections
{
public struct RcStackArray32<T>
{
public static RcStackArray32<T> Empty => new RcStackArray32<T>();
private const int Size = 32;
public int Length => Size;
public T V0;
public T V1;
public T V2;
public T V3;
public T V4;
public T V5;
public T V6;
public T V7;
public T V8;
public T V9;
public T V10;
public T V11;
public T V12;
public T V13;
public T V14;
public T V15;
public T V16;
public T V17;
public T V18;
public T V19;
public T V20;
public T V21;
public T V22;
public T V23;
public T V24;
public T V25;
public T V26;
public T V27;
public T V28;
public T V29;
public T V30;
public T V31;
public T this[int index]
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get
{
RcThrowHelper.ThrowExceptionIfIndexOutOfRange(index, Length);
return index switch
{
0 => V0,
1 => V1,
2 => V2,
3 => V3,
4 => V4,
5 => V5,
6 => V6,
7 => V7,
8 => V8,
9 => V9,
10 => V10,
11 => V11,
12 => V12,
13 => V13,
14 => V14,
15 => V15,
16 => V16,
17 => V17,
18 => V18,
19 => V19,
20 => V20,
21 => V21,
22 => V22,
23 => V23,
24 => V24,
25 => V25,
26 => V26,
27 => V27,
28 => V28,
29 => V29,
30 => V30,
31 => V31,
_ => throw new IndexOutOfRangeException($"{index}")
};
}
set
{
RcThrowHelper.ThrowExceptionIfIndexOutOfRange(index, Length);
switch (index)
{
case 0: V0 = value; break;
case 1: V1 = value; break;
case 2: V2 = value; break;
case 3: V3 = value; break;
case 4: V4 = value; break;
case 5: V5 = value; break;
case 6: V6 = value; break;
case 7: V7 = value; break;
case 8: V8 = value; break;
case 9: V9 = value; break;
case 10: V10 = value; break;
case 11: V11 = value; break;
case 12: V12 = value; break;
case 13: V13 = value; break;
case 14: V14 = value; break;
case 15: V15 = value; break;
case 16: V16 = value; break;
case 17: V17 = value; break;
case 18: V18 = value; break;
case 19: V19 = value; break;
case 20: V20 = value; break;
case 21: V21 = value; break;
case 22: V22 = value; break;
case 23: V23 = value; break;
case 24: V24 = value; break;
case 25: V25 = value; break;
case 26: V26 = value; break;
case 27: V27 = value; break;
case 28: V28 = value; break;
case 29: V29 = value; break;
case 30: V30 = value; break;
case 31: V31 = value; break;
}
}
}
}
}

View File

@ -0,0 +1,49 @@
using System;
using System.Runtime.CompilerServices;
namespace DotRecast.Core.Collections
{
public struct RcStackArray4<T>
{
public static RcStackArray4<T> Empty => new RcStackArray4<T>();
private const int Size = 4;
public int Length => Size;
public T V0;
public T V1;
public T V2;
public T V3;
public T this[int index]
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get
{
RcThrowHelper.ThrowExceptionIfIndexOutOfRange(index, Length);
return index switch
{
0 => V0,
1 => V1,
2 => V2,
3 => V3,
_ => throw new IndexOutOfRangeException($"{index}")
};
}
set
{
RcThrowHelper.ThrowExceptionIfIndexOutOfRange(index, Length);
switch (index)
{
case 0: V0 = value; break;
case 1: V1 = value; break;
case 2: V2 = value; break;
case 3: V3 = value; break;
}
}
}
}
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,229 @@
using System;
using System.Runtime.CompilerServices;
namespace DotRecast.Core.Collections
{
public struct RcStackArray64<T>
{
public static RcStackArray64<T> Empty => new RcStackArray64<T>();
private const int Size = 64;
public int Length => Size;
public T V0;
public T V1;
public T V2;
public T V3;
public T V4;
public T V5;
public T V6;
public T V7;
public T V8;
public T V9;
public T V10;
public T V11;
public T V12;
public T V13;
public T V14;
public T V15;
public T V16;
public T V17;
public T V18;
public T V19;
public T V20;
public T V21;
public T V22;
public T V23;
public T V24;
public T V25;
public T V26;
public T V27;
public T V28;
public T V29;
public T V30;
public T V31;
public T V32;
public T V33;
public T V34;
public T V35;
public T V36;
public T V37;
public T V38;
public T V39;
public T V40;
public T V41;
public T V42;
public T V43;
public T V44;
public T V45;
public T V46;
public T V47;
public T V48;
public T V49;
public T V50;
public T V51;
public T V52;
public T V53;
public T V54;
public T V55;
public T V56;
public T V57;
public T V58;
public T V59;
public T V60;
public T V61;
public T V62;
public T V63;
public T this[int index]
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get
{
RcThrowHelper.ThrowExceptionIfIndexOutOfRange(index, Length);
return index switch
{
0 => V0,
1 => V1,
2 => V2,
3 => V3,
4 => V4,
5 => V5,
6 => V6,
7 => V7,
8 => V8,
9 => V9,
10 => V10,
11 => V11,
12 => V12,
13 => V13,
14 => V14,
15 => V15,
16 => V16,
17 => V17,
18 => V18,
19 => V19,
20 => V20,
21 => V21,
22 => V22,
23 => V23,
24 => V24,
25 => V25,
26 => V26,
27 => V27,
28 => V28,
29 => V29,
30 => V30,
31 => V31,
32 => V32,
33 => V33,
34 => V34,
35 => V35,
36 => V36,
37 => V37,
38 => V38,
39 => V39,
40 => V40,
41 => V41,
42 => V42,
43 => V43,
44 => V44,
45 => V45,
46 => V46,
47 => V47,
48 => V48,
49 => V49,
50 => V50,
51 => V51,
52 => V52,
53 => V53,
54 => V54,
55 => V55,
56 => V56,
57 => V57,
58 => V58,
59 => V59,
60 => V60,
61 => V61,
62 => V62,
63 => V63,
_ => throw new ArgumentOutOfRangeException(nameof(index), index, null)
};
}
set
{
RcThrowHelper.ThrowExceptionIfIndexOutOfRange(index, Length);
switch (index)
{
case 0: V0 = value; break;
case 1: V1 = value; break;
case 2: V2 = value; break;
case 3: V3 = value; break;
case 4: V4 = value; break;
case 5: V5 = value; break;
case 6: V6 = value; break;
case 7: V7 = value; break;
case 8: V8 = value; break;
case 9: V9 = value; break;
case 10: V10 = value; break;
case 11: V11 = value; break;
case 12: V12 = value; break;
case 13: V13 = value; break;
case 14: V14 = value; break;
case 15: V15 = value; break;
case 16: V16 = value; break;
case 17: V17 = value; break;
case 18: V18 = value; break;
case 19: V19 = value; break;
case 20: V20 = value; break;
case 21: V21 = value; break;
case 22: V22 = value; break;
case 23: V23 = value; break;
case 24: V24 = value; break;
case 25: V25 = value; break;
case 26: V26 = value; break;
case 27: V27 = value; break;
case 28: V28 = value; break;
case 29: V29 = value; break;
case 30: V30 = value; break;
case 31: V31 = value; break;
case 32 : V32 = value; break;
case 33 : V33 = value; break;
case 34 : V34 = value; break;
case 35 : V35 = value; break;
case 36 : V36 = value; break;
case 37 : V37 = value; break;
case 38 : V38 = value; break;
case 39 : V39 = value; break;
case 40 : V40 = value; break;
case 41 : V41 = value; break;
case 42 : V42 = value; break;
case 43 : V43 = value; break;
case 44 : V44 = value; break;
case 45 : V45 = value; break;
case 46 : V46 = value; break;
case 47 : V47 = value; break;
case 48 : V48 = value; break;
case 49 : V49 = value; break;
case 50 : V50 = value; break;
case 51 : V51 = value; break;
case 52 : V52 = value; break;
case 53 : V53 = value; break;
case 54 : V54 = value; break;
case 55 : V55 = value; break;
case 56 : V56 = value; break;
case 57 : V57 = value; break;
case 58 : V58 = value; break;
case 59 : V59 = value; break;
case 60 : V60 = value; break;
case 61 : V61 = value; break;
case 62 : V62 = value; break;
case 63 : V63 = value; break;
}
}
}
}
}

View File

@ -0,0 +1,61 @@
using System;
using System.Runtime.CompilerServices;
namespace DotRecast.Core.Collections
{
public struct RcStackArray8<T>
{
public static RcStackArray8<T> Empty => new RcStackArray8<T>();
private const int Size = 8;
public int Length => Size;
public T V0;
public T V1;
public T V2;
public T V3;
public T V4;
public T V5;
public T V6;
public T V7;
public T this[int index]
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get
{
RcThrowHelper.ThrowExceptionIfIndexOutOfRange(index, Length);
return index switch
{
0 => V0,
1 => V1,
2 => V2,
3 => V3,
4 => V4,
5 => V5,
6 => V6,
7 => V7,
_ => throw new IndexOutOfRangeException($"{index}")
};
}
set
{
RcThrowHelper.ThrowExceptionIfIndexOutOfRange(index, Length);
switch (index)
{
case 0: V0 = value; break;
case 1: V1 = value; break;
case 2: V2 = value; break;
case 3: V3 = value; break;
case 4: V4 = value; break;
case 5: V5 = value; break;
case 6: V6 = value; break;
case 7: V7 = value; break;
}
}
}
}
}

View File

@ -356,7 +356,7 @@ namespace DotRecast.Core.Compression
return 0;
}
Array.Copy(input, ip, output, op, ctrl);
RcArrays.Copy(input, ip, output, op, ctrl);
ip += ctrl;
op += ctrl;
}
@ -452,7 +452,7 @@ namespace DotRecast.Core.Compression
return 0;
}
Array.Copy(input, ip, output, op, ctrl);
RcArrays.Copy(input, ip, output, op, ctrl);
ip += ctrl;
op += ctrl;
}
@ -498,16 +498,16 @@ namespace DotRecast.Core.Compression
// if (count >= 4)
// {
// count -= count % 4;
// Array.Copy(src, srcOffset, dest, destOffset, count);
// RcArrays.Copy(src, srcOffset, dest, destOffset, count);
// }
Array.Copy(src, srcOffset, dest, destOffset, count);
RcArrays.Copy(src, srcOffset, dest, destOffset, count);
}
// special case of memcpy: exactly MAX_COPY bytes
// flz_maxcopy
static void MaxCopy(byte[] dest, long destOffset, byte[] src, long secOffset)
{
Array.Copy(src, secOffset, dest, destOffset, MAX_COPY);
RcArrays.Copy(src, secOffset, dest, destOffset, MAX_COPY);
}
// flz_literals

View File

@ -1,15 +1,16 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>netstandard2.1;net6.0;net7.0</TargetFrameworks>
<TargetFrameworks>netstandard2.1;net6.0;net7.0;net8.0</TargetFrameworks>
<PackageId>DotRecast.Core</PackageId>
<PackageReadmeFile>README.md</PackageReadmeFile>
<Authors>ikpil</Authors>
<Description>DotRecast - a port of Recast Detour, navigation mesh toolset for games, Unity3D, servers, C#</Description>
<Description>DotRecast - a port of Recast Detour, Industry-standard navigation mesh toolset for .NET, C#, Unity3D, games, servers</Description>
<RepositoryType>git</RepositoryType>
<PackageProjectUrl>https://github.com/ikpil/DotRecast</PackageProjectUrl>
<RepositoryUrl>https://github.com/ikpil/DotRecast</RepositoryUrl>
<PackageTags>game gamedev ai csharp server unity navigation game-development unity3d pathfinding pathfinder recast detour navmesh crowd-simulation recastnavigation</PackageTags>
<PackageReleaseNotes>https://github.com/ikpil/DotRecast/blob/main/CHANGELOG.md</PackageReleaseNotes>
</PropertyGroup>
<ItemGroup>

View File

@ -8,7 +8,7 @@ namespace DotRecast.Core.Numerics
public float X;
public float Y;
public static RcVec2f Zero { get; } = new RcVec2f { X = 0, Y = 0 };
public static readonly RcVec2f Zero = new RcVec2f { X = 0, Y = 0 };
public RcVec2f(float x, float y)
{

View File

@ -27,11 +27,11 @@ namespace DotRecast.Core.Numerics
public float Y;
public float Z;
public static RcVec3f Zero { get; } = new RcVec3f(0.0f, 0.0f, 0.0f);
public static RcVec3f One { get; } = new RcVec3f(1.0f);
public static RcVec3f UnitX { get; } = new RcVec3f(1.0f, 0.0f, 0.0f);
public static RcVec3f UnitY { get; } = new RcVec3f(0.0f, 1.0f, 0.0f);
public static RcVec3f UnitZ { get; } = new RcVec3f(0.0f, 0.0f, 1.0f);
public static readonly RcVec3f Zero = new RcVec3f(0.0f, 0.0f, 0.0f);
public static readonly RcVec3f One = new RcVec3f(1.0f);
public static readonly RcVec3f UnitX = new RcVec3f(1.0f, 0.0f, 0.0f);
public static readonly RcVec3f UnitY = new RcVec3f(0.0f, 1.0f, 0.0f);
public static readonly RcVec3f UnitZ = new RcVec3f(0.0f, 0.0f, 1.0f);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public RcVec3f(float x, float y, float z)

View File

@ -1,20 +0,0 @@
using System;
namespace DotRecast.Core
{
public struct RcAnonymousDisposable : IDisposable
{
private Action _dispose;
public RcAnonymousDisposable(Action dispose)
{
_dispose = dispose;
}
public void Dispose()
{
_dispose?.Invoke();
_dispose = null;
}
}
}

View File

@ -1,9 +1,24 @@
using System;
using System.Runtime.CompilerServices;
namespace DotRecast.Core
{
public static class RcArrayUtils
public static class RcArrays
{
// Type Safe Copy
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void Copy<T>(T[] sourceArray, long sourceIndex, T[] destinationArray, long destinationIndex, long length)
{
Array.Copy(sourceArray, sourceIndex, destinationArray, destinationIndex, length);
}
// Type Safe Copy
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void Copy<T>(T[] sourceArray, T[] destinationArray, long length)
{
Array.Copy(sourceArray, destinationArray, length);
}
public static T[] CopyOf<T>(T[] source, int startIdx, int length)
{
var deatArr = new T[length];

View File

@ -1,8 +1,9 @@
using System.Threading;
using System;
using System.Threading;
namespace DotRecast.Core
{
public class RcAtomicLong
public class RcAtomicLong : IComparable<RcAtomicLong>
{
private long _location;
@ -15,6 +16,11 @@ namespace DotRecast.Core
_location = location;
}
public int CompareTo(RcAtomicLong other)
{
return Read().CompareTo(other.Read());
}
public long IncrementAndGet()
{
return Interlocked.Increment(ref _location);

View File

@ -25,21 +25,33 @@ using System.Threading;
namespace DotRecast.Core
{
public class RcTelemetry
/// Provides an interface for optional logging and performance tracking of the Recast
/// build process.
///
/// This class does not provide logging or timer functionality on its
/// own. Both must be provided by a concrete implementation
/// by overriding the protected member functions. Also, this class does not
/// provide an interface for extracting log messages. (Only adding them.)
/// So concrete implementations must provide one.
///
/// If no logging or timers are required, just pass an instance of this
/// class through the Recast build process.
///
/// @ingroup recast
public class RcContext
{
private readonly ThreadLocal<Dictionary<string, RcAtomicLong>> _timerStart;
private readonly ConcurrentDictionary<string, RcAtomicLong> _timerAccum;
public RcTelemetry()
public RcContext()
{
_timerStart = new ThreadLocal<Dictionary<string, RcAtomicLong>>(() => new Dictionary<string, RcAtomicLong>());
_timerAccum = new ConcurrentDictionary<string, RcAtomicLong>();
}
public IDisposable ScopedTimer(RcTimerLabel label)
public RcScopedTimer ScopedTimer(RcTimerLabel label)
{
StartTimer(label);
return new RcAnonymousDisposable(() => StopTimer(label));
return new RcScopedTimer(this, label);
}
public void StartTimer(RcTimerLabel label)

View File

@ -1,10 +1,26 @@
namespace DotRecast.Core
using System.Runtime.CompilerServices;
namespace DotRecast.Core
{
public static class RcHashCodes
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static int CombineHashCodes(int h1, int h2)
{
return (((h1 << 5) + h1) ^ h2);
}
// From Thomas Wang, https://gist.github.com/badboy/6267743
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static uint WangHash(uint a)
{
a = (~a) + (a << 18); // a = (a << 18) - a - 1;
a = a ^ (a >> 31);
a = a * 21; // a = (a + (a << 2)) + (a << 4);
a = a ^ (a >> 11);
a = a + (a << 6);
a = a ^ (a >> 22);
return (uint)a;
}
}
}

View File

@ -0,0 +1,34 @@
using System;
using System.Diagnostics;
using System.Runtime.InteropServices;
namespace DotRecast.Core
{
public static class RcProcess
{
public static void OpenUrl(string url)
{
try
{
// OS에 따라 다른 명령 실행
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{
var psi = new ProcessStartInfo("cmd", $"/c start {url}") { CreateNoWindow = true };
Process.Start(psi);
}
else if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
{
Process.Start("open", url);
}
else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
{
Process.Start("xdg-open", url);
}
}
catch (Exception ex)
{
Console.WriteLine($"Error opening web browser: {ex.Message}");
}
}
}
}

View File

@ -20,5 +20,10 @@ namespace DotRecast.Core
{
return (float)_r.NextDouble();
}
public int NextInt32()
{
return _r.Next();
}
}
}

View File

@ -0,0 +1,23 @@
using System;
namespace DotRecast.Core
{
public readonly struct RcScopedTimer : IDisposable
{
private readonly RcContext _context;
private readonly RcTimerLabel _label;
internal RcScopedTimer(RcContext context, RcTimerLabel label)
{
_context = context;
_label = label;
_context.StartTimer(_label);
}
public void Dispose()
{
_context.StopTimer(_label);
}
}
}

View File

@ -0,0 +1,42 @@
using System;
using System.Runtime.CompilerServices;
using DotRecast.Core.Collections;
namespace DotRecast.Core
{
public static class RcThrowHelper
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void ThrowExceptionIfIndexOutOfRange(int index, int size)
{
if (0 > index || index >= size)
{
throw new IndexOutOfRangeException($"Index {index} is out of range - size({size})");
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void StackOverflow()
{
var array_128_512_1 = RcStackArray128<RcStackArray512<float>>.Empty; // 128 * 512 = 65536
var array_128_512_2 = RcStackArray128<RcStackArray512<float>>.Empty; // 128 * 512 = 65536
var array_32_512_1 = RcStackArray32<RcStackArray512<float>>.Empty; // 32 * 512 = 16384
var array_16_512_1 = RcStackArray16<RcStackArray512<float>>.Empty; // 16 * 512 = 8192
var array_8_512_1 = RcStackArray8<RcStackArray512<float>>.Empty; // 8 * 512 = 4196
var array_4_256_1 = RcStackArray4<RcStackArray256<float>>.Empty; // 4 * 256 = 1024
var array_4_64_1 = RcStackArray4<RcStackArray64<float>>.Empty; // 4 * 64 = 256
//
var array_2_8_1 = RcStackArray2<RcStackArray8<float>>.Empty; // 2 * 8 = 16
var array_2_4_1 = RcStackArray2<RcStackArray2<float>>.Empty; // 2 * 2 = 4
float f1 = 0.0f; // 1
//float f2 = 0.0f; // my system stack overflow!
}
}
}

View File

@ -1,15 +1,16 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>netstandard2.1;net6.0;net7.0</TargetFrameworks>
<TargetFrameworks>netstandard2.1;net6.0;net7.0;net8.0</TargetFrameworks>
<PackageId>DotRecast.Detour.Crowd</PackageId>
<PackageReadmeFile>README.md</PackageReadmeFile>
<Authors>ikpil</Authors>
<Description>DotRecast - a port of Recast Detour, navigation mesh toolset for games, Unity3D, servers, C#</Description>
<Description>DotRecast - a port of Recast Detour, Industry-standard navigation mesh toolset for .NET, C#, Unity3D, games, servers</Description>
<RepositoryType>git</RepositoryType>
<PackageProjectUrl>https://github.com/ikpil/DotRecast</PackageProjectUrl>
<RepositoryUrl>https://github.com/ikpil/DotRecast</RepositoryUrl>
<PackageTags>game gamedev ai csharp server unity navigation game-development unity3d pathfinding pathfinder recast detour navmesh crowd-simulation recastnavigation</PackageTags>
<PackageReleaseNotes>https://github.com/ikpil/DotRecast/blob/main/CHANGELOG.md</PackageReleaseNotes>
</PropertyGroup>
<ItemGroup>

View File

@ -20,163 +20,155 @@ freely, subject to the following restrictions:
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using DotRecast.Core;
using DotRecast.Core.Collections;
using DotRecast.Core.Numerics;
namespace DotRecast.Detour.Crowd
{
///////////////////////////////////////////////////////////////////////////
// This section contains detailed documentation for members that don't have
// a source file. It reduces clutter in the main section of the header.
/**
* Members in this module implement local steering and dynamic avoidance features.
*
* The crowd is the big beast of the navigation features. It not only handles a lot of the path management for you, but
* also local steering and dynamic avoidance between members of the crowd. I.e. It can keep your agents from running
* into each other.
*
* Main class: Crowd
*
* The #dtNavMeshQuery and #dtPathCorridor classes provide perfectly good, easy to use path planning features. But in
* the end they only give you points that your navigation client should be moving toward. When it comes to deciding
* things like agent velocity and steering to avoid other agents, that is up to you to implement. Unless, of course, you
* decide to use Crowd.
*
* Basically, you add an agent to the crowd, providing various configuration settings such as maximum speed and
* acceleration. You also provide a local target to move toward. The crowd manager then provides, with every update, the
* new agent position and velocity for the frame. The movement will be constrained to the navigation mesh, and steering
* will be applied to ensure agents managed by the crowd do not collide with each other.
*
* This is very powerful feature set. But it comes with limitations.
*
* The biggest limitation is that you must give control of the agent's position completely over to the crowd manager.
* You can update things like maximum speed and acceleration. But in order for the crowd manager to do its thing, it
* can't allow you to constantly be giving it overrides to position and velocity. So you give up direct control of the
* agent's movement. It belongs to the crowd.
*
* The second biggest limitation revolves around the fact that the crowd manager deals with local planning. So the
* agent's target should never be more than 256 polygons away from its current position. If it is, you risk your agent
* failing to reach its target. So you may still need to do long distance planning and provide the crowd manager with
* intermediate targets.
*
* Other significant limitations:
*
* - All agents using the crowd manager will use the same #dtQueryFilter. - Crowd management is relatively expensive.
* The maximum agents under crowd management at any one time is between 20 and 30. A good place to start is a maximum of
* 25 agents for 0.5ms per frame.
*
* @note This is a summary list of members. Use the index or search feature to find minor members.
*
* @struct dtCrowdAgentParams
* @see CrowdAgent, Crowd::AddAgent(), Crowd::UpdateAgentParameters()
*
* @var dtCrowdAgentParams::obstacleAvoidanceType
* @par
*
* #dtCrowd permits agents to use different avoidance configurations. This value is the index of the
* #dtObstacleAvoidanceParams within the crowd.
*
* @see dtObstacleAvoidanceParams, dtCrowd::SetObstacleAvoidanceParams(), dtCrowd::GetObstacleAvoidanceParams()
*
* @var dtCrowdAgentParams::collisionQueryRange
* @par
*
* Collision elements include other agents and navigation mesh boundaries.
*
* This value is often based on the agent radius and/or maximum speed. E.g. radius * 8
*
* @var dtCrowdAgentParams::pathOptimizationRange
* @par
*
* Only applicable if #updateFlags includes the #DT_CROWD_OPTIMIZE_VIS flag.
*
* This value is often based on the agent radius. E.g. radius * 30
*
* @see dtPathCorridor::OptimizePathVisibility()
*
* @var dtCrowdAgentParams::separationWeight
* @par
*
* A higher value will result in agents trying to stay farther away from each other at the cost of more difficult
* steering in tight spaces.
*
*/
/**
* This is the core class of the refs crowd module. See the refs crowd documentation for a summary of the crowd
* features. A common method for setting up the crowd is as follows: -# Allocate the crowd -# Set the avoidance
* configurations using #SetObstacleAvoidanceParams(). -# Add agents using #AddAgent() and make an initial movement
* request using #RequestMoveTarget(). A common process for managing the crowd is as follows: -# Call #Update() to allow
* the crowd to manage its agents. -# Retrieve agent information using #GetActiveAgents(). -# Make movement requests
* using #RequestMoveTarget() when movement goal changes. -# Repeat every frame. Some agent configuration settings can
* be updated using #UpdateAgentParameters(). But the crowd owns the agent position. So it is not possible to update an
* active agent's position. If agent position must be fed back into the crowd, the agent must be removed and re-added.
* Notes: - Path related information is available for newly added agents only after an #Update() has been performed. -
* Agent objects are kept in a pool and re-used. So it is important when using agent objects to check the value of
* #dtCrowdAgent::active to determine if the agent is actually in use or not. - This class is meant to provide 'local'
* movement. There is a limit of 256 polygons in the path corridor. So it is not meant to provide automatic pathfinding
* services over long distances.
*
* @see DtAllocCrowd(), DtFreeCrowd(), Init(), dtCrowdAgent
*/
@defgroup crowd Crowd
Members in this module implement local steering and dynamic avoidance features.
The crowd is the big beast of the navigation features. It not only handles a
lot of the path management for you, but also local steering and dynamic
avoidance between members of the crowd. I.e. It can keep your agents from
running into each other.
Main class: #dtCrowd
The #dtNavMeshQuery and #dtPathCorridor classes provide perfectly good, easy
to use path planning features. But in the end they only give you points that
your navigation client should be moving toward. When it comes to deciding things
like agent velocity and steering to avoid other agents, that is up to you to
implement. Unless, of course, you decide to use #dtCrowd.
Basically, you add an agent to the crowd, providing various configuration
settings such as maximum speed and acceleration. You also provide a local
target to more toward. The crowd manager then provides, with every update, the
new agent position and velocity for the frame. The movement will be
constrained to the navigation mesh, and steering will be applied to ensure
agents managed by the crowd do not collide with each other.
This is very powerful feature set. But it comes with limitations.
The biggest limitation is that you must give control of the agent's position
completely over to the crowd manager. You can update things like maximum speed
and acceleration. But in order for the crowd manager to do its thing, it can't
allow you to constantly be giving it overrides to position and velocity. So
you give up direct control of the agent's movement. It belongs to the crowd.
The second biggest limitation revolves around the fact that the crowd manager
deals with local planning. So the agent's target should never be more than
256 polygons aways from its current position. If it is, you risk
your agent failing to reach its target. So you may still need to do long
distance planning and provide the crowd manager with intermediate targets.
Other significant limitations:
- All agents using the crowd manager will use the same #dtQueryFilter.
- Crowd management is relatively expensive. The maximum agents under crowd
management at any one time is between 20 and 30. A good place to start
is a maximum of 25 agents for 0.5ms per frame.
@note This is a summary list of members. Use the index or search
feature to find minor members.
@struct dtCrowdAgentParams
@see dtCrowdAgent, dtCrowd::addAgent(), dtCrowd::updateAgentParameters()
@var dtCrowdAgentParams::obstacleAvoidanceType
@par
#dtCrowd permits agents to use different avoidance configurations. This value
is the index of the #dtObstacleAvoidanceParams within the crowd.
@see dtObstacleAvoidanceParams, dtCrowd::setObstacleAvoidanceParams(),
dtCrowd::getObstacleAvoidanceParams()
@var dtCrowdAgentParams::collisionQueryRange
@par
Collision elements include other agents and navigation mesh boundaries.
This value is often based on the agent radius and/or maximum speed. E.g. radius * 8
@var dtCrowdAgentParams::pathOptimizationRange
@par
Only applicable if #updateFlags includes the #DT_CROWD_OPTIMIZE_VIS flag.
This value is often based on the agent radius. E.g. radius * 30
@see dtPathCorridor::optimizePathVisibility()
@var dtCrowdAgentParams::separationWeight
@par
A higher value will result in agents trying to stay farther away from each other at
the cost of more difficult steering in tight spaces.
*/
/// Provides local steering behaviors for a group of agents.
/// @ingroup crowd
public class DtCrowd
{
/// The maximum number of corners a crowd agent will look ahead in the path.
/// This value is used for sizing the crowd agent corner buffers.
/// Due to the behavior of the crowd manager, the actual number of useful
/// corners will be one less than this number.
/// @ingroup crowd
public const int DT_CROWDAGENT_MAX_CORNERS = 4;
/// The maximum number of crowd avoidance configurations supported by the
/// crowd manager.
/// @ingroup crowd
/// @see dtObstacleAvoidanceParams, dtCrowd::SetObstacleAvoidanceParams(), dtCrowd::GetObstacleAvoidanceParams(),
/// dtCrowdAgentParams::obstacleAvoidanceType
public const int DT_CROWD_MAX_OBSTAVOIDANCE_PARAMS = 8;
/// The maximum number of query filter types supported by the crowd manager.
/// @ingroup crowd
/// @see dtQueryFilter, dtCrowd::GetFilter() dtCrowd::GetEditableFilter(),
/// dtCrowdAgentParams::queryFilterType
public const int DT_CROWD_MAX_QUERY_FILTER_TYPE = 16;
private readonly RcAtomicInteger _agentId = new RcAtomicInteger();
private readonly List<DtCrowdAgent> _agents;
private readonly DtPathQueue _pathQ;
private readonly DtObstacleAvoidanceParams[] _obstacleQueryParams = new DtObstacleAvoidanceParams[DT_CROWD_MAX_OBSTAVOIDANCE_PARAMS];
private readonly DtObstacleAvoidanceParams[] _obstacleQueryParams;
private readonly DtObstacleAvoidanceQuery _obstacleQuery;
private DtProximityGrid _grid;
private readonly RcVec3f _ext = new RcVec3f();
private readonly IDtQueryFilter[] _filters = new IDtQueryFilter[DT_CROWD_MAX_QUERY_FILTER_TYPE];
private DtNavMeshQuery _navQuery;
private DtNavMesh _navMesh;
private int _maxPathResult;
private readonly RcVec3f _agentPlacementHalfExtents;
private readonly IDtQueryFilter[] _filters;
private readonly DtCrowdConfig _config;
private readonly DtCrowdTelemetry _telemetry = new DtCrowdTelemetry();
private int _velocitySampleCount;
public DtCrowd(DtCrowdConfig config, DtNavMesh nav) :
this(config, nav, i => new DtQueryDefaultFilter())
private DtNavMeshQuery _navQuery;
private DtNavMesh _navMesh;
private readonly DtCrowdTelemetry _telemetry = new DtCrowdTelemetry();
public DtCrowd(DtCrowdConfig config, DtNavMesh nav) : this(config, nav, i => new DtQueryDefaultFilter())
{
}
public DtCrowd(DtCrowdConfig config, DtNavMesh nav, Func<int, IDtQueryFilter> queryFilterFactory)
{
_config = config;
_ext = new RcVec3f(config.maxAgentRadius * 2.0f, config.maxAgentRadius * 1.5f, config.maxAgentRadius * 2.0f);
_agentPlacementHalfExtents = new RcVec3f(config.maxAgentRadius * 2.0f, config.maxAgentRadius * 1.5f, config.maxAgentRadius * 2.0f);
_obstacleQuery = new DtObstacleAvoidanceQuery(config.maxObstacleAvoidanceCircles, config.maxObstacleAvoidanceSegments);
for (int i = 0; i < DT_CROWD_MAX_QUERY_FILTER_TYPE; i++)
_filters = new IDtQueryFilter[DtCrowdConst.DT_CROWD_MAX_QUERY_FILTER_TYPE];
for (int i = 0; i < DtCrowdConst.DT_CROWD_MAX_QUERY_FILTER_TYPE; i++)
{
_filters[i] = queryFilterFactory.Invoke(i);
}
// Init obstacle query option.
for (int i = 0; i < DT_CROWD_MAX_OBSTAVOIDANCE_PARAMS; ++i)
_obstacleQueryParams = new DtObstacleAvoidanceParams[DtCrowdConst.DT_CROWD_MAX_OBSTAVOIDANCE_PARAMS];
for (int i = 0; i < DtCrowdConst.DT_CROWD_MAX_OBSTAVOIDANCE_PARAMS; ++i)
{
_obstacleQueryParams[i] = new DtObstacleAvoidanceParams();
}
// Allocate temp buffer for merging paths.
_maxPathResult = DtCrowdConst.MAX_PATH_RESULT;
_pathQ = new DtPathQueue(config);
_agents = new List<DtCrowdAgent>();
@ -206,7 +198,7 @@ namespace DotRecast.Detour.Crowd
/// @param[in] option The new configuration.
public void SetObstacleAvoidanceParams(int idx, DtObstacleAvoidanceParams option)
{
if (idx >= 0 && idx < DT_CROWD_MAX_OBSTAVOIDANCE_PARAMS)
if (idx >= 0 && idx < DtCrowdConst.DT_CROWD_MAX_OBSTAVOIDANCE_PARAMS)
{
_obstacleQueryParams[idx] = new DtObstacleAvoidanceParams(option);
}
@ -218,7 +210,7 @@ namespace DotRecast.Detour.Crowd
/// @return The requested configuration.
public DtObstacleAvoidanceParams GetObstacleAvoidanceParams(int idx)
{
if (idx >= 0 && idx < DT_CROWD_MAX_OBSTAVOIDANCE_PARAMS)
if (idx >= 0 && idx < DtCrowdConst.DT_CROWD_MAX_OBSTAVOIDANCE_PARAMS)
{
return _obstacleQueryParams[idx];
}
@ -234,23 +226,24 @@ namespace DotRecast.Detour.Crowd
agent.option = option;
}
/**
* Adds a new agent to the crowd.
*
* @param pos
* The requested position of the agent. [(x, y, z)]
* @param params
* The configuration of the agent.
* @return The newly created agent object
*/
/// @par
///
/// The agent's position will be constrained to the surface of the navigation mesh.
/// Adds a new agent to the crowd.
/// @param[in] pos The requested position of the agent. [(x, y, z)]
/// @param[in] params The configuration of the agent.
/// @return The index of the agent in the agent pool. Or -1 if the agent could not be added.
public DtCrowdAgent AddAgent(RcVec3f pos, DtCrowdAgentParams option)
{
DtCrowdAgent ag = new DtCrowdAgent(_agentId.GetAndIncrement());
int idx = _agentId.GetAndIncrement();
DtCrowdAgent ag = new DtCrowdAgent(idx);
ag.corridor.Init(_maxPathResult);
_agents.Add(ag);
UpdateAgentParameters(ag, option);
// Find nearest position on navmesh and place the agent there.
var status = _navQuery.FindNearestPoly(pos, _ext, _filters[ag.option.queryFilterType], out var refs, out var nearestPt, out var _);
var status = _navQuery.FindNearestPoly(pos, _agentPlacementHalfExtents, _filters[ag.option.queryFilterType], out var refs, out var nearestPt, out var _);
if (status.Failed())
{
nearestPt = pos;
@ -370,12 +363,12 @@ namespace DotRecast.Detour.Crowd
public RcVec3f GetQueryExtents()
{
return _ext;
return _agentPlacementHalfExtents;
}
public IDtQueryFilter GetFilter(int i)
{
return i >= 0 && i < DT_CROWD_MAX_QUERY_FILTER_TYPE ? _filters[i] : null;
return i >= 0 && i < DtCrowdConst.DT_CROWD_MAX_QUERY_FILTER_TYPE ? _filters[i] : null;
}
public DtProximityGrid GetGrid()
@ -451,8 +444,9 @@ namespace DotRecast.Detour.Crowd
{
using var timer = _telemetry.ScopedTimer(DtCrowdTimerLabel.CheckPathValidity);
foreach (DtCrowdAgent ag in agents)
for (var i = 0; i < agents.Count; i++)
{
var ag = agents[i];
if (ag.state != DtCrowdAgentState.DT_CROWDAGENT_STATE_WALKING)
{
continue;
@ -470,7 +464,7 @@ namespace DotRecast.Detour.Crowd
{
// Current location is not valid, try to reposition.
// TODO: this can snap agents, how to handle that?
_navQuery.FindNearestPoly(ag.npos, _ext, _filters[ag.option.queryFilterType], out agentRef, out var nearestPt, out var _);
_navQuery.FindNearestPoly(ag.npos, _agentPlacementHalfExtents, _filters[ag.option.queryFilterType], out agentRef, out var nearestPt, out var _);
agentPos = nearestPt;
if (agentRef == 0)
@ -510,7 +504,7 @@ namespace DotRecast.Detour.Crowd
if (!_navQuery.IsValidPolyRef(ag.targetRef, _filters[ag.option.queryFilterType]))
{
// Current target is not valid, try to reposition.
_navQuery.FindNearestPoly(ag.targetPos, _ext, _filters[ag.option.queryFilterType], out ag.targetRef, out var nearestPt, out var _);
_navQuery.FindNearestPoly(ag.targetPos, _agentPlacementHalfExtents, _filters[ag.option.queryFilterType], out ag.targetRef, out var nearestPt, out var _);
ag.targetPos = nearestPt;
replan = true;
}
@ -534,8 +528,7 @@ namespace DotRecast.Detour.Crowd
replan = true;
}
// If the end of the path is near and it is not the requested
// location, replan.
// If the end of the path is near and it is not the requested location, replan.
if (ag.targetState == DtMoveRequestState.DT_CROWDAGENT_TARGET_VALID)
{
if (ag.targetReplanTime > _config.targetReplanDelay && ag.corridor.GetPathCount() < _config.checkLookAhead
@ -564,8 +557,9 @@ namespace DotRecast.Detour.Crowd
// Fire off new requests.
List<long> reqPath = new List<long>();
foreach (DtCrowdAgent ag in agents)
for (var i = 0; i < agents.Count; i++)
{
var ag = agents[i];
if (ag.state == DtCrowdAgentState.DT_CROWDAGENT_STATE_INVALID)
{
continue;
@ -677,8 +671,9 @@ namespace DotRecast.Detour.Crowd
}
// Process path results.
foreach (DtCrowdAgent ag in agents)
for (var i = 0; i < agents.Count; i++)
{
var ag = agents[i];
if (ag.targetState == DtMoveRequestState.DT_CROWDAGENT_TARGET_NONE
|| ag.targetState == DtMoveRequestState.DT_CROWDAGENT_TARGET_VELOCITY)
{
@ -816,8 +811,9 @@ namespace DotRecast.Detour.Crowd
RcSortedQueue<DtCrowdAgent> queue = new RcSortedQueue<DtCrowdAgent>((a1, a2) => a2.topologyOptTime.CompareTo(a1.topologyOptTime));
foreach (DtCrowdAgent ag in agents)
for (var i = 0; i < agents.Count; i++)
{
var ag = agents[i];
if (ag.state != DtCrowdAgentState.DT_CROWDAGENT_STATE_WALKING)
{
continue;
@ -855,8 +851,9 @@ namespace DotRecast.Detour.Crowd
_grid = new DtProximityGrid(_config.maxAgentRadius * 3);
foreach (DtCrowdAgent ag in agents)
for (var i = 0; i < agents.Count; i++)
{
var ag = agents[i];
RcVec3f p = ag.npos;
float r = ag.option.radius;
_grid.AddItem(ag, p.X - r, p.Z - r, p.X + r, p.Z + r);
@ -867,8 +864,9 @@ namespace DotRecast.Detour.Crowd
{
using var timer = _telemetry.ScopedTimer(DtCrowdTimerLabel.BuildNeighbours);
foreach (DtCrowdAgent ag in agents)
for (var i = 0; i < agents.Count; i++)
{
var ag = agents[i];
if (ag.state != DtCrowdAgentState.DT_CROWDAGENT_STATE_WALKING)
{
continue;
@ -894,10 +892,12 @@ namespace DotRecast.Detour.Crowd
{
result.Clear();
var proxAgents = new HashSet<DtCrowdAgent>();
int nids = grid.QueryItems(pos.X - range, pos.Z - range, pos.X + range, pos.Z + range, ref proxAgents);
foreach (DtCrowdAgent ag in proxAgents)
int MAX_NEIS = 32;
var ids = new DtCrowdAgent[MAX_NEIS];
int nids = grid.QueryItems(pos.X - range, pos.Z - range, pos.X + range, pos.Z + range, ids, ids.Length);
for (int i = 0; i < nids; ++i)
{
var ag = ids[i];
if (ag == skip)
{
continue;
@ -929,8 +929,9 @@ namespace DotRecast.Detour.Crowd
using var timer = _telemetry.ScopedTimer(DtCrowdTimerLabel.FindCorners);
DtCrowdAgent debugAgent = debug != null ? debug.agent : null;
foreach (DtCrowdAgent ag in agents)
for (var i = 0; i < agents.Count; i++)
{
var ag = agents[i];
if (ag.state != DtCrowdAgentState.DT_CROWDAGENT_STATE_WALKING)
{
continue;
@ -943,7 +944,7 @@ namespace DotRecast.Detour.Crowd
}
// Find corners for steering
ag.corridor.FindCorners(ref ag.corners, DT_CROWDAGENT_MAX_CORNERS, _navQuery, _filters[ag.option.queryFilterType]);
ag.corridor.FindCorners(ref ag.corners, DtCrowdConst.DT_CROWDAGENT_MAX_CORNERS, _navQuery, _filters[ag.option.queryFilterType]);
// Check to see if the corner after the next corner is directly visible,
// and short cut to there.
@ -976,8 +977,9 @@ namespace DotRecast.Detour.Crowd
{
using var timer = _telemetry.ScopedTimer(DtCrowdTimerLabel.TriggerOffMeshConnections);
foreach (DtCrowdAgent ag in agents)
for (var i = 0; i < agents.Count; i++)
{
var ag = agents[i];
if (ag.state != DtCrowdAgentState.DT_CROWDAGENT_STATE_WALKING)
{
continue;
@ -1024,8 +1026,9 @@ namespace DotRecast.Detour.Crowd
{
using var timer = _telemetry.ScopedTimer(DtCrowdTimerLabel.CalculateSteering);
foreach (DtCrowdAgent ag in agents)
for (var i = 0; i < agents.Count; i++)
{
var ag = agents[i];
if (ag.state != DtCrowdAgentState.DT_CROWDAGENT_STATE_WALKING)
{
continue;
@ -1122,8 +1125,9 @@ namespace DotRecast.Detour.Crowd
using var timer = _telemetry.ScopedTimer(DtCrowdTimerLabel.PlanVelocity);
DtCrowdAgent debugAgent = debug != null ? debug.agent : null;
foreach (DtCrowdAgent ag in agents)
for (var i = 0; i < agents.Count; i++)
{
var ag = agents[i];
if (ag.state != DtCrowdAgentState.DT_CROWDAGENT_STATE_WALKING)
{
continue;
@ -1145,7 +1149,7 @@ namespace DotRecast.Detour.Crowd
{
RcVec3f[] s = ag.boundary.GetSegment(j);
RcVec3f s3 = s[1];
//Array.Copy(s, 3, s3, 0, 3);
//RcArrays.Copy(s, 3, s3, 0, 3);
if (DtUtils.TriArea2D(ag.npos, s[0], s3) < 0.0f)
{
continue;
@ -1191,8 +1195,9 @@ namespace DotRecast.Detour.Crowd
{
using var timer = _telemetry.ScopedTimer(DtCrowdTimerLabel.Integrate);
foreach (DtCrowdAgent ag in agents)
for (var i = 0; i < agents.Count; i++)
{
var ag = agents[i];
if (ag.state != DtCrowdAgentState.DT_CROWDAGENT_STATE_WALKING)
{
continue;
@ -1208,8 +1213,9 @@ namespace DotRecast.Detour.Crowd
for (int iter = 0; iter < 4; ++iter)
{
foreach (DtCrowdAgent ag in agents)
for (var i = 0; i < agents.Count; i++)
{
var ag = agents[i];
long idx0 = ag.idx;
if (ag.state != DtCrowdAgentState.DT_CROWDAGENT_STATE_WALKING)
{
@ -1266,8 +1272,9 @@ namespace DotRecast.Detour.Crowd
}
}
foreach (DtCrowdAgent ag in agents)
for (var i = 0; i < agents.Count; i++)
{
var ag = agents[i];
if (ag.state != DtCrowdAgentState.DT_CROWDAGENT_STATE_WALKING)
{
continue;
@ -1282,8 +1289,9 @@ namespace DotRecast.Detour.Crowd
{
using var timer = _telemetry.ScopedTimer(DtCrowdTimerLabel.MoveAgents);
foreach (DtCrowdAgent ag in agents)
for (var i = 0; i < agents.Count; i++)
{
var ag = agents[i];
if (ag.state != DtCrowdAgentState.DT_CROWDAGENT_STATE_WALKING)
{
continue;
@ -1308,8 +1316,9 @@ namespace DotRecast.Detour.Crowd
{
using var timer = _telemetry.ScopedTimer(DtCrowdTimerLabel.UpdateOffMeshConnections);
foreach (DtCrowdAgent ag in agents)
for (var i = 0; i < agents.Count; i++)
{
var ag = agents[i];
DtCrowdAgentAnimation anim = ag.animation;
if (!anim.active)
{

View File

@ -33,8 +33,7 @@ namespace DotRecast.Detour.Crowd
/// The type of mesh polygon the agent is traversing. (See: #CrowdAgentState)
public DtCrowdAgentState state;
/// True if the agent has valid path (targetState == DT_CROWDAGENT_TARGET_VALID) and the path does not lead to the
/// requested position, else false.
/// True if the agent has valid path (targetState == DT_CROWDAGENT_TARGET_VALID) and the path does not lead to the requested position, else false.
public bool partial;
/// The path corridor the agent is using.
@ -52,51 +51,24 @@ namespace DotRecast.Detour.Crowd
/// The desired speed.
public float desiredSpeed;
public RcVec3f npos = new RcVec3f();
public RcVec3f npos = new RcVec3f(); // < The current agent position. [(x, y, z)]
public RcVec3f disp = new RcVec3f(); // < A temporary value used to accumulate agent displacement during iterative collision resolution. [(x, y, z)]
public RcVec3f dvel = new RcVec3f(); // < The desired velocity of the agent. Based on the current path, calculated from scratch each frame. [(x, y, z)]
public RcVec3f nvel = new RcVec3f(); // < The desired velocity adjusted by obstacle avoidance, calculated from scratch each frame. [(x, y, z)]
public RcVec3f vel = new RcVec3f(); // < The actual velocity of the agent. The change from nvel -> vel is constrained by max acceleration. [(x, y, z)]
/// < The current agent position. [(x, y, z)]
public RcVec3f disp = new RcVec3f();
/// < A temporary value used to accumulate agent displacement during iterative
/// collision resolution. [(x, y, z)]
public RcVec3f dvel = new RcVec3f();
/// < The desired velocity of the agent. Based on the current path, calculated
/// from
/// scratch each frame. [(x, y, z)]
public RcVec3f nvel = new RcVec3f();
/// < The desired velocity adjusted by obstacle avoidance, calculated from scratch each
/// frame. [(x, y, z)]
public RcVec3f vel = new RcVec3f();
/// < The actual velocity of the agent. The change from nvel -> vel is
/// constrained by max acceleration. [(x, y, z)]
/// The agent's configuration parameters.
public DtCrowdAgentParams option;
/// The local path corridor corners for the agent.
public List<DtStraightPath> corners = new List<DtStraightPath>();
public DtMoveRequestState targetState;
/// < State of the movement request.
public long targetRef;
/// < Target polyref of the movement request.
public RcVec3f targetPos = new RcVec3f();
/// < Target position of the movement request (or velocity in case of
/// DT_CROWDAGENT_TARGET_VELOCITY).
public DtPathQueryResult targetPathQueryResult;
/// < Path finder query
public bool targetReplan;
/// < Flag indicating that the current path is being replanned.
public float targetReplanTime;
/// <Time since the agent's target was replanned.
public DtMoveRequestState targetState; // < State of the movement request.
public long targetRef; // < Target polyref of the movement request.
public RcVec3f targetPos = new RcVec3f(); // < Target position of the movement request (or velocity in case of DT_CROWDAGENT_TARGET_VELOCITY).
public DtPathQueryResult targetPathQueryResult; // < Path finder query
public bool targetReplan; // < Flag indicating that the current path is being replanned.
public float targetReplanTime; // <Time since the agent's target was replanned.
public float targetReplanWaitTime;
public DtCrowdAgentAnimation animation;

View File

@ -23,52 +23,16 @@ namespace DotRecast.Detour.Crowd
{
public readonly float maxAgentRadius;
/**
* Max number of path requests in the queue
*/
public int pathQueueSize = 32;
/**
* Max number of sliced path finding iterations executed per update (used to handle longer paths and replans)
*/
public int maxFindPathIterations = 100;
/**
* Max number of sliced path finding iterations executed per agent to find the initial path to target
*/
public int maxTargetFindPathIterations = 20;
/**
* Min time between topology optimizations (in seconds)
*/
public float topologyOptimizationTimeThreshold = 0.5f;
/**
* The number of polygons from the beginning of the corridor to check to ensure path validity
*/
public int checkLookAhead = 10;
/**
* Min time between target re-planning (in seconds)
*/
public float targetReplanDelay = 1.0f;
/**
* Max number of sliced path finding iterations executed per topology optimization per agent
*/
public int maxTopologyOptimizationIterations = 32;
public int pathQueueSize = 32; // Max number of path requests in the queue
public int maxFindPathIterations = 100; // Max number of sliced path finding iterations executed per update (used to handle longer paths and replans)
public int maxTargetFindPathIterations = 20; // Max number of sliced path finding iterations executed per agent to find the initial path to target
public float topologyOptimizationTimeThreshold = 0.5f; // Min time between topology optimizations (in seconds)
public int checkLookAhead = 10; // The number of polygons from the beginning of the corridor to check to ensure path validity
public float targetReplanDelay = 1.0f; // Min time between target re-planning (in seconds)
public int maxTopologyOptimizationIterations = 32; // Max number of sliced path finding iterations executed per topology optimization per agent
public float collisionResolveFactor = 0.7f;
/**
* Max number of neighbour agents to consider in obstacle avoidance processing
*/
public int maxObstacleAvoidanceCircles = 6;
/**
* Max number of neighbour segments to consider in obstacle avoidance processing
*/
public int maxObstacleAvoidanceSegments = 8;
public int maxObstacleAvoidanceCircles = 6; // Max number of neighbour agents to consider in obstacle avoidance processing
public int maxObstacleAvoidanceSegments = 8; // Max number of neighbour segments to consider in obstacle avoidance processing
public DtCrowdConfig(float maxAgentRadius)
{

View File

@ -0,0 +1,35 @@
namespace DotRecast.Detour.Crowd
{
public static class DtCrowdConst
{
/// The maximum number of neighbors that a crowd agent can take into account
/// for steering decisions.
/// @ingroup crowd
public const int DT_CROWDAGENT_MAX_NEIGHBOURS = 6;
/// The maximum number of corners a crowd agent will look ahead in the path.
/// This value is used for sizing the crowd agent corner buffers.
/// Due to the behavior of the crowd manager, the actual number of useful
/// corners will be one less than this number.
/// @ingroup crowd
public const int DT_CROWDAGENT_MAX_CORNERS = 4;
/// The maximum number of crowd avoidance configurations supported by the
/// crowd manager.
/// @ingroup crowd
/// @see dtObstacleAvoidanceParams, dtCrowd::SetObstacleAvoidanceParams(), dtCrowd::GetObstacleAvoidanceParams(),
/// dtCrowdAgentParams::obstacleAvoidanceType
public const int DT_CROWD_MAX_OBSTAVOIDANCE_PARAMS = 8;
/// The maximum number of query filter types supported by the crowd manager.
/// @ingroup crowd
/// @see dtQueryFilter, dtCrowd::GetFilter() dtCrowd::GetEditableFilter(),
/// dtCrowdAgentParams::queryFilterType
public const int DT_CROWD_MAX_QUERY_FILTER_TYPE = 16;
public const int MAX_ITERS_PER_UPDATE = 100;
public const int MAX_PATHQUEUE_NODES = 4096;
public const int MAX_COMMON_NODES = 512;
public const int MAX_PATH_RESULT = 256;
}
}

View File

@ -0,0 +1,23 @@
using System;
namespace DotRecast.Detour.Crowd
{
internal readonly struct DtCrowdScopedTimer : IDisposable
{
private readonly DtCrowdTimerLabel _label;
private readonly DtCrowdTelemetry _telemetry;
internal DtCrowdScopedTimer(DtCrowdTelemetry telemetry, DtCrowdTimerLabel label)
{
_telemetry = telemetry;
_label = label;
_telemetry.Start(_label);
}
public void Dispose()
{
_telemetry.Stop(_label);
}
}
}

View File

@ -19,11 +19,9 @@ freely, subject to the following restrictions:
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Reflection.Emit;
using DotRecast.Core;
using DotRecast.Core.Numerics;
using DotRecast.Core.Buffers;
namespace DotRecast.Detour.Crowd
{
@ -34,7 +32,7 @@ namespace DotRecast.Detour.Crowd
private float _maxTimeToFindPath;
private readonly Dictionary<DtCrowdTimerLabel, long> _executionTimings = new Dictionary<DtCrowdTimerLabel, long>();
private readonly Dictionary<DtCrowdTimerLabel, List<long>> _executionTimingSamples = new Dictionary<DtCrowdTimerLabel, List<long>>();
private readonly Dictionary<DtCrowdTimerLabel, RcCyclicBuffer<long>> _executionTimingSamples = new Dictionary<DtCrowdTimerLabel, RcCyclicBuffer<long>>();
public float MaxTimeToEnqueueRequest()
{
@ -71,33 +69,27 @@ namespace DotRecast.Detour.Crowd
_maxTimeToFindPath = Math.Max(_maxTimeToFindPath, time);
}
public IDisposable ScopedTimer(DtCrowdTimerLabel label)
internal DtCrowdScopedTimer ScopedTimer(DtCrowdTimerLabel label)
{
Start(label);
return new RcAnonymousDisposable(() => Stop(label));
return new DtCrowdScopedTimer(this, label);
}
private void Start(DtCrowdTimerLabel name)
internal void Start(DtCrowdTimerLabel name)
{
_executionTimings.Add(name, RcFrequency.Ticks);
}
private void Stop(DtCrowdTimerLabel name)
internal void Stop(DtCrowdTimerLabel name)
{
long duration = RcFrequency.Ticks - _executionTimings[name];
if (!_executionTimingSamples.TryGetValue(name, out var s))
if (!_executionTimingSamples.TryGetValue(name, out var cb))
{
s = new List<long>();
_executionTimingSamples.Add(name, s);
cb = new RcCyclicBuffer<long>(TIMING_SAMPLES);
_executionTimingSamples.Add(name, cb);
}
if (s.Count == TIMING_SAMPLES)
{
s.RemoveAt(0);
}
s.Add(duration);
_executionTimings[name] = (long)s.Average();
cb.PushBack(duration);
_executionTimings[name] = (long)cb.Average();
}
}
}

View File

@ -25,8 +25,6 @@ using DotRecast.Core.Numerics;
namespace DotRecast.Detour.Crowd
{
public class DtLocalBoundary
{
public const int MAX_LOCAL_SEGS = 8;
@ -54,7 +52,7 @@ namespace DotRecast.Detour.Crowd
DtSegment seg = new DtSegment();
seg.s[0] = s.vmin;
seg.s[1] = s.vmax;
//Array.Copy(s, seg.s, 6);
//RcArrays.Copy(s, seg.s, 6);
seg.d = dist;
if (0 == m_segs.Count)
{

View File

@ -26,95 +26,114 @@ using DotRecast.Core.Numerics;
namespace DotRecast.Detour.Crowd
{
/**
* Represents a dynamic polygon corridor used to plan agent movement.
*
* The corridor is loaded with a path, usually obtained from a #NavMeshQuery::FindPath() query. The corridor is then
* used to plan local movement, with the corridor automatically updating as needed to deal with inaccurate agent
* locomotion.
*
* Example of a common use case:
*
* -# Construct the corridor object and call -# Obtain a path from a #dtNavMeshQuery object. -# Use #Reset() to set the
* agent's current position. (At the beginning of the path.) -# Use #SetCorridor() to load the path and target. -# Use
* #FindCorners() to plan movement. (This handles dynamic path straightening.) -# Use #MovePosition() to feed agent
* movement back into the corridor. (The corridor will automatically adjust as needed.) -# If the target is moving, use
* #MoveTargetPosition() to update the end of the corridor. (The corridor will automatically adjust as needed.) -#
* Repeat the previous 3 steps to continue to move the agent.
*
* The corridor position and target are always constrained to the navigation mesh.
*
* One of the difficulties in maintaining a path is that floating point errors, locomotion inaccuracies, and/or local
* steering can result in the agent crossing the boundary of the path corridor, temporarily invalidating the path. This
* class uses local mesh queries to detect and update the corridor as needed to handle these types of issues.
*
* The fact that local mesh queries are used to move the position and target locations results in two beahviors that
* need to be considered:
*
* Every time a move function is used there is a chance that the path will become non-optimial. Basically, the further
* the target is moved from its original location, and the further the position is moved outside the original corridor,
* the more likely the path will become non-optimal. This issue can be addressed by periodically running the
* #OptimizePathTopology() and #OptimizePathVisibility() methods.
*
* All local mesh queries have distance limitations. (Review the #dtNavMeshQuery methods for details.) So the most
* accurate use case is to move the position and target in small increments. If a large increment is used, then the
* corridor may not be able to accurately find the new location. Because of this limiation, if a position is moved in a
* large increment, then compare the desired and resulting polygon references. If the two do not match, then path
* replanning may be needed. E.g. If you move the target, check #GetLastPoly() to see if it is the expected polygon.
*
*/
/// Represents a dynamic polygon corridor used to plan agent movement.
/// @ingroup crowd, detour
public class DtPathCorridor
{
private RcVec3f m_pos = new RcVec3f();
private RcVec3f m_target = new RcVec3f();
private List<long> m_path;
private RcVec3f m_pos;
private RcVec3f m_target;
private List<long> m_path;
private int m_maxPath;
/**
* Allocates the corridor's path buffer.
*/
@class dtPathCorridor
@par
The corridor is loaded with a path, usually obtained from a #dtNavMeshQuery::findPath() query. The corridor
is then used to plan local movement, with the corridor automatically updating as needed to deal with inaccurate
agent locomotion.
Example of a common use case:
-# Construct the corridor object and call #init() to allocate its path buffer.
-# Obtain a path from a #dtNavMeshQuery object.
-# Use #reset() to set the agent's current position. (At the beginning of the path.)
-# Use #setCorridor() to load the path and target.
-# Use #findCorners() to plan movement. (This handles dynamic path straightening.)
-# Use #movePosition() to feed agent movement back into the corridor. (The corridor will automatically adjust as needed.)
-# If the target is moving, use #moveTargetPosition() to update the end of the corridor.
(The corridor will automatically adjust as needed.)
-# Repeat the previous 3 steps to continue to move the agent.
The corridor position and target are always constrained to the navigation mesh.
One of the difficulties in maintaining a path is that floating point errors, locomotion inaccuracies, and/or local
steering can result in the agent crossing the boundary of the path corridor, temporarily invalidating the path.
This class uses local mesh queries to detect and update the corridor as needed to handle these types of issues.
The fact that local mesh queries are used to move the position and target locations results in two beahviors that
need to be considered:
Every time a move function is used there is a chance that the path will become non-optimial. Basically, the further
the target is moved from its original location, and the further the position is moved outside the original corridor,
the more likely the path will become non-optimal. This issue can be addressed by periodically running the
#optimizePathTopology() and #optimizePathVisibility() methods.
All local mesh queries have distance limitations. (Review the #dtNavMeshQuery methods for details.) So the most accurate
use case is to move the position and target in small increments. If a large increment is used, then the corridor
may not be able to accurately find the new location. Because of this limiation, if a position is moved in a large
increment, then compare the desired and resulting polygon references. If the two do not match, then path replanning
may be needed. E.g. If you move the target, check #getLastPoly() to see if it is the expected polygon.
*/
public DtPathCorridor()
{
m_path = new List<long>();
}
/**
* Resets the path corridor to the specified position.
*
* @param ref
* The polygon reference containing the position.
* @param pos
* The new position in the corridor. [(x, y, z)]
*/
/// @par
///
/// @warning Cannot be called more than once.
/// Allocates the corridor's path buffer.
/// @param[in] maxPath The maximum path size the corridor can handle.
/// @return True if the initialization succeeded.
public bool Init(int maxPath)
{
m_path = new List<long>();
m_maxPath = maxPath;
return true;
}
/// @par
///
/// Essentially, the corridor is set of one polygon in size with the target
/// equal to the position.
///
/// Resets the path corridor to the specified position.
/// @param[in] ref The polygon reference containing the position.
/// @param[in] pos The new position in the corridor. [(x, y, z)]
public void Reset(long refs, RcVec3f pos)
{
m_path.Clear();
m_path.Add(refs);
m_pos = pos;
m_target = pos;
m_path.Clear();
m_path.Add(refs);
}
private static readonly float MIN_TARGET_DIST = RcMath.Sqr(0.01f);
/**
* Finds the corners in the corridor from the position toward the target. (The straightened path.)
*
* This is the function used to plan local movement within the corridor. One or more corners can be detected in
* order to plan movement. It performs essentially the same function as #dtNavMeshQuery::findStraightPath.
*
* Due to internal optimizations, the maximum number of corners returned will be (@p maxCorners - 1) For example: If
* the buffers are sized to hold 10 corners, the function will never return more than 9 corners. So if 10 corners
* are needed, the buffers should be sized for 11 corners.
*
* If the target is within range, it will be the last corner and have a polygon reference id of zero.
*
* @param filter
*
* @param[in] navquery The query object used to build the corridor.
* @return Corners
*/
@par
This is the function used to plan local movement within the corridor. One or more corners can be
detected in order to plan movement. It performs essentially the same function as #dtNavMeshQuery::findStraightPath.
Due to internal optimizations, the maximum number of corners returned will be (@p maxCorners - 1)
For example: If the buffers are sized to hold 10 corners, the function will never return more than 9 corners.
So if 10 corners are needed, the buffers should be sized for 11 corners.
If the target is within range, it will be the last corner and have a polygon reference id of zero.
*/
/// Finds the corners in the corridor from the position toward the target. (The straightened path.)
/// @param[out] cornerVerts The corner vertices. [(x, y, z) * cornerCount] [Size: <= maxCorners]
/// @param[out] cornerFlags The flag for each corner. [(flag) * cornerCount] [Size: <= maxCorners]
/// @param[out] cornerPolys The polygon reference for each corner. [(polyRef) * cornerCount]
/// [Size: <= @p maxCorners]
/// @param[in] maxCorners The maximum number of corners the buffers can hold.
/// @param[in] navquery The query object used to build the corridor.
/// @param[in] filter The filter to apply to the operation.
/// @return The number of corners returned in the corner buffers. [0 <= value <= @p maxCorners]
public int FindCorners(ref List<DtStraightPath> corners, int maxCorners, DtNavMeshQuery navquery, IDtQueryFilter filter)
{
const float MIN_TARGET_DIST = 0.01f;
var result = navquery.FindStraightPath(m_pos, m_target, m_path, ref corners, maxCorners, 0);
if (result.Succeeded())
{
@ -123,7 +142,7 @@ namespace DotRecast.Detour.Crowd
foreach (DtStraightPath spi in corners)
{
if ((spi.flags & DtStraightPathFlags.DT_STRAIGHTPATH_OFFMESH_CONNECTION) != 0
|| RcVecUtils.Dist2DSqr(spi.pos, m_pos) > MIN_TARGET_DIST)
|| RcVecUtils.Dist2DSqr(spi.pos, m_pos) > RcMath.Sqr(MIN_TARGET_DIST))
{
break;
}
@ -150,32 +169,28 @@ namespace DotRecast.Detour.Crowd
}
/**
* Attempts to optimize the path if the specified point is visible from the current position.
*
* Inaccurate locomotion or dynamic obstacle avoidance can force the agent position significantly outside the
* original corridor. Over time this can result in the formation of a non-optimal corridor. Non-optimal paths can
* also form near the corners of tiles.
*
* This function uses an efficient local visibility search to try to optimize the corridor between the current
* position and @p next.
*
* The corridor will change only if @p next is visible from the current position and moving directly toward the
* point is better than following the existing path.
*
* The more inaccurate the agent movement, the more beneficial this function becomes. Simply adjust the frequency of
* the call to match the needs to the agent.
*
* This function is not suitable for long distance searches.
*
* @param next
* The point to search toward. [(x, y, z])
* @param pathOptimizationRange
* The maximum range to search. [Limit: > 0]
* @param navquery
* The query object used to build the corridor.
* @param filter
* The filter to apply to the operation.
*/
@par
Inaccurate locomotion or dynamic obstacle avoidance can force the argent position significantly outside the
original corridor. Over time this can result in the formation of a non-optimal corridor. Non-optimal paths can
also form near the corners of tiles.
This function uses an efficient local visibility search to try to optimize the corridor
between the current position and @p next.
The corridor will change only if @p next is visible from the current position and moving directly toward the point
is better than following the existing path.
The more inaccurate the agent movement, the more beneficial this function becomes. Simply adjust the frequency
of the call to match the needs to the agent.
This function is not suitable for long distance searches.
*/
/// Attempts to optimize the path if the specified point is visible from the current position.
/// @param[in] next The point to search toward. [(x, y, z])
/// @param[in] pathOptimizationRange The maximum range to search. [Limit: > 0]
/// @param[in] navquery The query object used to build the corridor.
/// @param[in] filter The filter to apply to the operation.
public void OptimizePathVisibility(RcVec3f next, float pathOptimizationRange, DtNavMeshQuery navquery, IDtQueryFilter filter)
{
// Clamp the ray to max distance.
@ -187,40 +202,37 @@ namespace DotRecast.Detour.Crowd
return;
}
// Overshoot a little. This helps to optimize open fields in tiled
// meshes.
// Overshoot a little. This helps to optimize open fields in tiled meshes.
dist = Math.Min(dist + 0.01f, pathOptimizationRange);
// Adjust ray length.
var delta = RcVec3f.Subtract(next, m_pos);
RcVec3f goal = RcVecUtils.Mad(m_pos, delta, pathOptimizationRange / dist);
var status = navquery.Raycast(m_path[0], m_pos, goal, filter, 0, 0, out var rayHit);
var res = new List<long>();
var status = navquery.Raycast(m_path[0], m_pos, goal, filter, out var t, out var norm, ref res);
if (status.Succeeded())
{
if (rayHit.path.Count > 1 && rayHit.t > 0.99f)
if (res.Count > 1 && t > 0.99f)
{
m_path = DtPathUtils.MergeCorridorStartShortcut(m_path, rayHit.path);
m_path = DtPathUtils.MergeCorridorStartShortcut(m_path, m_path.Count, m_maxPath, res);
}
}
}
/**
* Attempts to optimize the path using a local area search. (Partial replanning.)
*
* Inaccurate locomotion or dynamic obstacle avoidance can force the agent position significantly outside the
* original corridor. Over time this can result in the formation of a non-optimal corridor. This function will use a
* local area path search to try to re-optimize the corridor.
*
* The more inaccurate the agent movement, the more beneficial this function becomes. Simply adjust the frequency of
* the call to match the needs to the agent.
*
* @param navquery
* The query object used to build the corridor.
* @param filter
* The filter to apply to the operation.
*
*/
@par
Inaccurate locomotion or dynamic obstacle avoidance can force the agent position significantly outside the
original corridor. Over time this can result in the formation of a non-optimal corridor. This function will use a
local area path search to try to re-optimize the corridor.
The more inaccurate the agent movement, the more beneficial this function becomes. Simply adjust the frequency of
the call to match the needs to the agent.
*/
/// Attempts to optimize the path using a local area search. (Partial replanning.)
/// @param[in] navquery The query object used to build the corridor.
/// @param[in] filter The filter to apply to the operation.
public bool OptimizePathTopology(DtNavMeshQuery navquery, IDtQueryFilter filter, int maxIterations)
{
if (m_path.Count < 3)
@ -229,13 +241,13 @@ namespace DotRecast.Detour.Crowd
}
var res = new List<long>();
navquery.InitSlicedFindPath(m_path[0], m_path[m_path.Count - 1], m_pos, m_target, filter, 0);
navquery.InitSlicedFindPath(m_path[0], m_path[^1], m_pos, m_target, filter, 0);
navquery.UpdateSlicedFindPath(maxIterations, out var _);
var status = navquery.FinalizeSlicedFindPathPartial(m_path, ref res);
if (status.Succeeded() && res.Count > 0)
{
m_path = DtPathUtils.MergeCorridorStartShortcut(m_path, res);
m_path = DtPathUtils.MergeCorridorStartShortcut(m_path, m_path.Count, m_maxPath, res);
return true;
}
@ -277,28 +289,26 @@ namespace DotRecast.Detour.Crowd
}
/**
* Moves the position from the current location to the desired location, adjusting the corridor as needed to reflect
* the change.
*
* Behavior:
*
* - The movement is constrained to the surface of the navigation mesh. - The corridor is automatically adjusted
* (shorted or lengthened) in order to remain valid. - The new position will be located in the adjusted corridor's
* first polygon.
*
* The expected use case is that the desired position will be 'near' the current corridor. What is considered 'near'
* depends on local polygon density, query search extents, etc.
*
* The resulting position will differ from the desired position if the desired position is not on the navigation
* mesh, or it can't be reached using a local search.
*
* @param npos
* The desired new position. [(x, y, z)]
* @param navquery
* The query object used to build the corridor.
* @param filter
* The filter to apply to the operation.
*/
@par
Behavior:
- The movement is constrained to the surface of the navigation mesh.
- The corridor is automatically adjusted (shorted or lengthened) in order to remain valid.
- The new position will be located in the adjusted corridor's first polygon.
The expected use case is that the desired position will be 'near' the current corridor. What is considered 'near'
depends on local polygon density, query search half extents, etc.
The resulting position will differ from the desired position if the desired position is not on the navigation mesh,
or it can't be reached using a local search.
*/
/// Moves the position from the current location to the desired location, adjusting the corridor
/// as needed to reflect the change.
/// @param[in] npos The desired new position. [(x, y, z)]
/// @param[in] navquery The query object used to build the corridor.
/// @param[in] filter The filter to apply to the operation.
/// @return Returns true if move succeeded.
public bool MovePosition(RcVec3f npos, DtNavMeshQuery navquery, IDtQueryFilter filter)
{
// Move along navmesh and update new position.
@ -306,7 +316,7 @@ namespace DotRecast.Detour.Crowd
var status = navquery.MoveAlongSurface(m_path[0], m_pos, npos, filter, out var result, ref visited);
if (status.Succeeded())
{
m_path = DtPathUtils.MergeCorridorStartMoved(m_path, visited);
m_path = DtPathUtils.MergeCorridorStartMoved(m_path, m_path.Count, m_maxPath, visited);
// Adjust the position to stay on top of the navmesh.
m_pos = result;
@ -323,30 +333,32 @@ namespace DotRecast.Detour.Crowd
}
/**
* Moves the target from the curent location to the desired location, adjusting the corridor as needed to reflect
* the change. Behavior: - The movement is constrained to the surface of the navigation mesh. - The corridor is
* automatically adjusted (shorted or lengthened) in order to remain valid. - The new target will be located in the
* adjusted corridor's last polygon.
*
* The expected use case is that the desired target will be 'near' the current corridor. What is considered 'near'
* depends on local polygon density, query search extents, etc. The resulting target will differ from the desired
* target if the desired target is not on the navigation mesh, or it can't be reached using a local search.
*
* @param npos
* The desired new target position. [(x, y, z)]
* @param navquery
* The query object used to build the corridor.
* @param filter
* The filter to apply to the operation.
*/
@par
Behavior:
- The movement is constrained to the surface of the navigation mesh.
- The corridor is automatically adjusted (shorted or lengthened) in order to remain valid.
- The new target will be located in the adjusted corridor's last polygon.
The expected use case is that the desired target will be 'near' the current corridor. What is considered 'near' depends on local polygon density, query search half extents, etc.
The resulting target will differ from the desired target if the desired target is not on the navigation mesh, or it can't be reached using a local search.
*/
/// Moves the target from the curent location to the desired location, adjusting the corridor
/// as needed to reflect the change.
/// @param[in] npos The desired new target position. [(x, y, z)]
/// @param[in] navquery The query object used to build the corridor.
/// @param[in] filter The filter to apply to the operation.
/// @return Returns true if move succeeded.
public bool MoveTargetPosition(RcVec3f npos, DtNavMeshQuery navquery, IDtQueryFilter filter)
{
// Move along navmesh and update new position.
var visited = new List<long>();
var status = navquery.MoveAlongSurface(m_path[m_path.Count - 1], m_target, npos, filter, out var result, ref visited);
var status = navquery.MoveAlongSurface(m_path[^1], m_target, npos, filter, out var result, ref visited);
if (status.Succeeded())
{
m_path = DtPathUtils.MergeCorridorEndMoved(m_path, visited);
m_path = DtPathUtils.MergeCorridorEndMoved(m_path, m_path.Count, m_maxPath, visited);
// TODO: should we do that?
// Adjust the position to stay on top of the navmesh.
/*
@ -360,16 +372,16 @@ namespace DotRecast.Detour.Crowd
return false;
}
/**
* Loads a new path and target into the corridor. The current corridor position is expected to be within the first
* polygon in the path. The target is expected to be in the last polygon.
*
* @warning The size of the path must not exceed the size of corridor's path buffer set during #Init().
* @param target
* The target location within the last polygon of the path. [(x, y, z)]
* @param path
* The path corridor.
*/
/// @par
///
/// The current corridor position is expected to be within the first polygon in the path. The target
/// is expected to be in the last polygon.
///
/// @warning The size of the path must not exceed the size of corridor's path buffer set during #init().
/// Loads a new path and target into the corridor.
/// @param[in] target The target location within the last polygon of the path. [(x, y, z)]
/// @param[in] path The path corridor. [(polyRef) * @p npolys]
/// @param[in] npath The number of polygons in the path.
public void SetCorridor(RcVec3f target, List<long> path)
{
m_target = target;
@ -427,19 +439,15 @@ namespace DotRecast.Detour.Crowd
return true;
}
/**
* Checks the current corridor path to see if its polygon references remain valid. The path can be invalidated if
* there are structural changes to the underlying navigation mesh, or the state of a polygon within the path changes
* resulting in it being filtered out. (E.g. An exclusion or inclusion flag changes.)
*
* @param maxLookAhead
* The number of polygons from the beginning of the corridor to search.
* @param navquery
* The query object used to build the corridor.
* @param filter
* The filter to apply to the operation.
* @return
*/
/// @par
///
/// The path can be invalidated if there are structural changes to the underlying navigation mesh, or the state of
/// a polygon within the path changes resulting in it being filtered out. (E.g. An exclusion or inclusion flag changes.)
/// Checks the current corridor path to see if its polygon references remain valid.
///
/// @param[in] maxLookAhead The number of polygons from the beginning of the corridor to search.
/// @param[in] navquery The query object used to build the corridor.
/// @param[in] filter The filter to apply to the operation.
public bool IsValid(int maxLookAhead, DtNavMeshQuery navquery, IDtQueryFilter filter)
{
// Check that all polygons still pass query filter.
@ -455,59 +463,43 @@ namespace DotRecast.Detour.Crowd
return true;
}
/**
* Gets the current position within the corridor. (In the first polygon.)
*
* @return The current position within the corridor.
*/
/// Gets the current position within the corridor. (In the first polygon.)
/// @return The current position within the corridor.
public RcVec3f GetPos()
{
return m_pos;
}
/**
* Gets the current target within the corridor. (In the last polygon.)
*
* @return The current target within the corridor.
*/
/// Gets the current target within the corridor. (In the last polygon.)
/// @return The current target within the corridor.
public RcVec3f GetTarget()
{
return m_target;
}
/**
* The polygon reference id of the first polygon in the corridor, the polygon containing the position.
*
* @return The polygon reference id of the first polygon in the corridor. (Or zero if there is no path.)
*/
/// The polygon reference id of the first polygon in the corridor, the polygon containing the position.
/// @return The polygon reference id of the first polygon in the corridor. (Or zero if there is no path.)
public long GetFirstPoly()
{
return 0 == m_path.Count ? 0 : m_path[0];
}
/**
* The polygon reference id of the last polygon in the corridor, the polygon containing the target.
*
* @return The polygon reference id of the last polygon in the corridor. (Or zero if there is no path.)
*/
/// The polygon reference id of the last polygon in the corridor, the polygon containing the target.
/// @return The polygon reference id of the last polygon in the corridor. (Or zero if there is no path.)
public long GetLastPoly()
{
return 0 == m_path.Count ? 0 : m_path[m_path.Count - 1];
}
/**
* The corridor's path.
*/
/// The corridor's path.
/// @return The corridor's path. [(polyRef) * #getPathCount()]
public List<long> GetPath()
{
return m_path;
}
/**
* The number of polygons in the current corridor path.
*
* @return The number of polygons in the current corridor path.
*/
/// The number of polygons in the current corridor path.
/// @return The number of polygons in the current corridor path.
public int GetPathCount()
{
return m_path.Count;

View File

@ -26,28 +26,29 @@ namespace DotRecast.Detour.Crowd
{
public class DtPathQueue
{
private readonly DtCrowdConfig config;
private readonly LinkedList<DtPathQuery> queue = new LinkedList<DtPathQuery>();
private readonly DtCrowdConfig m_config;
private readonly LinkedList<DtPathQuery> m_queue;
public DtPathQueue(DtCrowdConfig config)
{
this.config = config;
m_config = config;
m_queue = new LinkedList<DtPathQuery>();
}
public void Update(DtNavMesh navMesh)
{
// Update path request until there is nothing to update or up to maxIters pathfinder iterations has been
// consumed.
int iterCount = config.maxFindPathIterations;
// Update path request until there is nothing to update
// or upto maxIters pathfinder iterations has been consumed.
int iterCount = m_config.maxFindPathIterations;
while (iterCount > 0)
{
DtPathQuery q = queue.First?.Value;
DtPathQuery q = m_queue.First?.Value;
if (q == null)
{
break;
}
queue.RemoveFirst();
m_queue.RemoveFirst();
// Handle query start.
if (q.result.status.IsEmpty())
@ -70,14 +71,14 @@ namespace DotRecast.Detour.Crowd
if (!(q.result.status.Failed() || q.result.status.Succeeded()))
{
queue.AddFirst(q);
m_queue.AddFirst(q);
}
}
}
public DtPathQueryResult Request(long startRef, long endRef, RcVec3f startPos, RcVec3f endPos, IDtQueryFilter filter)
{
if (queue.Count >= config.pathQueueSize)
if (m_queue.Count >= m_config.pathQueueSize)
{
return null;
}
@ -88,7 +89,7 @@ namespace DotRecast.Detour.Crowd
q.endPos = endPos;
q.endRef = endRef;
q.filter = filter;
queue.AddLast(q);
m_queue.AddLast(q);
return q.result;
}
}

View File

@ -83,30 +83,51 @@ namespace DotRecast.Detour.Crowd
}
}
// 해당 셀 사이즈의 크기로 x ~ y 영역을 찾아, 군집 에이전트를 가져오는 코드
public int QueryItems(float minx, float miny, float maxx, float maxy, ref HashSet<DtCrowdAgent> result)
public int QueryItems(float minx, float miny, float maxx, float maxy, DtCrowdAgent[] ids, int maxIds)
{
int iminx = (int)MathF.Floor(minx * _invCellSize);
int iminy = (int)MathF.Floor(miny * _invCellSize);
int imaxx = (int)MathF.Floor(maxx * _invCellSize);
int imaxy = (int)MathF.Floor(maxy * _invCellSize);
int n = 0;
for (int y = iminy; y <= imaxy; ++y)
{
for (int x = iminx; x <= imaxx; ++x)
{
long key = CombineKey(x, y);
if (_items.TryGetValue(key, out var ids))
bool hasPool = _items.TryGetValue(key, out var pool);
if (!hasPool)
{
for (int i = 0; i < ids.Count; ++i)
continue;
}
for (int idx = 0; idx < pool.Count; ++idx)
{
var item = pool[idx];
// Check if the id exists already.
int end = n;
int i = 0;
while (i != end && ids[i] != item)
{
result.Add(ids[i]);
++i;
}
// Item not found, add it.
if (i == n)
{
ids[n++] = item;
if (n >= maxIds)
return n;
}
}
}
}
return result.Count;
return n;
}
public IEnumerable<(long, int)> GetItemCounts()

View File

@ -62,10 +62,10 @@ namespace DotRecast.Detour.Dynamic.Colliders
return bounds;
}
public override void Rasterize(RcHeightfield hf, RcTelemetry telemetry)
public override void Rasterize(RcHeightfield hf, RcContext context)
{
RcFilledVolumeRasterization.RasterizeBox(
hf, center, halfEdges, area, (int)MathF.Floor(flagMergeThreshold / hf.ch), telemetry);
hf, center, halfEdges, area, (int)MathF.Floor(flagMergeThreshold / hf.ch), context);
}
public static RcVec3f[] GetHalfEdges(RcVec3f up, RcVec3f forward, RcVec3f extent)

View File

@ -38,9 +38,9 @@ namespace DotRecast.Detour.Dynamic.Colliders
this.radius = radius;
}
public override void Rasterize(RcHeightfield hf, RcTelemetry telemetry)
public override void Rasterize(RcHeightfield hf, RcContext context)
{
RcFilledVolumeRasterization.RasterizeCapsule(hf, start, end, radius, area, (int)MathF.Floor(flagMergeThreshold / hf.ch), telemetry);
RcFilledVolumeRasterization.RasterizeCapsule(hf, start, end, radius, area, (int)MathF.Floor(flagMergeThreshold / hf.ch), context);
}
private static float[] Bounds(RcVec3f start, RcVec3f end, float radius)

View File

@ -40,6 +40,6 @@ namespace DotRecast.Detour.Dynamic.Colliders
return _bounds;
}
public abstract void Rasterize(RcHeightfield hf, RcTelemetry telemetry);
public abstract void Rasterize(RcHeightfield hf, RcContext context);
}
}

View File

@ -68,10 +68,10 @@ namespace DotRecast.Detour.Dynamic.Colliders
return bounds;
}
public void Rasterize(RcHeightfield hf, RcTelemetry telemetry)
public void Rasterize(RcHeightfield hf, RcContext context)
{
foreach (var c in colliders)
c.Rasterize(hf, telemetry);
c.Rasterize(hf, context);
}
}
}

View File

@ -42,10 +42,10 @@ namespace DotRecast.Detour.Dynamic.Colliders
this.triangles = triangles;
}
public override void Rasterize(RcHeightfield hf, RcTelemetry telemetry)
public override void Rasterize(RcHeightfield hf, RcContext context)
{
RcFilledVolumeRasterization.RasterizeConvex(hf, vertices, triangles, area,
(int)MathF.Floor(flagMergeThreshold / hf.ch), telemetry);
(int)MathF.Floor(flagMergeThreshold / hf.ch), context);
}
}
}

View File

@ -38,10 +38,10 @@ namespace DotRecast.Detour.Dynamic.Colliders
this.radius = radius;
}
public override void Rasterize(RcHeightfield hf, RcTelemetry telemetry)
public override void Rasterize(RcHeightfield hf, RcContext context)
{
RcFilledVolumeRasterization.RasterizeCylinder(hf, start, end, radius, area, (int)MathF.Floor(flagMergeThreshold / hf.ch),
telemetry);
context);
}
private static float[] Bounds(RcVec3f start, RcVec3f end, float radius)

View File

@ -36,10 +36,10 @@ namespace DotRecast.Detour.Dynamic.Colliders
this.radius = radius;
}
public override void Rasterize(RcHeightfield hf, RcTelemetry telemetry)
public override void Rasterize(RcHeightfield hf, RcContext context)
{
RcFilledVolumeRasterization.RasterizeSphere(hf, center, radius, area, (int)MathF.Floor(flagMergeThreshold / hf.ch),
telemetry);
context);
}
private static float[] Bounds(RcVec3f center, float radius)

View File

@ -58,12 +58,12 @@ namespace DotRecast.Detour.Dynamic.Colliders
return bounds;
}
public override void Rasterize(RcHeightfield hf, RcTelemetry telemetry)
public override void Rasterize(RcHeightfield hf, RcContext context)
{
for (int i = 0; i < triangles.Length; i += 3)
{
RcRasterizations.RasterizeTriangle(hf, vertices, triangles[i], triangles[i + 1], triangles[i + 2], area,
(int)MathF.Floor(flagMergeThreshold / hf.ch), telemetry);
RcRasterizations.RasterizeTriangle(context, vertices, triangles[i], triangles[i + 1], triangles[i + 2], area,
hf, (int)MathF.Floor(flagMergeThreshold / hf.ch));
}
}
}

View File

@ -25,6 +25,6 @@ namespace DotRecast.Detour.Dynamic.Colliders
public interface IDtCollider
{
float[] Bounds();
void Rasterize(RcHeightfield hf, RcTelemetry telemetry);
void Rasterize(RcHeightfield hf, RcContext context);
}
}

View File

@ -1,15 +1,16 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>netstandard2.1;net6.0;net7.0</TargetFrameworks>
<TargetFrameworks>netstandard2.1;net6.0;net7.0;net8.0</TargetFrameworks>
<PackageId>DotRecast.Detour.Dynamic</PackageId>
<PackageReadmeFile>README.md</PackageReadmeFile>
<Authors>ikpil</Authors>
<Description>DotRecast - a port of Recast Detour, navigation mesh toolset for games, Unity3D, servers, C#</Description>
<Description>DotRecast - a port of Recast Detour, Industry-standard navigation mesh toolset for .NET, C#, Unity3D, games, servers</Description>
<RepositoryType>git</RepositoryType>
<PackageProjectUrl>https://github.com/ikpil/DotRecast</PackageProjectUrl>
<RepositoryUrl>https://github.com/ikpil/DotRecast</RepositoryUrl>
<PackageTags>game gamedev ai csharp server unity navigation game-development unity3d pathfinding pathfinder recast detour navmesh crowd-simulation recastnavigation</PackageTags>
<PackageReleaseNotes>https://github.com/ikpil/DotRecast/blob/main/CHANGELOG.md</PackageReleaseNotes>
</PropertyGroup>
<ItemGroup>

View File

@ -35,7 +35,7 @@ namespace DotRecast.Detour.Dynamic
public readonly DtDynamicNavMeshConfig config;
private readonly RcBuilder builder;
private readonly Dictionary<long, DtDynamicTile> _tiles = new Dictionary<long, DtDynamicTile>();
private readonly RcTelemetry telemetry;
private readonly RcContext _context;
private readonly DtNavMeshParams navMeshParams;
private readonly BlockingCollection<IDtDaynmicTileJob> updateQueue = new BlockingCollection<IDtDaynmicTileJob>();
private readonly RcAtomicLong currentColliderId = new RcAtomicLong(0);
@ -72,7 +72,7 @@ namespace DotRecast.Detour.Dynamic
}
;
telemetry = new RcTelemetry();
_context = new RcContext();
}
public DtNavMesh NavMesh()
@ -218,7 +218,7 @@ namespace DotRecast.Detour.Dynamic
{
DtNavMeshCreateParams option = new DtNavMeshCreateParams();
option.walkableHeight = config.walkableHeight;
dirty = dirty | tile.Build(builder, config, telemetry);
dirty = dirty | tile.Build(builder, config, _context);
}
private bool UpdateNavMesh()

View File

@ -44,12 +44,12 @@ namespace DotRecast.Detour.Dynamic
this.voxelTile = voxelTile;
}
public bool Build(RcBuilder builder, DtDynamicNavMeshConfig config, RcTelemetry telemetry)
public bool Build(RcBuilder builder, DtDynamicNavMeshConfig config, RcContext context)
{
if (dirty)
{
RcHeightfield heightfield = BuildHeightfield(config, telemetry);
RcBuilderResult r = BuildRecast(builder, config, voxelTile, heightfield, telemetry);
RcHeightfield heightfield = BuildHeightfield(config, context);
RcBuilderResult r = BuildRecast(builder, config, voxelTile, heightfield, context);
DtNavMeshCreateParams option = NavMeshCreateParams(voxelTile.tileX, voxelTile.tileZ, voxelTile.cellSize,
voxelTile.cellHeight, config, r);
meshData = DtNavMeshBuilder.CreateNavMeshData(option);
@ -59,7 +59,7 @@ namespace DotRecast.Detour.Dynamic
return false;
}
private RcHeightfield BuildHeightfield(DtDynamicNavMeshConfig config, RcTelemetry telemetry)
private RcHeightfield BuildHeightfield(DtDynamicNavMeshConfig config, RcContext context)
{
ICollection<long> rasterizedColliders = checkpoint != null
? checkpoint.colliders as ICollection<long>
@ -74,7 +74,7 @@ namespace DotRecast.Detour.Dynamic
if (!rasterizedColliders.Contains(cid))
{
heightfield.bmax.Y = Math.Max(heightfield.bmax.Y, c.Bounds()[4] + heightfield.ch * 2);
c.Rasterize(heightfield, telemetry);
c.Rasterize(heightfield, context);
}
}
@ -87,7 +87,7 @@ namespace DotRecast.Detour.Dynamic
}
private RcBuilderResult BuildRecast(RcBuilder builder, DtDynamicNavMeshConfig config, DtVoxelTile vt,
RcHeightfield heightfield, RcTelemetry telemetry)
RcHeightfield heightfield, RcContext context)
{
RcConfig rcConfig = new RcConfig(
config.useTiles, config.tileSizeX, config.tileSizeZ,
@ -99,8 +99,8 @@ namespace DotRecast.Detour.Dynamic
config.maxEdgeLen, config.maxSimplificationError,
Math.Min(DtDynamicNavMesh.MAX_VERTS_PER_POLY, config.vertsPerPoly),
config.detailSampleDistance, config.detailSampleMaxError,
true, true, true, null, true);
RcBuilderResult r = builder.Build(vt.tileX, vt.tileZ, null, rcConfig, heightfield, telemetry);
true, true, true, default, true);
RcBuilderResult r = builder.Build(context, vt.tileX, vt.tileZ, null, rcConfig, heightfield);
if (config.keepIntermediateResults)
{
recastResult = r;

View File

@ -1,15 +1,16 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>netstandard2.1;net6.0;net7.0</TargetFrameworks>
<TargetFrameworks>netstandard2.1;net6.0;net7.0;net8.0</TargetFrameworks>
<PackageId>DotRecast.Detour.Extras</PackageId>
<PackageReadmeFile>README.md</PackageReadmeFile>
<Authors>ikpil</Authors>
<Description>DotRecast - a port of Recast Detour, navigation mesh toolset for games, Unity3D, servers, C#</Description>
<Description>DotRecast - a port of Recast Detour, Industry-standard navigation mesh toolset for .NET, C#, Unity3D, games, servers</Description>
<RepositoryType>git</RepositoryType>
<PackageProjectUrl>https://github.com/ikpil/DotRecast</PackageProjectUrl>
<RepositoryUrl>https://github.com/ikpil/DotRecast</RepositoryUrl>
<PackageTags>game gamedev ai csharp server unity navigation game-development unity3d pathfinding pathfinder recast detour navmesh crowd-simulation recastnavigation</PackageTags>
<PackageReleaseNotes>https://github.com/ikpil/DotRecast/blob/main/CHANGELOG.md</PackageReleaseNotes>
</PropertyGroup>
<ItemGroup>

View File

@ -64,8 +64,8 @@ namespace DotRecast.Detour.Extras.Jumplink
{
JumpLink link = new JumpLink();
links.Add(link);
link.startSamples = RcArrayUtils.CopyOf(es.start.gsamples, js.startSample, js.samples);
link.endSamples = RcArrayUtils.CopyOf(end.gsamples, js.startSample, js.samples);
link.startSamples = RcArrays.CopyOf(es.start.gsamples, js.startSample, js.samples);
link.endSamples = RcArrays.CopyOf(end.gsamples, js.startSample, js.samples);
link.start = es.start;
link.end = end;
link.trajectory = es.trajectory;

View File

@ -10,7 +10,7 @@ namespace DotRecast.Detour.Extras.Jumplink
public static readonly JumpLinkType EDGE_CLIMB_DOWN = new JumpLinkType(EDGE_CLIMB_DOWN_BIT);
public static readonly JumpLinkType EDGE_JUMP_OVER = new JumpLinkType(EDGE_JUMP_OVER_BIT);
public int Bit { get; }
public readonly int Bit;
private JumpLinkType(int bit)
{

View File

@ -9,7 +9,7 @@ namespace DotRecast.Detour.Extras.Jumplink
public JumpSegment[] Build(JumpLinkBuilderConfig acfg, EdgeSampler es)
{
int n = es.end[0].gsamples.Length;
int[][] sampleGrid = RcArrayUtils.Of<int>(n, es.end.Count);
int[][] sampleGrid = RcArrays.Of<int>(n, es.end.Count);
for (int j = 0; j < es.end.Count; j++)
{
for (int i = 0; i < n; i++)

View File

@ -34,8 +34,7 @@ namespace DotRecast.Detour.Extras
{
for (int v = 0; v < tile.data.header.vertCount; v++)
{
fw.Write("v " + tile.data.verts[v * 3] + " " + tile.data.verts[v * 3 + 1] + " "
+ tile.data.verts[v * 3 + 2] + "\n");
fw.Write("v " + tile.data.verts[v * 3] + " " + tile.data.verts[v * 3 + 1] + " " + tile.data.verts[v * 3 + 2] + "\n");
}
}
}

View File

@ -97,11 +97,11 @@ namespace DotRecast.Detour.Extras.Unity.Astar
ymax = Math.Max(ymax, verts[nodes[i].verts[0] * 3 + 1]);
ymax = Math.Max(ymax, verts[nodes[i].verts[1] * 3 + 1]);
ymax = Math.Max(ymax, verts[nodes[i].verts[2] * 3 + 1]);
detailNodes[i] = new DtPolyDetail();
detailNodes[i].vertBase = 0;
detailNodes[i].vertCount = 0;
detailNodes[i].triBase = i;
detailNodes[i].triCount = 1;
int vertBase = 0;
int vertCount = 0;
int triBase = i;
int triCount = 1;
detailNodes[i] = new DtPolyDetail(vertBase, triBase, vertCount, triCount);
detailTris[4 * i] = 0;
detailTris[4 * i + 1] = 1;
detailTris[4 * i + 2] = 2;
@ -116,8 +116,8 @@ namespace DotRecast.Detour.Extras.Unity.Astar
tiles[tileIndex].detailVerts = detailVerts;
tiles[tileIndex].detailTris = detailTris;
DtMeshHeader header = new DtMeshHeader();
header.magic = DtMeshHeader.DT_NAVMESH_MAGIC;
header.version = DtMeshHeader.DT_NAVMESH_VERSION;
header.magic = DtNavMesh.DT_NAVMESH_MAGIC;
header.version = DtNavMesh.DT_NAVMESH_VERSION;
header.x = x;
header.y = z;
header.polyCount = nodeCount;
@ -125,16 +125,16 @@ namespace DotRecast.Detour.Extras.Unity.Astar
header.detailMeshCount = nodeCount;
header.detailTriCount = nodeCount;
header.maxLinkCount = nodeCount * 3 * 2; // XXX: Needed by Recast, not needed by recast4j
header.bmin.X = meta.forcedBoundsCenter.x - 0.5f * meta.forcedBoundsSize.x
+ meta.cellSize * meta.tileSizeX * x;
header.bmin.X = meta.forcedBoundsCenter.x - 0.5f * meta.forcedBoundsSize.x +
meta.cellSize * meta.tileSizeX * x;
header.bmin.Y = ymin;
header.bmin.Z = meta.forcedBoundsCenter.z - 0.5f * meta.forcedBoundsSize.z
+ meta.cellSize * meta.tileSizeZ * z;
header.bmax.X = meta.forcedBoundsCenter.x - 0.5f * meta.forcedBoundsSize.x
+ meta.cellSize * meta.tileSizeX * (x + 1);
header.bmin.Z = meta.forcedBoundsCenter.z - 0.5f * meta.forcedBoundsSize.z +
meta.cellSize * meta.tileSizeZ * z;
header.bmax.X = meta.forcedBoundsCenter.x - 0.5f * meta.forcedBoundsSize.x +
meta.cellSize * meta.tileSizeX * (x + 1);
header.bmax.Y = ymax;
header.bmax.Z = meta.forcedBoundsCenter.z - 0.5f * meta.forcedBoundsSize.z
+ meta.cellSize * meta.tileSizeZ * (z + 1);
header.bmax.Z = meta.forcedBoundsCenter.z - 0.5f * meta.forcedBoundsSize.z +
meta.cellSize * meta.tileSizeZ * (z + 1);
header.bvQuantFactor = 1.0f / meta.cellSize;
header.offMeshBase = nodeCount;
header.walkableClimb = meta.walkableClimb;

View File

@ -25,7 +25,7 @@ namespace DotRecast.Detour.Extras.Unity.Astar
{
public const string TYPENAME_RECAST_GRAPH = "Pathfinding.RecastGraph";
public const string MIN_SUPPORTED_VERSION = "4.0.6";
public const string UPDATED_STRUCT_VERSION = "4.1.16";
public const string UPDATED_STRUCT_VERSION = "4.1.0";
public static readonly Regex VERSION_PATTERN = new Regex(@"(\d+)\.(\d+)\.(\d+)");
public string version { get; set; }
public int graphs { get; set; }

View File

@ -37,26 +37,25 @@ namespace DotRecast.Detour.Extras.Unity.Astar
if (startNode != null && endNode != null)
{
// FIXME: Optimise
startTile.polys = RcArrayUtils.CopyOf(startTile.polys, startTile.polys.Length + 1);
startTile.polys = RcArrays.CopyOf(startTile.polys, startTile.polys.Length + 1);
int poly = startTile.header.polyCount;
startTile.polys[poly] = new DtPoly(poly, 2);
startTile.polys[poly].verts[0] = startTile.header.vertCount;
startTile.polys[poly].verts[1] = startTile.header.vertCount + 1;
startTile.polys[poly].SetPolyType(DtPolyTypes.DT_POLYTYPE_OFFMESH_CONNECTION);
startTile.verts = RcArrayUtils.CopyOf(startTile.verts, startTile.verts.Length + 6);
startTile.verts = RcArrays.CopyOf(startTile.verts, startTile.verts.Length + 6);
startTile.header.polyCount++;
startTile.header.vertCount += 2;
DtOffMeshConnection connection = new DtOffMeshConnection();
connection.poly = poly;
connection.pos = new float[]
connection.pos = new RcVec3f[]
{
l.clamped1.X, l.clamped1.Y, l.clamped1.Z,
l.clamped2.X, l.clamped2.Y, l.clamped2.Z
l.clamped1, l.clamped2
};
connection.rad = 0.1f;
connection.side = startTile == endTile
? 0xFF
: DtNavMeshBuilder.ClassifyOffMeshPoint(RcVecUtils.Create(connection.pos, 3), startTile.header.bmin, startTile.header.bmax);
: DtNavMeshBuilder.ClassifyOffMeshPoint(connection.pos[1], startTile.header.bmin, startTile.header.bmax);
connection.userId = (int)l.linkID;
if (startTile.offMeshCons == null)
{
@ -64,7 +63,7 @@ namespace DotRecast.Detour.Extras.Unity.Astar
}
else
{
startTile.offMeshCons = RcArrayUtils.CopyOf(startTile.offMeshCons, startTile.offMeshCons.Length + 1);
startTile.offMeshCons = RcArrays.CopyOf(startTile.offMeshCons, startTile.offMeshCons.Length + 1);
}
startTile.offMeshCons[startTile.offMeshCons.Length - 1] = connection;

View File

@ -48,8 +48,7 @@ namespace DotRecast.Detour.Extras.Unity.Astar
int nodeCount = graphMeshData.CountNodes();
if (connections.Count != nodeCount)
{
throw new ArgumentException("Inconsistent number of nodes in data file: " + nodeCount
+ " and connecton files: " + connections.Count);
throw new ArgumentException($"Inconsistent number of nodes in data file: {nodeCount} and connection files: {connections.Count}");
}
// Build BV tree

View File

@ -1,15 +1,16 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>netstandard2.1;net6.0;net7.0</TargetFrameworks>
<TargetFrameworks>netstandard2.1;net6.0;net7.0;net8.0</TargetFrameworks>
<PackageId>DotRecast.Detour.TileCache</PackageId>
<PackageReadmeFile>README.md</PackageReadmeFile>
<Authors>ikpil</Authors>
<Description>DotRecast - a port of Recast Detour, navigation mesh toolset for games, Unity3D, servers, C#</Description>
<Description>DotRecast - a port of Recast Detour, Industry-standard navigation mesh toolset for .NET, C#, Unity3D, games, servers</Description>
<RepositoryType>git</RepositoryType>
<PackageProjectUrl>https://github.com/ikpil/DotRecast</PackageProjectUrl>
<RepositoryUrl>https://github.com/ikpil/DotRecast</RepositoryUrl>
<PackageTags>game gamedev ai csharp server unity navigation game-development unity3d pathfinding pathfinder recast detour navmesh crowd-simulation recastnavigation</PackageTags>
<PackageReleaseNotes>https://github.com/ikpil/DotRecast/blob/main/CHANGELOG.md</PackageReleaseNotes>
</PropertyGroup>
<ItemGroup>

View File

@ -24,7 +24,7 @@ using System.IO;
using DotRecast.Core;
using DotRecast.Core.Numerics;
using DotRecast.Detour.TileCache.Io;
using DotRecast.Detour.TileCache.Io.Compress;
using DotRecast.Recast;
namespace DotRecast.Detour.TileCache
@ -46,10 +46,10 @@ namespace DotRecast.Detour.TileCache
Array.Fill(layer.regs, (short)0x00FF);
int nsweeps = w;
DtLayerSweepSpan[] sweeps = new DtLayerSweepSpan[nsweeps];
RcLayerSweepSpan[] sweeps = new RcLayerSweepSpan[nsweeps];
for (int i = 0; i < sweeps.Length; i++)
{
sweeps[i] = new DtLayerSweepSpan();
sweeps[i] = new RcLayerSweepSpan();
}
// Partition walkable area into monotone regions.
@ -1275,7 +1275,7 @@ namespace DotRecast.Detour.TileCache
// Add pb
for (int i = 0; i < nb - 1; ++i)
tmp[n++] = polys[pb + (eb + 1 + i) % nb];
Array.Copy(tmp, 0, polys, pa, maxVertsPerPoly);
RcArrays.Copy(tmp, 0, polys, pa, maxVertsPerPoly);
}
private int PushFront(int v, List<int> arr)
@ -1434,7 +1434,7 @@ namespace DotRecast.Detour.TileCache
// Remove the polygon.
int p2 = (mesh.npolys - 1) * maxVertsPerPoly * 2;
Array.Copy(mesh.polys, p2, mesh.polys, p, maxVertsPerPoly);
RcArrays.Copy(mesh.polys, p2, mesh.polys, p, maxVertsPerPoly);
Array.Fill(mesh.polys, DT_TILECACHE_NULL_IDX, p + maxVertsPerPoly, maxVertsPerPoly);
mesh.areas[i] = mesh.areas[mesh.npolys - 1];
mesh.npolys--;
@ -1597,7 +1597,7 @@ namespace DotRecast.Detour.TileCache
int pa = bestPa * maxVertsPerPoly;
int pb = bestPb * maxVertsPerPoly;
MergePolys(polys, pa, pb, bestEa, bestEb, maxVertsPerPoly);
Array.Copy(polys, (npolys - 1) * maxVertsPerPoly, polys, pb, maxVertsPerPoly);
RcArrays.Copy(polys, (npolys - 1) * maxVertsPerPoly, polys, pb, maxVertsPerPoly);
pareas[bestPb] = pareas[npolys - 1];
npolys--;
}
@ -1753,7 +1753,7 @@ namespace DotRecast.Detour.TileCache
int pa = bestPa * maxVertsPerPoly;
int pb = bestPb * maxVertsPerPoly;
MergePolys(polys, pa, pb, bestEa, bestEb, maxVertsPerPoly);
Array.Copy(polys, (npolys - 1) * maxVertsPerPoly, polys, pb, maxVertsPerPoly);
RcArrays.Copy(polys, (npolys - 1) * maxVertsPerPoly, polys, pb, maxVertsPerPoly);
npolys--;
}
else

View File

@ -47,7 +47,7 @@ namespace DotRecast.Detour.TileCache.Io.Compress
{
byte[] output = new byte[FastLZ.EstimateCompressedSize(buf.Length)];
long len = FastLZ.CompressLevel(2, buf, 0, buf.Length, output);
return RcArrayUtils.CopyOf(output, len);
return RcArrays.CopyOf(output, len);
}
}
}

View File

@ -1,15 +1,16 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>netstandard2.1;net6.0;net7.0</TargetFrameworks>
<TargetFrameworks>netstandard2.1;net6.0;net7.0;net8.0</TargetFrameworks>
<PackageId>DotRecast.Detour</PackageId>
<PackageReadmeFile>README.md</PackageReadmeFile>
<Authors>ikpil</Authors>
<Description>DotRecast - a port of Recast Detour, navigation mesh toolset for games, Unity3D, servers, C#</Description>
<Description>DotRecast - a port of Recast Detour, Industry-standard navigation mesh toolset for .NET, C#, Unity3D, games, servers</Description>
<RepositoryType>git</RepositoryType>
<PackageProjectUrl>https://github.com/ikpil/DotRecast</PackageProjectUrl>
<RepositoryUrl>https://github.com/ikpil/DotRecast</RepositoryUrl>
<PackageTags>game gamedev ai csharp server unity navigation game-development unity3d pathfinding pathfinder recast detour navmesh crowd-simulation recastnavigation</PackageTags>
<PackageReleaseNotes>https://github.com/ikpil/DotRecast/blob/main/CHANGELOG.md</PackageReleaseNotes>
</PropertyGroup>
<ItemGroup>

View File

@ -18,6 +18,7 @@ freely, subject to the following restrictions:
*/
using System;
using DotRecast.Core;
using DotRecast.Core.Numerics;
namespace DotRecast.Detour
@ -171,7 +172,7 @@ namespace DotRecast.Detour
}
float[] copied = new float[ii];
Array.Copy(inters, copied, ii);
RcArrays.Copy(inters, copied, ii);
return copied;
}

View File

@ -25,23 +25,6 @@ namespace DotRecast.Detour
/** Provides high level information related to a dtMeshTile object. */
public class DtMeshHeader
{
/** A magic number used to detect compatibility of navigation tile data. */
public const int DT_NAVMESH_MAGIC = 'D' << 24 | 'N' << 16 | 'A' << 8 | 'V';
/** A version number used to detect compatibility of navigation tile data. */
public const int DT_NAVMESH_VERSION = 7;
public const int DT_NAVMESH_VERSION_RECAST4J_FIRST = 0x8807;
public const int DT_NAVMESH_VERSION_RECAST4J_NO_POLY_FIRSTLINK = 0x8808;
public const int DT_NAVMESH_VERSION_RECAST4J_32BIT_BVTREE = 0x8809;
public const int DT_NAVMESH_VERSION_RECAST4J_LAST = 0x8809;
/** A magic number used to detect the compatibility of navigation tile states. */
public const int DT_NAVMESH_STATE_MAGIC = 'D' << 24 | 'N' << 16 | 'M' << 8 | 'S';
/** A version number used to detect compatibility of navigation tile states. */
public const int DT_NAVMESH_STATE_VERSION = 1;
/** Tile magic number. (Used to identify the data format.) */
public int magic;

View File

@ -27,6 +27,23 @@ namespace DotRecast.Detour
{
public class DtNavMesh
{
/** A magic number used to detect compatibility of navigation tile data. */
public const int DT_NAVMESH_MAGIC = 'D' << 24 | 'N' << 16 | 'A' << 8 | 'V';
/** A version number used to detect compatibility of navigation tile data. */
public const int DT_NAVMESH_VERSION = 7;
public const int DT_NAVMESH_VERSION_RECAST4J_FIRST = 0x8807;
public const int DT_NAVMESH_VERSION_RECAST4J_NO_POLY_FIRSTLINK = 0x8808;
public const int DT_NAVMESH_VERSION_RECAST4J_32BIT_BVTREE = 0x8809;
public const int DT_NAVMESH_VERSION_RECAST4J_LAST = 0x8809;
/** A magic number used to detect the compatibility of navigation tile states. */
public const int DT_NAVMESH_STATE_MAGIC = 'D' << 24 | 'N' << 16 | 'M' << 8 | 'S';
/** A version number used to detect compatibility of navigation tile states. */
public const int DT_NAVMESH_STATE_VERSION = 1;
public const int DT_SALT_BITS = 16;
public const int DT_TILE_BITS = 28;
public const int DT_POLY_BITS = 20;
@ -267,7 +284,7 @@ namespace DotRecast.Detour
return DtStatus.DT_FAILURE | DtStatus.DT_INVALID_PARAM;
}
if (m_tiles[it].salt != salt || m_tiles[it].data.header == null)
if (m_tiles[it].salt != salt || m_tiles[it].data == null || m_tiles[it].data.header == null)
{
return DtStatus.DT_FAILURE | DtStatus.DT_INVALID_PARAM;
}
@ -280,7 +297,7 @@ namespace DotRecast.Detour
tile = m_tiles[it];
poly = m_tiles[it].data.polys[ip];
return DtStatus.DT_SUCCSESS;
return DtStatus.DT_SUCCESS;
}
/// @par
@ -676,6 +693,7 @@ namespace DotRecast.Detour
}
}
/// Removes external links at specified side.V
void UnconnectLinks(DtMeshTile tile, DtMeshTile target)
{
if (tile == null || target == null)
@ -718,6 +736,7 @@ namespace DotRecast.Detour
}
}
/// Builds external polygon links for a tile.
void ConnectExtLinks(DtMeshTile tile, DtMeshTile target, int side)
{
if (tile == null)
@ -803,6 +822,7 @@ namespace DotRecast.Detour
}
}
/// Builds external polygon links for a tile.
void ConnectExtOffMeshLinks(DtMeshTile tile, DtMeshTile target, int side)
{
if (tile == null)
@ -838,10 +858,7 @@ namespace DotRecast.Detour
};
// Find polygon to connect to.
RcVec3f p = new RcVec3f();
p.X = targetCon.pos[3];
p.Y = targetCon.pos[4];
p.Z = targetCon.pos[5];
RcVec3f p = targetCon.pos[1];
var refs = FindNearestPolyInTile(tile, p, ext, out var nearestPt);
if (refs == 0)
{
@ -1044,11 +1061,7 @@ namespace DotRecast.Detour
return false;
}
/**
* Builds internal polygons links for a tile.
*
* @param tile
*/
/// Builds internal polygons links for a tile.
void BaseOffMeshLinks(DtMeshTile tile)
{
if (tile == null)
@ -1072,16 +1085,16 @@ namespace DotRecast.Detour
};
// Find polygon to connect to.
var refs = FindNearestPolyInTile(tile, new RcVec3f(con.pos[0], con.pos[1], con.pos[2]), ext, out var nearestPt);
var refs = FindNearestPolyInTile(tile, con.pos[0], ext, out var nearestPt);
if (refs == 0)
{
continue;
}
float[] p = con.pos; // First vertex
RcVec3f[] p = con.pos; // First vertex
// findNearestPoly may return too optimistic results, further check
// to make sure.
if (RcMath.Sqr(nearestPt.X - p[0]) + RcMath.Sqr(nearestPt.Z - p[2]) > RcMath.Sqr(con.rad))
if (RcMath.Sqr(nearestPt.X - p[0].X) + RcMath.Sqr(nearestPt.Z - p[0].Z) > RcMath.Sqr(con.rad))
{
continue;
}
@ -1137,7 +1150,7 @@ namespace DotRecast.Detour
if (tile.data.detailMeshes != null)
{
DtPolyDetail pd = tile.data.detailMeshes[ip];
ref DtPolyDetail pd = ref tile.data.detailMeshes[ip];
for (int i = 0; i < pd.triCount; i++)
{
int ti = (pd.triBase + i) * 4;
@ -1237,7 +1250,7 @@ namespace DotRecast.Detour
int nv = poly.vertCount;
for (int i = 0; i < nv; ++i)
{
Array.Copy(tile.data.verts, poly.verts[i] * 3, verts, i * 3, 3);
RcArrays.Copy(tile.data.verts, poly.verts[i] * 3, verts, i * 3, 3);
}
if (!DtUtils.PointInPolygon(pos, verts, nv))
@ -1248,7 +1261,7 @@ namespace DotRecast.Detour
// Find height at the location.
if (tile.data.detailMeshes != null)
{
DtPolyDetail pd = tile.data.detailMeshes[ip];
ref DtPolyDetail pd = ref tile.data.detailMeshes[ip];
for (int j = 0; j < pd.triCount; ++j)
{
int t = (pd.triBase + j) * 4;
@ -1572,7 +1585,7 @@ namespace DotRecast.Detour
startPos = RcVecUtils.Create(tile.data.verts, poly.verts[idx0] * 3);
endPos = RcVecUtils.Create(tile.data.verts, poly.verts[idx1] * 3);
return DtStatus.DT_SUCCSESS;
return DtStatus.DT_SUCCESS;
}
public int GetMaxVertsPerPoly()
@ -1618,7 +1631,7 @@ namespace DotRecast.Detour
// Change flags.
poly.flags = flags;
return DtStatus.DT_SUCCSESS;
return DtStatus.DT_SUCCESS;
}
/// Gets the user defined flags for the specified polygon.
@ -1655,7 +1668,7 @@ namespace DotRecast.Detour
resultFlags = poly.flags;
return DtStatus.DT_SUCCSESS;
return DtStatus.DT_SUCCESS;
}
public DtStatus SetPolyArea(long refs, char area)
@ -1686,7 +1699,7 @@ namespace DotRecast.Detour
poly.SetArea(area);
return DtStatus.DT_SUCCSESS;
return DtStatus.DT_SUCCESS;
}
public DtStatus GetPolyArea(long refs, out int resultArea)
@ -1718,7 +1731,7 @@ namespace DotRecast.Detour
DtPoly poly = tile.data.polys[ip];
resultArea = poly.GetArea();
return DtStatus.DT_SUCCSESS;
return DtStatus.DT_SUCCESS;
}
public RcVec3f GetPolyCenter(long refs)

View File

@ -19,6 +19,7 @@ freely, subject to the following restrictions:
*/
using System;
using DotRecast.Core;
using DotRecast.Core.Numerics;
namespace DotRecast.Detour
@ -423,8 +424,8 @@ namespace DotRecast.Detour
DtOffMeshConnection[] offMeshCons = new DtOffMeshConnection[storedOffMeshConCount];
// Store header
header.magic = DtMeshHeader.DT_NAVMESH_MAGIC;
header.version = DtMeshHeader.DT_NAVMESH_VERSION;
header.magic = DtNavMesh.DT_NAVMESH_MAGIC;
header.version = DtNavMesh.DT_NAVMESH_VERSION;
header.x = option.tileX;
header.y = option.tileZ;
header.layer = option.tileLayer;
@ -468,7 +469,7 @@ namespace DotRecast.Detour
{
int linkv = i * 2 * 3;
int v = (offMeshVertsBase + n * 2) * 3;
Array.Copy(option.offMeshConVerts, linkv, navVerts, v, 6);
RcArrays.Copy(option.offMeshConVerts, linkv, navVerts, v, 6);
n++;
}
}
@ -545,26 +546,25 @@ namespace DotRecast.Detour
int vbase = 0;
for (int i = 0; i < option.polyCount; ++i)
{
DtPolyDetail dtl = new DtPolyDetail();
navDMeshes[i] = dtl;
int vb = option.detailMeshes[i * 4 + 0];
int ndv = option.detailMeshes[i * 4 + 1];
int nv = navPolys[i].vertCount;
dtl.vertBase = vbase;
dtl.vertCount = (ndv - nv);
dtl.triBase = option.detailMeshes[i * 4 + 2];
dtl.triCount = option.detailMeshes[i * 4 + 3];
int vertBase = vbase;
int vertCount = (ndv - nv);
int triBase = option.detailMeshes[i * 4 + 2];
int triCount = option.detailMeshes[i * 4 + 3];
navDMeshes[i] = new DtPolyDetail(vertBase, triBase, vertCount, triCount);
// Copy vertices except the first 'nv' verts which are equal to
// nav poly verts.
if (ndv - nv != 0)
{
Array.Copy(option.detailVerts, (vb + nv) * 3, navDVerts, vbase * 3, 3 * (ndv - nv));
RcArrays.Copy(option.detailVerts, (vb + nv) * 3, navDVerts, vbase * 3, 3 * (ndv - nv));
vbase += ndv - nv;
}
}
// Store triangles.
Array.Copy(option.detailTris, 0, navDTris, 0, 4 * option.detailTriCount);
RcArrays.Copy(option.detailTris, 0, navDTris, 0, 4 * option.detailTriCount);
}
else
{
@ -572,13 +572,12 @@ namespace DotRecast.Detour
int tbase = 0;
for (int i = 0; i < option.polyCount; ++i)
{
DtPolyDetail dtl = new DtPolyDetail();
navDMeshes[i] = dtl;
int nv = navPolys[i].vertCount;
dtl.vertBase = 0;
dtl.vertCount = 0;
dtl.triBase = tbase;
dtl.triCount = (nv - 2);
int vertBase = 0;
int vertCount = 0;
int triBase = tbase;
int triCount = (nv - 2);
navDMeshes[i] = new DtPolyDetail(vertBase, triBase, vertCount, triCount);
// Triangulate polygon (local indices).
for (int j = 2; j < nv; ++j)
{
@ -617,7 +616,11 @@ namespace DotRecast.Detour
con.poly = (offMeshPolyBase + n);
// Copy connection end-points.
int endPts = i * 2 * 3;
Array.Copy(option.offMeshConVerts, endPts, con.pos, 0, 6);
for (int j = 0; j < 2; ++j)
{
con.pos[j] = RcVecUtils.Create(option.offMeshConVerts, endPts + (j * 3));
}
con.rad = option.offMeshConRad[i];
con.flags = option.offMeshConDir[i] != 0 ? DtNavMesh.DT_OFFMESH_CON_BIDIR : 0;
con.side = offMeshConClass[i * 2 + 1];

View File

@ -31,14 +31,17 @@ namespace DotRecast.Detour
/// < Add a vertex at every polygon edge crossing.
protected readonly DtNavMesh m_nav;
protected readonly DtNodePool m_tinyNodePool;
protected readonly DtNodePool m_nodePool;
protected readonly DtNodeQueue m_openList;
protected DtQueryData m_query;
/// < Sliced query state.
public DtNavMeshQuery(DtNavMesh nav)
{
m_nav = nav;
m_tinyNodePool = new DtNodePool();
m_nodePool = new DtNodePool();
m_openList = new DtNodeQueue();
}
@ -136,10 +139,10 @@ namespace DotRecast.Detour
// Randomly pick point on polygon.
float[] verts = new float[3 * m_nav.GetMaxVertsPerPoly()];
float[] areas = new float[m_nav.GetMaxVertsPerPoly()];
Array.Copy(tile.data.verts, poly.verts[0] * 3, verts, 0, 3);
RcArrays.Copy(tile.data.verts, poly.verts[0] * 3, verts, 0, 3);
for (int j = 1; j < poly.vertCount; ++j)
{
Array.Copy(tile.data.verts, poly.verts[j] * 3, verts, j * 3, 3);
RcArrays.Copy(tile.data.verts, poly.verts[j] * 3, verts, j * 3, 3);
}
float s = frand.Next();
@ -151,25 +154,20 @@ namespace DotRecast.Detour
randomRef = polyRef;
randomPt = closest;
return DtStatus.DT_SUCCSESS;
return DtStatus.DT_SUCCESS;
}
/**
* Returns random location on navmesh within the reach of specified location. Polygons are chosen weighted by area.
* The search runs in linear related to number of polygon. The location is not exactly constrained by the circle,
* but it limits the visited polygons.
*
* @param startRef
* The reference id of the polygon where the search starts.
* @param centerPos
* The center of the search circle. [(x, y, z)]
* @param maxRadius
* @param filter
* The polygon filter to apply to the query.
* @param frand
* Function returning a random number [0..1).
* @return Random location
*/
/// Returns random location on navmesh within the reach of specified location.
/// Polygons are chosen weighted by area. The search runs in linear related to number of polygon.
/// The location is not exactly constrained by the circle, but it limits the visited polygons.
/// @param[in] startRef The reference id of the polygon where the search starts.
/// @param[in] centerPos The center of the search circle. [(x, y, z)]
/// @param[in] maxRadius The radius of the search circle. [Units: wu]
/// @param[in] filter The polygon filter to apply to the query.
/// @param[in] frand Function returning a random number [0..1).
/// @param[out] randomRef The reference id of the random location.
/// @param[out] randomPt The random location. [(x, y, z)]
/// @returns The status flags for the query.
public DtStatus FindRandomPointAroundCircle(long startRef, RcVec3f centerPos, float maxRadius,
IDtQueryFilter filter, IRcRand frand, out long randomRef, out RcVec3f randomPt)
{
@ -197,6 +195,18 @@ namespace DotRecast.Detour
return FindRandomPointAroundCircle(startRef, centerPos, maxRadius, filter, frand, DtStrictDtPolygonByCircleConstraint.Shared, out randomRef, out randomPt);
}
/// Returns random location on navmesh within the reach of specified location.
/// Polygons are chosen weighted by area. The search runs in linear related to number of polygon.
/// The location is not exactly constrained by the circle, but it limits the visited polygons.
/// @param[in] startRef The reference id of the polygon where the search starts.
/// @param[in] centerPos The center of the search circle. [(x, y, z)]
/// @param[in] maxRadius The radius of the search circle. [Units: wu]
/// @param[in] filter The polygon filter to apply to the query.
/// @param[in] frand Function returning a random number [0..1).
/// @param[in] constraint
/// @param[out] randomRef The reference id of the random location.
/// @param[out] randomPt The random location. [(x, y, z)]
/// @returns The status flags for the query.
public DtStatus FindRandomPointAroundCircle(long startRef, RcVec3f centerPos, float maxRadius,
IDtQueryFilter filter, IRcRand frand, IDtPolygonByCircleConstraint constraint,
out long randomRef, out RcVec3f randomPt)
@ -229,7 +239,7 @@ namespace DotRecast.Detour
startNode.flags = DtNodeFlags.DT_NODE_OPEN;
m_openList.Push(startNode);
DtStatus status = DtStatus.DT_SUCCSESS;
DtStatus status = DtStatus.DT_SUCCESS;
float radiusSqr = maxRadius * maxRadius;
float areaSum = 0.0f;
@ -256,7 +266,7 @@ namespace DotRecast.Detour
float[] polyVerts = new float[bestPoly.vertCount * 3];
for (int j = 0; j < bestPoly.vertCount; ++j)
{
Array.Copy(bestTile.data.verts, bestPoly.verts[j] * 3, polyVerts, j * 3, 3);
RcArrays.Copy(bestTile.data.verts, bestPoly.verts[j] * 3, polyVerts, j * 3, 3);
}
float[] constrainedVerts = constraint.Apply(polyVerts, centerPos, maxRadius);
@ -393,13 +403,13 @@ namespace DotRecast.Detour
///
/// @p pos does not have to be within the bounds of the polygon or navigation mesh.
///
/// See ClosestPointOnPolyBoundary() for a limited but faster option.
/// See closestPointOnPolyBoundary() for a limited but faster option.
///
/// Finds the closest point on the specified polygon.
/// @param[in] ref The reference id of the polygon.
/// @param[in] pos The position to check. [(x, y, z)]
/// @param[out] closest
/// @param[out] posOverPoly
/// @param[in] ref The reference id of the polygon.
/// @param[in] pos The position to check. [(x, y, z)]
/// @param[out] closest The closest point on the polygon. [(x, y, z)]
/// @param[out] posOverPoly True of the position is over the polygon.
/// @returns The status flags for the query.
public DtStatus ClosestPointOnPoly(long refs, RcVec3f pos, out RcVec3f closest, out bool posOverPoly)
{
@ -412,7 +422,7 @@ namespace DotRecast.Detour
}
m_nav.ClosestPointOnPoly(refs, pos, out closest, out posOverPoly);
return DtStatus.DT_SUCCSESS;
return DtStatus.DT_SUCCESS;
}
/// @par
@ -453,7 +463,7 @@ namespace DotRecast.Detour
int nv = poly.vertCount;
for (int i = 0; i < nv; ++i)
{
Array.Copy(tile.data.verts, poly.verts[i] * 3, verts, i * 3, 3);
RcArrays.Copy(tile.data.verts, poly.verts[i] * 3, verts, i * 3, 3);
}
if (DtUtils.DistancePtPolyEdgesSqr(pos, verts, nv, edged, edget))
@ -479,7 +489,7 @@ namespace DotRecast.Detour
closest = RcVecUtils.Lerp(verts, va, vb, edget[imin]);
}
return DtStatus.DT_SUCCSESS;
return DtStatus.DT_SUCCESS;
}
/// @par
@ -519,7 +529,7 @@ namespace DotRecast.Detour
DtUtils.DistancePtSegSqr2D(pos, v0, v1, out var t);
height = v0.Y + (v1.Y - v0.Y) * t;
return DtStatus.DT_SUCCSESS;
return DtStatus.DT_SUCCESS;
}
if (!m_nav.GetPolyHeight(tile, poly, pos, out var h))
@ -528,7 +538,7 @@ namespace DotRecast.Detour
}
height = h;
return DtStatus.DT_SUCCSESS;
return DtStatus.DT_SUCCESS;
}
/// Finds the polygon nearest to the specified center point.
@ -560,7 +570,7 @@ namespace DotRecast.Detour
nearestPt = query.NearestPt();
isOverPoly = query.OverPoly();
return DtStatus.DT_SUCCSESS;
return DtStatus.DT_SUCCESS;
}
// FIXME: (PP) duplicate?
@ -686,7 +696,7 @@ namespace DotRecast.Detour
QueryPolygonsInTile(t, bmin, bmax, filter, query);
}
return DtStatus.DT_SUCCSESS;
return DtStatus.DT_SUCCESS;
}
/**
@ -716,26 +726,29 @@ namespace DotRecast.Detour
return tiles;
}
/**
* Finds a path from the start polygon to the end polygon.
*
* If the end polygon cannot be reached through the navigation graph, the last polygon in the path will be the
* nearest the end polygon.
*
* The start and end positions are used to calculate traversal costs. (The y-values impact the result.)
*
* @param startRef
* The reference id of the start polygon.
* @param endRef
* The reference id of the end polygon.
* @param startPos
* A position within the start polygon. [(x, y, z)]
* @param endPos
* A position within the end polygon. [(x, y, z)]
* @param filter
* The polygon filter to apply to the query.
* @return Found path
*/
/// @par
///
/// If the end polygon cannot be reached through the navigation graph,
/// the last polygon in the path will be the nearest the end polygon.
///
/// If the path array is to small to hold the full result, it will be filled as
/// far as possible from the start polygon toward the end polygon.
///
/// The start and end positions are used to calculate traversal costs.
/// (The y-values impact the result.)
///
/// @name Standard Pathfinding Functions
/// @{
/// Finds a path from the start polygon to the end polygon.
/// @param[in] startRef The reference id of the start polygon.
/// @param[in] endRef The reference id of the end polygon.
/// @param[in] startPos A position within the start polygon. [(x, y, z)]
/// @param[in] endPos A position within the end polygon. [(x, y, z)]
/// @param[in] filter The polygon filter to apply to the query.
/// @param[out] path An ordered list of polygon references representing the path. (Start to end.)
/// [(polyRef) * @p pathCount]
/// @param[out] pathCount The number of polygons returned in the @p path array.
/// @param[in] maxPath The maximum number of polygons the @p path array can hold. [Limit: >= 1]
public DtStatus FindPath(long startRef, long endRef, RcVec3f startPos, RcVec3f endPos, IDtQueryFilter filter, ref List<long> path, DtFindPathOption fpo)
{
if (null == path)
@ -768,7 +781,7 @@ namespace DotRecast.Detour
if (startRef == endRef)
{
path.Add(startRef);
return DtStatus.DT_SUCCSESS;
return DtStatus.DT_SUCCESS;
}
m_nodePool.Clear();
@ -786,7 +799,8 @@ namespace DotRecast.Detour
DtNode lastBestNode = startNode;
float lastBestNodeCost = startNode.total;
DtRaycastHit rayHit = new DtRaycastHit();
rayHit.path = new List<long>();
while (!m_openList.IsEmpty())
{
// Remove node from open list and put it in closed list.
@ -886,13 +900,13 @@ namespace DotRecast.Detour
if (tryLOS)
{
var rayStatus = Raycast(parentRef, parentNode.pos, neighbourPos, filter,
DtRaycastOptions.DT_RAYCAST_USE_COSTS, grandpaRef, out var rayHit);
DtRaycastOptions.DT_RAYCAST_USE_COSTS, ref rayHit, grandpaRef);
if (rayStatus.Succeeded())
{
foundShortCut = rayHit.t >= 1.0f;
if (foundShortCut)
{
shortcut = rayHit.path;
shortcut = new List<long>(rayHit.path);
// shortcut found using raycast. Using shorter cost
// instead
cost = parentNode.cost + rayHit.pathCost;
@ -1037,8 +1051,8 @@ namespace DotRecast.Detour
if (startRef == endRef)
{
m_query.status = DtStatus.DT_SUCCSESS;
return DtStatus.DT_SUCCSESS;
m_query.status = DtStatus.DT_SUCCESS;
return DtStatus.DT_SUCCESS;
}
m_nodePool.Clear();
@ -1082,6 +1096,9 @@ namespace DotRecast.Detour
return DtStatus.DT_FAILURE;
}
var rayHit = new DtRaycastHit();
rayHit.path = new List<long>();
int iter = 0;
while (iter < maxIter && !m_openList.IsEmpty())
{
@ -1097,7 +1114,7 @@ namespace DotRecast.Detour
{
m_query.lastBestNode = bestNode;
var details = m_query.status & DtStatus.DT_STATUS_DETAIL_MASK;
m_query.status = DtStatus.DT_SUCCSESS | details;
m_query.status = DtStatus.DT_SUCCESS | details;
doneIters = iter;
return m_query.status;
}
@ -1207,13 +1224,13 @@ namespace DotRecast.Detour
if (tryLOS)
{
status = Raycast(parentRef, parentNode.pos, neighbourPos, m_query.filter,
DtRaycastOptions.DT_RAYCAST_USE_COSTS, grandpaRef, out var rayHit);
DtRaycastOptions.DT_RAYCAST_USE_COSTS, ref rayHit, grandpaRef);
if (status.Succeeded())
{
foundShortCut = rayHit.t >= 1.0f;
if (foundShortCut)
{
shortcut = rayHit.path;
shortcut = new List<long>(rayHit.path);
// shortcut found using raycast. Using shorter cost
// instead
cost = parentNode.cost + rayHit.pathCost;
@ -1295,7 +1312,7 @@ namespace DotRecast.Detour
if (m_openList.IsEmpty())
{
var details = m_query.status & DtStatus.DT_STATUS_DETAIL_MASK;
m_query.status = DtStatus.DT_SUCCSESS | details;
m_query.status = DtStatus.DT_SUCCESS | details;
}
doneIters = iter;
@ -1341,7 +1358,7 @@ namespace DotRecast.Detour
// Reset query.
m_query = new DtQueryData();
return DtStatus.DT_SUCCSESS | details;
return DtStatus.DT_SUCCESS | details;
}
/// Finalizes and returns the results of an incomplete sliced path query, returning the path to the furthest
@ -1402,7 +1419,7 @@ namespace DotRecast.Detour
// Reset query.
m_query = new DtQueryData();
return DtStatus.DT_SUCCSESS | details;
return DtStatus.DT_SUCCESS | details;
}
protected DtStatus AppendVertex(RcVec3f pos, int flags, long refs, ref List<DtStraightPath> straightPath,
@ -1424,7 +1441,7 @@ namespace DotRecast.Detour
// If reached end of path or there is no space to append more vertices, return.
if (flags == DtStraightPathFlags.DT_STRAIGHTPATH_END || straightPath.Count >= maxStraightPath)
{
return DtStatus.DT_SUCCSESS;
return DtStatus.DT_SUCCESS;
}
}
@ -1736,7 +1753,7 @@ namespace DotRecast.Detour
// Ignore status return value as we're just about to return anyway.
AppendVertex(closestEndPos, DtStraightPathFlags.DT_STRAIGHTPATH_END, 0, ref straightPath, maxStraightPath);
return DtStatus.DT_SUCCSESS | (straightPath.Count >= maxStraightPath ? DtStatus.DT_BUFFER_TOO_SMALL : DtStatus.DT_STATUS_NOTHING);
return DtStatus.DT_SUCCESS | (straightPath.Count >= maxStraightPath ? DtStatus.DT_BUFFER_TOO_SMALL : DtStatus.DT_STATUS_NOTHING);
}
/// @par
@ -1785,9 +1802,9 @@ namespace DotRecast.Detour
return DtStatus.DT_FAILURE | DtStatus.DT_INVALID_PARAM;
}
DtNodePool tinyNodePool = new DtNodePool();
m_tinyNodePool.Clear();
DtNode startNode = tinyNodePool.GetNode(startRef);
DtNode startNode = m_tinyNodePool.GetNode(startRef);
startNode.pidx = 0;
startNode.cost = 0;
startNode.total = 0;
@ -1822,7 +1839,7 @@ namespace DotRecast.Detour
int nverts = curPoly.vertCount;
for (int i = 0; i < nverts; ++i)
{
Array.Copy(curTile.data.verts, curPoly.verts[i] * 3, verts, i * 3, 3);
RcArrays.Copy(curTile.data.verts, curPoly.verts[i] * 3, verts, i * 3, 3);
}
// If target is inside the poly, stop search.
@ -1892,7 +1909,7 @@ namespace DotRecast.Detour
{
for (int k = 0; k < nneis; ++k)
{
DtNode neighbourNode = tinyNodePool.GetNode(neis[k]);
DtNode neighbourNode = m_tinyNodePool.GetNode(neis[k]);
// Skip if already visited.
if ((neighbourNode.flags & DtNodeFlags.DT_NODE_CLOSED) != 0)
{
@ -1910,7 +1927,7 @@ namespace DotRecast.Detour
}
// Mark as the node as visited and push to queue.
neighbourNode.pidx = tinyNodePool.GetNodeIdx(curNode);
neighbourNode.pidx = m_tinyNodePool.GetNodeIdx(curNode);
neighbourNode.flags |= DtNodeFlags.DT_NODE_CLOSED;
stack.AddLast(neighbourNode);
}
@ -1925,8 +1942,8 @@ namespace DotRecast.Detour
DtNode node = bestNode;
do
{
DtNode next = tinyNodePool.GetNodeAtIdx(node.pidx);
node.pidx = tinyNodePool.GetNodeIdx(prev);
DtNode next = m_tinyNodePool.GetNodeAtIdx(node.pidx);
node.pidx = m_tinyNodePool.GetNodeIdx(prev);
prev = node;
node = next;
} while (node != null);
@ -1936,13 +1953,13 @@ namespace DotRecast.Detour
do
{
visited.Add(node.id);
node = tinyNodePool.GetNodeAtIdx(node.pidx);
node = m_tinyNodePool.GetNodeAtIdx(node.pidx);
} while (node != null);
}
resultPos = bestPos;
return DtStatus.DT_SUCCSESS;
return DtStatus.DT_SUCCESS;
}
protected DtStatus GetPortalPoints(long from, long to, out RcVec3f left, out RcVec3f right, out int fromType, out int toType)
@ -2012,7 +2029,7 @@ namespace DotRecast.Detour
right.Y = fromTile.data.verts[fromPoly.verts[v] * 3 + 1];
right.Z = fromTile.data.verts[fromPoly.verts[v] * 3 + 2];
return DtStatus.DT_SUCCSESS;
return DtStatus.DT_SUCCESS;
}
}
@ -2034,7 +2051,7 @@ namespace DotRecast.Detour
right.Y = toTile.data.verts[toPoly.verts[v] * 3 + 1];
right.Z = toTile.data.verts[toPoly.verts[v] * 3 + 2];
return DtStatus.DT_SUCCSESS;
return DtStatus.DT_SUCCESS;
}
}
@ -2067,7 +2084,7 @@ namespace DotRecast.Detour
}
}
return DtStatus.DT_SUCCSESS;
return DtStatus.DT_SUCCESS;
}
protected DtStatus GetEdgeMidPoint(long from, DtPoly fromPoly, DtMeshTile fromTile, long to,
@ -2083,7 +2100,7 @@ namespace DotRecast.Detour
mid.Y = (left.Y + right.Y) * 0.5f;
mid.Z = (left.Z + right.Z) * 0.5f;
return DtStatus.DT_SUCCSESS;
return DtStatus.DT_SUCCESS;
}
protected DtStatus GetEdgeIntersectionPoint(RcVec3f fromPos, long from, DtPoly fromPoly, DtMeshTile fromTile,
@ -2103,9 +2120,62 @@ namespace DotRecast.Detour
}
pt = RcVec3f.Lerp(left, right, t);
return DtStatus.DT_SUCCSESS;
return DtStatus.DT_SUCCESS;
}
/// @par
///
/// This method is meant to be used for quick, short distance checks.
///
/// If the path array is too small to hold the result, it will be filled as
/// far as possible from the start postion toward the end position.
///
/// <b>Using the Hit Parameter (t)</b>
///
/// If the hit parameter is a very high value (FLT_MAX), then the ray has hit
/// the end position. In this case the path represents a valid corridor to the
/// end position and the value of @p hitNormal is undefined.
///
/// If the hit parameter is zero, then the start position is on the wall that
/// was hit and the value of @p hitNormal is undefined.
///
/// If 0 < t < 1.0 then the following applies:
///
/// @code
/// distanceToHitBorder = distanceToEndPosition * t
/// hitPoint = startPos + (endPos - startPos) * t
/// @endcode
///
/// <b>Use Case Restriction</b>
///
/// The raycast ignores the y-value of the end position. (2D check.) This
/// places significant limits on how it can be used. For example:
///
/// Consider a scene where there is a main floor with a second floor balcony
/// that hangs over the main floor. So the first floor mesh extends below the
/// balcony mesh. The start position is somewhere on the first floor. The end
/// position is on the balcony.
///
/// The raycast will search toward the end position along the first floor mesh.
/// If it reaches the end position's xz-coordinates it will indicate FLT_MAX
/// (no wall hit), meaning it reached the end position. This is one example of why
/// this method is meant for short distance checks.
///
public DtStatus Raycast(long startRef, RcVec3f startPos, RcVec3f endPos,
IDtQueryFilter filter,
out float t, out RcVec3f hitNormal, ref List<long> path)
{
DtRaycastHit hit = new DtRaycastHit();
hit.path = path;
DtStatus status = Raycast(startRef, startPos, endPos, filter, 0, ref hit, 0);
t = hit.t;
hitNormal = hit.hitNormal;
path = hit.path;
return status;
}
/// @par
///
@ -2159,11 +2229,10 @@ namespace DotRecast.Detour
/// @param[out] pathCount The number of visited polygons. [opt]
/// @param[in] maxPath The maximum number of polygons the @p path array can hold.
/// @returns The status flags for the query.
public DtStatus Raycast(long startRef, RcVec3f startPos, RcVec3f endPos, IDtQueryFilter filter, int options,
long prevRef, out DtRaycastHit hit)
public DtStatus Raycast(long startRef, RcVec3f startPos, RcVec3f endPos,
IDtQueryFilter filter, int options,
ref DtRaycastHit hit, long prevRef)
{
hit = null;
// Validate input
if (!m_nav.IsValidPolyRef(startRef) || !startPos.IsFinite() || !endPos.IsFinite()
|| null == filter || (prevRef != 0 && !m_nav.IsValidPolyRef(prevRef)))
@ -2171,7 +2240,9 @@ namespace DotRecast.Detour
return DtStatus.DT_FAILURE | DtStatus.DT_INVALID_PARAM;
}
hit = new DtRaycastHit();
hit.t = 0;
hit.path.Clear();
hit.pathCost = 0;
RcVec3f[] verts = new RcVec3f[m_nav.GetMaxVertsPerPoly() + 1];
@ -2179,7 +2250,8 @@ namespace DotRecast.Detour
RcVec3f lastPos = RcVec3f.Zero;
curPos = startPos;
var dir = RcVec3f.Subtract(endPos, startPos);
RcVec3f dir = RcVec3f.Subtract(endPos, startPos);
hit.hitNormal = RcVec3f.Zero;
DtMeshTile prevTile, tile, nextTile;
DtPoly prevPoly, poly, nextPoly;
@ -2210,7 +2282,7 @@ namespace DotRecast.Detour
if (!intersects)
{
// Could not hit the polygon, keep the old t and report hit.
return DtStatus.DT_SUCCSESS;
return DtStatus.DT_SUCCESS;
}
hit.hitEdgeIndex = segMax;
@ -2236,7 +2308,7 @@ namespace DotRecast.Detour
curRef, tile, poly);
}
return DtStatus.DT_SUCCSESS;
return DtStatus.DT_SUCCESS;
}
// Follow neighbours.
@ -2364,7 +2436,7 @@ namespace DotRecast.Detour
float dx = verts[b].X - verts[a].X;
float dz = verts[b].Z - verts[a].X;
hit.hitNormal = RcVec3f.Normalize(new RcVec3f(dz, 0, -dx));
return DtStatus.DT_SUCCSESS;
return DtStatus.DT_SUCCESS;
}
// No hit, advance to neighbour polygon.
@ -2376,7 +2448,7 @@ namespace DotRecast.Detour
poly = nextPoly;
}
return DtStatus.DT_SUCCSESS;
return DtStatus.DT_SUCCESS;
}
/// @par
@ -2556,7 +2628,7 @@ namespace DotRecast.Detour
}
}
return DtStatus.DT_SUCCSESS;
return DtStatus.DT_SUCCESS;
}
/// @par
@ -2739,7 +2811,7 @@ namespace DotRecast.Detour
}
}
return DtStatus.DT_SUCCSESS;
return DtStatus.DT_SUCCESS;
}
/// @par
@ -2787,9 +2859,9 @@ namespace DotRecast.Detour
resultRef.Clear();
resultParent.Clear();
DtNodePool tinyNodePool = new DtNodePool();
m_tinyNodePool.Clear();
DtNode startNode = tinyNodePool.GetNode(startRef);
DtNode startNode = m_tinyNodePool.GetNode(startRef);
startNode.pidx = 0;
startNode.id = startRef;
startNode.flags = DtNodeFlags.DT_NODE_CLOSED;
@ -2825,7 +2897,7 @@ namespace DotRecast.Detour
continue;
}
DtNode neighbourNode = tinyNodePool.GetNode(neighbourRef);
DtNode neighbourNode = m_tinyNodePool.GetNode(neighbourRef);
// Skip visited.
if ((neighbourNode.flags & DtNodeFlags.DT_NODE_CLOSED) != 0)
{
@ -2865,7 +2937,7 @@ namespace DotRecast.Detour
// Mark node visited, this is done before the overlap test so that
// we will not visit the poly again if the test fails.
neighbourNode.flags |= DtNodeFlags.DT_NODE_CLOSED;
neighbourNode.pidx = tinyNodePool.GetNodeIdx(curNode);
neighbourNode.pidx = m_tinyNodePool.GetNodeIdx(curNode);
// Check that the polygon does not collide with existing polygons.
@ -2873,7 +2945,7 @@ namespace DotRecast.Detour
int npa = neighbourPoly.vertCount;
for (int k = 0; k < npa; ++k)
{
Array.Copy(neighbourTile.data.verts, neighbourPoly.verts[k] * 3, pa, k * 3, 3);
RcArrays.Copy(neighbourTile.data.verts, neighbourPoly.verts[k] * 3, pa, k * 3, 3);
}
bool overlap = false;
@ -2904,7 +2976,7 @@ namespace DotRecast.Detour
int npb = pastPoly.vertCount;
for (int k = 0; k < npb; ++k)
{
Array.Copy(pastTile.data.verts, pastPoly.verts[k] * 3, pb, k * 3, 3);
RcArrays.Copy(pastTile.data.verts, pastPoly.verts[k] * 3, pb, k * 3, 3);
}
if (DtUtils.OverlapPolyPoly2D(pa, npa, pb, npb))
@ -2925,7 +2997,7 @@ namespace DotRecast.Detour
}
}
return DtStatus.DT_SUCCSESS;
return DtStatus.DT_SUCCESS;
}
@ -3033,8 +3105,8 @@ namespace DotRecast.Detour
var seg = new RcSegmentVert();
seg.vmin = RcVecUtils.Create(tile.data.verts, ivj);
seg.vmax = RcVecUtils.Create(tile.data.verts, ivi);
// Array.Copy(tile.data.verts, ivj, seg, 0, 3);
// Array.Copy(tile.data.verts, ivi, seg, 3, 3);
// RcArrays.Copy(tile.data.verts, ivj, seg, 0, 3);
// RcArrays.Copy(tile.data.verts, ivi, seg, 3, 3);
segmentVerts.Add(seg);
segmentRefs.Add(neiRef);
continue;
@ -3077,7 +3149,7 @@ namespace DotRecast.Detour
}
}
return DtStatus.DT_SUCCSESS;
return DtStatus.DT_SUCCESS;
}
/// @par
@ -3133,7 +3205,7 @@ namespace DotRecast.Detour
var bestvj = RcVec3f.Zero;
var bestvi = RcVec3f.Zero;
var status = DtStatus.DT_SUCCSESS;
var status = DtStatus.DT_SUCCESS;
while (!m_openList.IsEmpty())
{
DtNode bestNode = m_openList.Pop();
@ -3334,15 +3406,18 @@ namespace DotRecast.Detour
return m_nav;
}
/**
* Gets a path from the explored nodes in the previous search.
*
* @param endRef
* The reference id of the end polygon.
* @returns An ordered list of polygon references representing the path. (Start to end.)
* @remarks The result of this function depends on the state of the query object. For that reason it should only be
* used immediately after one of the two Dijkstra searches, findPolysAroundCircle or findPolysAroundShape.
*/
/// Gets a path from the explored nodes in the previous search.
/// @param[in] endRef The reference id of the end polygon.
/// @param[out] path An ordered list of polygon references representing the path. (Start to end.)
/// [(polyRef) * @p pathCount]
/// @param[out] pathCount The number of polygons returned in the @p path array.
/// @param[in] maxPath The maximum number of polygons the @p path array can hold. [Limit: >= 0]
/// @returns The status flags. Returns DT_FAILURE | DT_INVALID_PARAM if any parameter is wrong, or if
/// @p endRef was not explored in the previous search. Returns DT_SUCCESS | DT_BUFFER_TOO_SMALL
/// if @p path cannot contain the entire path. In this case it is filled to capacity with a partial path.
/// Otherwise returns DT_SUCCESS.
/// @remarks The result of this function depends on the state of the query object. For that reason it should only
/// be used immediately after one of the two Dijkstra searches, findPolysAroundCircle or findPolysAroundShape.
public DtStatus GetPathFromDijkstraSearch(long endRef, ref List<long> path)
{
if (!m_nav.IsValidPolyRef(endRef) || null == path)
@ -3352,17 +3427,13 @@ namespace DotRecast.Detour
path.Clear();
List<DtNode> nodes = m_nodePool.FindNodes(endRef);
if (nodes.Count != 1)
if (m_nodePool.FindNodes(endRef, out var endNodes) != 1
|| (endNodes[0].flags & DtNodeFlags.DT_NODE_CLOSED) == 0)
{
return DtStatus.DT_FAILURE | DtStatus.DT_INVALID_PARAM;
}
DtNode endNode = nodes[0];
if ((endNode.flags & DtNodeFlags.DT_NODE_CLOSED) == 0)
{
return DtStatus.DT_FAILURE | DtStatus.DT_INVALID_PARAM;
}
DtNode endNode = endNodes[0];
return GetPathToNode(endNode, ref path);
}
@ -3393,13 +3464,14 @@ namespace DotRecast.Detour
} while (curNode != null);
path.Reverse();
return DtStatus.DT_SUCCSESS;
return DtStatus.DT_SUCCESS;
}
/**
* The closed list is the list of polygons that were fully evaluated during the last navigation graph search. (A* or
* Dijkstra)
*/
/// @par
///
/// The closed list is the list of polygons that were fully evaluated during
/// the last navigation graph search. (A* or Dijkstra)
///
public bool IsInClosedList(long refs)
{
if (m_nodePool == null)
@ -3407,9 +3479,10 @@ namespace DotRecast.Detour
return false;
}
foreach (DtNode n in m_nodePool.FindNodes(refs))
int n = m_nodePool.FindNodes(refs, out var nodes);
for (int i = 0; i < n; ++i)
{
if ((n.flags & DtNodeFlags.DT_NODE_CLOSED) != 0)
if ((nodes[i].flags & DtNodeFlags.DT_NODE_CLOSED) != 0)
{
return true;
}

View File

@ -56,40 +56,33 @@ namespace DotRecast.Detour
continue;
}
DtPolyDetail pd = tile.data.detailMeshes[i];
ref DtPolyDetail pd = ref tile.data.detailMeshes[i];
if (pd != null)
RcVec3f[] verts = new RcVec3f[3];
for (int j = 0; j < pd.triCount; ++j)
{
RcVec3f[] verts = new RcVec3f[3];
for (int j = 0; j < pd.triCount; ++j)
int t = (pd.triBase + j) * 4;
for (int k = 0; k < 3; ++k)
{
int t = (pd.triBase + j) * 4;
for (int k = 0; k < 3; ++k)
int v = tile.data.detailTris[t + k];
if (v < p.vertCount)
{
int v = tile.data.detailTris[t + k];
if (v < p.vertCount)
{
verts[k].X = tile.data.verts[p.verts[v] * 3];
verts[k].Y = tile.data.verts[p.verts[v] * 3 + 1];
verts[k].Z = tile.data.verts[p.verts[v] * 3 + 2];
}
else
{
verts[k].X = tile.data.detailVerts[(pd.vertBase + v - p.vertCount) * 3];
verts[k].Y = tile.data.detailVerts[(pd.vertBase + v - p.vertCount) * 3 + 1];
verts[k].Z = tile.data.detailVerts[(pd.vertBase + v - p.vertCount) * 3 + 2];
}
verts[k].X = tile.data.verts[p.verts[v] * 3];
verts[k].Y = tile.data.verts[p.verts[v] * 3 + 1];
verts[k].Z = tile.data.verts[p.verts[v] * 3 + 2];
}
if (RcIntersections.IntersectSegmentTriangle(sp, sq, verts[0], verts[1], verts[2], out hitTime))
else
{
return true;
verts[k].X = tile.data.detailVerts[(pd.vertBase + v - p.vertCount) * 3];
verts[k].Y = tile.data.detailVerts[(pd.vertBase + v - p.vertCount) * 3 + 1];
verts[k].Z = tile.data.detailVerts[(pd.vertBase + v - p.vertCount) * 3 + 2];
}
}
}
else
{
// FIXME: Use Poly if PolyDetail is unavailable
if (RcIntersections.IntersectSegmentTriangle(sp, sq, verts[0], verts[1], verts[2], out hitTime))
{
return true;
}
}
}

View File

@ -25,42 +25,34 @@ namespace DotRecast.Detour
{
public class DtNode
{
public readonly int index;
public readonly int ptr;
/** Position of the node. */
public RcVec3f pos = new RcVec3f();
public RcVec3f pos; // Position of the node.
public float cost; // Cost from previous node to current node.
public float total; // Cost up to the node.
public int pidx; // Index to parent node.
public int state; // extra state information. A polyRef can have multiple nodes with different extra info. see DT_MAX_STATES_PER_NODE
public int flags; // Node flags. A combination of dtNodeFlags.
public long id; // Polygon ref the node corresponds to.
public List<long> shortcut; // Shortcut found by raycast.
/** Cost of reaching the given node. */
public float cost;
/** Total cost of reaching the goal via the given node including heuristics. */
public float total;
/** Index to parent node. */
public int pidx;
/**
* extra state information. A polyRef can have multiple nodes with different extra info. see DT_MAX_STATES_PER_NODE
*/
public int state;
/** Node flags. A combination of dtNodeFlags. */
public int flags;
/** Polygon ref the node corresponds to. */
public long id;
/** Shortcut found by raycast. */
public List<long> shortcut;
public DtNode(int index)
public DtNode(int ptr)
{
this.index = index;
this.ptr = ptr;
}
public static int ComparisonNodeTotal(DtNode a, DtNode b)
{
int compare = a.total.CompareTo(b.total);
if (0 != compare)
return compare;
return a.ptr.CompareTo(b.ptr);
}
public override string ToString()
{
return "Node [id=" + id + "]";
return $"Node [ptr={ptr} id={id} cost={cost} total={total}]";
}
}
}

View File

@ -19,40 +19,48 @@ freely, subject to the following restrictions:
*/
using System.Collections.Generic;
using System.Linq;
namespace DotRecast.Detour
{
public class DtNodePool
{
private readonly Dictionary<long, List<DtNode>> m_map = new Dictionary<long, List<DtNode>>();
private readonly List<DtNode> m_nodes = new List<DtNode>();
private readonly Dictionary<long, List<DtNode>> m_map;
private int m_nodeCount;
private readonly List<DtNode> m_nodes;
public DtNodePool()
{
m_map = new Dictionary<long, List<DtNode>>();
m_nodes = new List<DtNode>();
}
public void Clear()
{
m_nodes.Clear();
m_map.Clear();
m_nodeCount = 0;
}
public List<DtNode> FindNodes(long id)
public int GetNodeCount()
{
var hasNode = m_map.TryGetValue(id, out var nodes);
;
if (nodes == null)
return m_nodeCount;
}
public int FindNodes(long id, out List<DtNode> nodes)
{
var hasNode = m_map.TryGetValue(id, out nodes);
if (hasNode)
{
nodes = new List<DtNode>();
return nodes.Count;
}
return nodes;
return 0;
}
public DtNode FindNode(long id)
{
var hasNode = m_map.TryGetValue(id, out var nodes);
;
m_map.TryGetValue(id, out var nodes);
if (nodes != null && 0 != nodes.Count)
{
return nodes[0];
@ -63,7 +71,7 @@ namespace DotRecast.Detour
public DtNode GetNode(long id, int state)
{
var hasNode = m_map.TryGetValue(id, out var nodes);
m_map.TryGetValue(id, out var nodes);
if (nodes != null)
{
foreach (DtNode node in nodes)
@ -85,10 +93,22 @@ namespace DotRecast.Detour
private DtNode Create(long id, int state, List<DtNode> nodes)
{
DtNode node = new DtNode(m_nodes.Count + 1);
if (m_nodes.Count <= m_nodeCount)
{
var newNode = new DtNode(m_nodeCount);
m_nodes.Add(newNode);
}
int i = m_nodeCount;
m_nodeCount++;
var node = m_nodes[i];
node.pidx = 0;
node.cost = 0;
node.total = 0;
node.id = id;
node.state = state;
m_nodes.Add(node);
node.flags = 0;
node.shortcut = null;
nodes.Add(node);
return node;
@ -96,12 +116,16 @@ namespace DotRecast.Detour
public int GetNodeIdx(DtNode node)
{
return node != null ? node.index : 0;
return node != null
? node.ptr + 1
: 0;
}
public DtNode GetNodeAtIdx(int idx)
{
return idx != 0 ? m_nodes[idx - 1] : null;
return idx != 0
? m_nodes[idx - 1]
: null;
}
public DtNode GetNode(long refs)
@ -109,9 +133,9 @@ namespace DotRecast.Detour
return GetNode(refs, 0);
}
public Dictionary<long, List<DtNode>> GetNodeMap()
public IEnumerable<DtNode> AsEnumerable()
{
return m_map;
return m_nodes.Take(m_nodeCount);
}
}
}

View File

@ -24,7 +24,12 @@ namespace DotRecast.Detour
{
public class DtNodeQueue
{
private readonly RcSortedQueue<DtNode> m_heap = new RcSortedQueue<DtNode>((n1, n2) => n1.total.CompareTo(n2.total));
private readonly RcSortedQueue<DtNode> m_heap;
public DtNodeQueue()
{
m_heap = new RcSortedQueue<DtNode>(DtNode.ComparisonNodeTotal);
}
public int Count()
{
@ -43,9 +48,7 @@ namespace DotRecast.Detour
public DtNode Pop()
{
var node = Peek();
m_heap.Remove(node);
return node;
return m_heap.Dequeue();
}
public void Push(DtNode node)
@ -61,7 +64,7 @@ namespace DotRecast.Detour
public bool IsEmpty()
{
return 0 == m_heap.Count();
return m_heap.IsEmpty();
}
}
}

View File

@ -18,6 +18,8 @@ freely, subject to the following restrictions:
3. This notice may not be removed or altered from any source distribution.
*/
using DotRecast.Core.Numerics;
namespace DotRecast.Detour
{
/// Defines an navigation mesh off-mesh connection within a dtMeshTile object.
@ -25,7 +27,7 @@ namespace DotRecast.Detour
public class DtOffMeshConnection
{
/// The endpoints of the connection. [(ax, ay, az, bx, by, bz)]
public float[] pos = new float[6];
public RcVec3f[] pos = new RcVec3f[2];
/// The radius of the endpoints. [Limit: >= 0]
public float rad;

View File

@ -141,7 +141,7 @@ namespace DotRecast.Detour
return path;
}
public static List<long> MergeCorridorStartMoved(List<long> path, List<long> visited)
public static List<long> MergeCorridorStartMoved(List<long> path, int npath, int maxPath, List<long> visited)
{
int furthestPath = -1;
int furthestVisited = -1;
@ -186,7 +186,7 @@ namespace DotRecast.Detour
return result;
}
public static List<long> MergeCorridorEndMoved(List<long> path, List<long> visited)
public static List<long> MergeCorridorEndMoved(List<long> path, int npath, int maxPath, List<long> visited)
{
int furthestPath = -1;
int furthestVisited = -1;
@ -223,7 +223,7 @@ namespace DotRecast.Detour
return result;
}
public static List<long> MergeCorridorStartShortcut(List<long> path, List<long> visited)
public static List<long> MergeCorridorStartShortcut(List<long> path, int npath, int maxPath, List<long> visited)
{
int furthestPath = -1;
int furthestVisited = -1;

View File

@ -21,18 +21,26 @@ freely, subject to the following restrictions:
namespace DotRecast.Detour
{
/** Defines the location of detail sub-mesh data within a dtMeshTile. */
public class DtPolyDetail
public readonly struct DtPolyDetail
{
/** The offset of the vertices in the MeshTile::detailVerts array. */
public int vertBase;
public readonly int vertBase;
/** The offset of the triangles in the MeshTile::detailTris array. */
public int triBase;
public readonly int triBase;
/** The number of vertices in the sub-mesh. */
public int vertCount;
public readonly int vertCount;
/** The number of triangles in the sub-mesh. */
public int triCount;
public readonly int triCount;
public DtPolyDetail(int vertBase, int triBase, int vertCount, int triCount)
{
this.vertBase = vertBase;
this.triBase = triBase;
this.vertCount = vertCount;
this.triCount = triCount;
}
}
}

View File

@ -23,24 +23,24 @@ using DotRecast.Core.Numerics;
namespace DotRecast.Detour
{
/**
* Provides information about raycast hit. Filled by NavMeshQuery::raycast
*/
public class DtRaycastHit
/// Provides information about raycast hit
/// filled by dtNavMeshQuery::raycast
/// @ingroup detour
public struct DtRaycastHit
{
/** The hit parameter. (float.MaxValue if no wall hit.) */
/// The hit parameter. (FLT_MAX if no wall hit.)
public float t;
/** hitNormal The normal of the nearest wall hit. [(x, y, z)] */
public RcVec3f hitNormal = new RcVec3f();
/// hitNormal The normal of the nearest wall hit. [(x, y, z)]
public RcVec3f hitNormal;
/** Visited polygons. */
public readonly List<long> path = new List<long>();
/** The cost of the path until hit. */
public float pathCost;
/** The index of the edge on the readonly polygon where the wall was hit. */
/// The index of the edge on the final polygon where the wall was hit.
public int hitEdgeIndex;
/// Pointer to an array of reference ids of the visited polygons. [opt]
public List<long> path;
/// The cost of the path until hit.
public float pathCost;
}
}

View File

@ -26,7 +26,7 @@ namespace DotRecast.Detour
{
// High level status.
public static readonly DtStatus DT_FAILURE = new DtStatus(1u << 31); // Operation failed.
public static readonly DtStatus DT_SUCCSESS = new DtStatus(1u << 30); // Operation succeed.
public static readonly DtStatus DT_SUCCESS = new DtStatus(1u << 30); // Operation succeed.
public static readonly DtStatus DT_IN_PROGRESS = new DtStatus(1u << 29); // Operation still in progress.
// Detail information for status.
@ -57,7 +57,7 @@ namespace DotRecast.Detour
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool Succeeded()
{
return 0 != (Value & (DT_SUCCSESS.Value | DT_PARTIAL_RESULT.Value));
return 0 != (Value & (DT_SUCCESS.Value | DT_PARTIAL_RESULT.Value));
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]

View File

@ -53,10 +53,10 @@ namespace DotRecast.Detour.Io
DtMeshHeader header = new DtMeshHeader();
data.header = header;
header.magic = buf.GetInt();
if (header.magic != DtMeshHeader.DT_NAVMESH_MAGIC)
if (header.magic != DtNavMesh.DT_NAVMESH_MAGIC)
{
header.magic = IOUtils.SwapEndianness(header.magic);
if (header.magic != DtMeshHeader.DT_NAVMESH_MAGIC)
if (header.magic != DtNavMesh.DT_NAVMESH_MAGIC)
{
throw new IOException("Invalid magic");
}
@ -65,16 +65,16 @@ namespace DotRecast.Detour.Io
}
header.version = buf.GetInt();
if (header.version != DtMeshHeader.DT_NAVMESH_VERSION)
if (header.version != DtNavMesh.DT_NAVMESH_VERSION)
{
if (header.version < DtMeshHeader.DT_NAVMESH_VERSION_RECAST4J_FIRST
|| header.version > DtMeshHeader.DT_NAVMESH_VERSION_RECAST4J_LAST)
if (header.version < DtNavMesh.DT_NAVMESH_VERSION_RECAST4J_FIRST
|| header.version > DtNavMesh.DT_NAVMESH_VERSION_RECAST4J_LAST)
{
throw new IOException("Invalid version " + header.version);
}
}
bool cCompatibility = header.version == DtMeshHeader.DT_NAVMESH_VERSION;
bool cCompatibility = header.version == DtNavMesh.DT_NAVMESH_VERSION;
header.x = buf.GetInt();
header.y = buf.GetInt();
header.layer = buf.GetInt();
@ -141,7 +141,7 @@ namespace DotRecast.Detour.Io
for (int i = 0; i < polys.Length; i++)
{
polys[i] = new DtPoly(i, maxVertPerPoly);
if (header.version < DtMeshHeader.DT_NAVMESH_VERSION_RECAST4J_NO_POLY_FIRSTLINK)
if (header.version < DtNavMesh.DT_NAVMESH_VERSION_RECAST4J_NO_POLY_FIRSTLINK)
{
buf.GetInt(); // polys[i].firstLink
}
@ -169,11 +169,11 @@ namespace DotRecast.Detour.Io
DtPolyDetail[] polys = new DtPolyDetail[header.detailMeshCount];
for (int i = 0; i < polys.Length; i++)
{
polys[i] = new DtPolyDetail();
polys[i].vertBase = buf.GetInt();
polys[i].triBase = buf.GetInt();
polys[i].vertCount = buf.Get() & 0xFF;
polys[i].triCount = buf.Get() & 0xFF;
int vertBase = buf.GetInt();
int triBase = buf.GetInt();
int vertCount = buf.Get() & 0xFF;
int triCount = buf.Get() & 0xFF;
polys[i] = new DtPolyDetail(vertBase, triBase, vertCount, triCount);
if (cCompatibility)
{
buf.GetShort(); // C struct padding
@ -200,7 +200,7 @@ namespace DotRecast.Detour.Io
for (int i = 0; i < nodes.Length; i++)
{
nodes[i] = new DtBVNode();
if (header.version < DtMeshHeader.DT_NAVMESH_VERSION_RECAST4J_32BIT_BVTREE)
if (header.version < DtNavMesh.DT_NAVMESH_VERSION_RECAST4J_32BIT_BVTREE)
{
for (int j = 0; j < 3; j++)
{
@ -237,9 +237,11 @@ namespace DotRecast.Detour.Io
for (int i = 0; i < cons.Length; i++)
{
cons[i] = new DtOffMeshConnection();
for (int j = 0; j < 6; j++)
for (int j = 0; j < 2; j++)
{
cons[i].pos[j] = buf.GetFloat();
cons[i].pos[j].X = buf.GetFloat();
cons[i].pos[j].Y = buf.GetFloat();
cons[i].pos[j].Z = buf.GetFloat();
}
cons[i].rad = buf.GetFloat();

View File

@ -27,7 +27,7 @@ namespace DotRecast.Detour.Io
{
DtMeshHeader header = data.header;
Write(stream, header.magic, order);
Write(stream, cCompatibility ? DtMeshHeader.DT_NAVMESH_VERSION : DtMeshHeader.DT_NAVMESH_VERSION_RECAST4J_LAST, order);
Write(stream, cCompatibility ? DtNavMesh.DT_NAVMESH_VERSION : DtNavMesh.DT_NAVMESH_VERSION_RECAST4J_LAST, order);
Write(stream, header.x, order);
Write(stream, header.y, order);
Write(stream, header.layer, order);
@ -159,9 +159,11 @@ namespace DotRecast.Detour.Io
{
for (int i = 0; i < data.header.offMeshConCount; i++)
{
for (int j = 0; j < 6; j++)
for (int j = 0; j < 2; j++)
{
Write(stream, data.offMeshCons[i].pos[j], order);
Write(stream, data.offMeshCons[i].pos[j].X, order);
Write(stream, data.offMeshCons[i].pos[j].Y, order);
Write(stream, data.offMeshCons[i].pos[j].Z, order);
}
Write(stream, data.offMeshCons[i].rad, order);

View File

@ -67,7 +67,7 @@ namespace DotRecast.Detour.Io
bool cCompatibility = header.version == NavMeshSetHeader.NAVMESHSET_VERSION;
DtNavMesh mesh = new DtNavMesh(header.option, header.maxVertsPerPoly);
ReadTiles(bb, is32Bit, header, cCompatibility, mesh);
ReadTiles(bb, is32Bit, ref header, cCompatibility, mesh);
return mesh;
}
@ -104,7 +104,7 @@ namespace DotRecast.Detour.Io
return header;
}
private void ReadTiles(RcByteBuffer bb, bool is32Bit, NavMeshSetHeader header, bool cCompatibility, DtNavMesh mesh)
private void ReadTiles(RcByteBuffer bb, bool is32Bit, ref NavMeshSetHeader header, bool cCompatibility, DtNavMesh mesh)
{
// Read tiles.
for (int i = 0; i < header.numTiles; ++i)

View File

@ -18,7 +18,7 @@ freely, subject to the following restrictions:
namespace DotRecast.Detour.Io
{
public class NavMeshSetHeader
public struct NavMeshSetHeader
{
public const int NAVMESHSET_MAGIC = 'M' << 24 | 'S' << 16 | 'E' << 8 | 'T'; // 'MSET';
public const int NAVMESHSET_VERSION = 1;
@ -28,7 +28,7 @@ namespace DotRecast.Detour.Io
public int magic;
public int version;
public int numTiles;
public DtNavMeshParams option = new DtNavMeshParams();
public DtNavMeshParams option;
public int maxVertsPerPoly;
}
}

View File

@ -1,6 +1,6 @@
namespace DotRecast.Detour.Io
{
public class NavMeshTileHeader
public struct NavMeshTileHeader
{
public long tileRef;
public int dataSize;

View File

@ -2,16 +2,17 @@
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFrameworks>net6.0;net7.0</TargetFrameworks>
<TargetFrameworks>net6.0;net7.0;net8.0</TargetFrameworks>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<PackageId>DotRecast.Recast.Demo</PackageId>
<PackageReadmeFile>README.md</PackageReadmeFile>
<Authors>ikpil</Authors>
<Description>DotRecast - a port of Recast Detour, navigation mesh toolset for games, Unity3D, servers, C#</Description>
<Description>DotRecast - a port of Recast Detour, Industry-standard navigation mesh toolset for .NET, C#, Unity3D, games, servers</Description>
<RepositoryType>git</RepositoryType>
<PackageProjectUrl>https://github.com/ikpil/DotRecast</PackageProjectUrl>
<RepositoryUrl>https://github.com/ikpil/DotRecast</RepositoryUrl>
<PackageTags>game gamedev ai csharp server unity navigation game-development unity3d pathfinding pathfinder recast detour navmesh crowd-simulation recastnavigation</PackageTags>
<PackageReleaseNotes>https://github.com/ikpil/DotRecast/blob/main/CHANGELOG.md</PackageReleaseNotes>
</PropertyGroup>
<ItemGroup>
@ -19,15 +20,15 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="Serilog" Version="3.0.1"/>
<PackageReference Include="Serilog.Settings.Configuration" Version="7.0.1"/>
<PackageReference Include="Serilog" Version="3.1.1" />
<PackageReference Include="Serilog.Settings.Configuration" Version="8.0.0" />
<PackageReference Include="Serilog.Enrichers.Thread" Version="3.1.0"/>
<PackageReference Include="Serilog.Sinks.Async" Version="1.5.0"/>
<PackageReference Include="Serilog.Sinks.Console" Version="4.1.0"/>
<PackageReference Include="Serilog.Sinks.Console" Version="5.0.1" />
<PackageReference Include="Serilog.Sinks.File" Version="5.0.0"/>
<PackageReference Include="K4os.Compression.LZ4" Version="1.3.6"/>
<PackageReference Include="Silk.NET" Version="2.17.1"/>
<PackageReference Include="Silk.NET.OpenGL.Extensions.ImGui" Version="2.17.1"/>
<PackageReference Include="Silk.NET" Version="2.20.0" />
<PackageReference Include="Silk.NET.OpenGL.Extensions.ImGui" Version="2.20.0" />
</ItemGroup>
<ItemGroup>

View File

@ -1,4 +1,5 @@
using System;
using DotRecast.Core;
namespace DotRecast.Recast.Demo.Draw;
@ -24,7 +25,7 @@ public class ArrayBuffer<T>
if (_items.Length <= _size)
{
var temp = new T[(int)(_size * 1.5)];
Array.Copy(_items, 0, temp, 0, _items.Length);
RcArrays.Copy(_items, 0, temp, 0, _items.Length);
_items = temp;
}

View File

@ -64,8 +64,8 @@ public class DrawMode
DRAWMODE_POLYMESH_DETAIL
);
public int Idx { get; }
public string Text { get; }
public readonly int Idx;
public readonly string Text;
private DrawMode(int idx, string text)
{

View File

@ -126,7 +126,7 @@ public static class GLU
// This code comes directly from GLU except that it is for float
static int GlhInvertMatrixf2(float[] m, float[] @out)
{
float[][] wtmp = RcArrayUtils.Of<float>(4, 8);
float[][] wtmp = RcArrays.Of<float>(4, 8);
float m0, m1, m2, m3, s;
float[] r0, r1, r2, r3;
r0 = wtmp[0];

View File

@ -213,25 +213,27 @@ public class RecastDebugDraw : DebugDraw
// End points and their on-mesh locations.
Vertex(va.X, va.Y, va.Z, col);
Vertex(con.pos[0], con.pos[1], con.pos[2], col);
Vertex(con.pos[0], col);
col2 = startSet ? col : DuRGBA(220, 32, 16, 196);
AppendCircle(con.pos[0], con.pos[1] + 0.1f, con.pos[2], con.rad, col2);
AppendCircle(con.pos[0].X, con.pos[0].Y + 0.1f, con.pos[0].Z, con.rad, col2);
Vertex(vb.X, vb.Y, vb.Z, col);
Vertex(con.pos[3], con.pos[4], con.pos[5], col);
Vertex(con.pos[1], col);
col2 = endSet ? col : DuRGBA(220, 32, 16, 196);
AppendCircle(con.pos[3], con.pos[4] + 0.1f, con.pos[5], con.rad, col2);
AppendCircle(con.pos[1].X, con.pos[1].Y + 0.1f, con.pos[1].Z, con.rad, col2);
// End point vertices.
Vertex(con.pos[0], con.pos[1], con.pos[2], DuRGBA(0, 48, 64, 196));
Vertex(con.pos[0], con.pos[1] + 0.2f, con.pos[2], DuRGBA(0, 48, 64, 196));
Vertex(con.pos[0], DuRGBA(0, 48, 64, 196));
Vertex(con.pos[0].X, con.pos[0].Y + 0.2f, con.pos[0].Z, DuRGBA(0, 48, 64, 196));
Vertex(con.pos[3], con.pos[4], con.pos[5], DuRGBA(0, 48, 64, 196));
Vertex(con.pos[3], con.pos[4] + 0.2f, con.pos[5], DuRGBA(0, 48, 64, 196));
Vertex(con.pos[1], DuRGBA(0, 48, 64, 196));
Vertex(con.pos[1].X, con.pos[1].Y + 0.2f, con.pos[1].Z, DuRGBA(0, 48, 64, 196));
// Connection arc.
AppendArc(con.pos[0], con.pos[1], con.pos[2], con.pos[3], con.pos[4], con.pos[5], 0.25f,
(con.flags & 1) != 0 ? 0.6f : 0, 0.6f, col);
AppendArc(
con.pos[0].X, con.pos[0].Y, con.pos[0].Z,
con.pos[1].X, con.pos[1].Y, con.pos[1].Z,
0.25f, (con.flags & 1) != 0 ? 0.6f : 0, 0.6f, col);
}
End();
@ -255,26 +257,23 @@ public class RecastDebugDraw : DebugDraw
DtPoly p = tile.data.polys[index];
if (tile.data.detailMeshes != null)
{
DtPolyDetail pd = tile.data.detailMeshes[index];
if (pd != null)
ref DtPolyDetail pd = ref tile.data.detailMeshes[index];
for (int j = 0; j < pd.triCount; ++j)
{
for (int j = 0; j < pd.triCount; ++j)
int t = (pd.triBase + j) * 4;
for (int k = 0; k < 3; ++k)
{
int t = (pd.triBase + j) * 4;
for (int k = 0; k < 3; ++k)
int v = tile.data.detailTris[t + k];
if (v < p.vertCount)
{
int v = tile.data.detailTris[t + k];
if (v < p.vertCount)
{
Vertex(tile.data.verts[p.verts[v] * 3], tile.data.verts[p.verts[v] * 3 + 1],
tile.data.verts[p.verts[v] * 3 + 2], col);
}
else
{
Vertex(tile.data.detailVerts[(pd.vertBase + v - p.vertCount) * 3],
tile.data.detailVerts[(pd.vertBase + v - p.vertCount) * 3 + 1],
tile.data.detailVerts[(pd.vertBase + v - p.vertCount) * 3 + 2], col);
}
Vertex(tile.data.verts[p.verts[v] * 3], tile.data.verts[p.verts[v] * 3 + 1],
tile.data.verts[p.verts[v] * 3 + 2], col);
}
else
{
Vertex(tile.data.detailVerts[(pd.vertBase + v - p.vertCount) * 3],
tile.data.detailVerts[(pd.vertBase + v - p.vertCount) * 3 + 1],
tile.data.detailVerts[(pd.vertBase + v - p.vertCount) * 3 + 2], col);
}
}
}
@ -367,7 +366,7 @@ public class RecastDebugDraw : DebugDraw
// This is really slow.
if (tile.data.detailMeshes != null)
{
DtPolyDetail pd = tile.data.detailMeshes[i];
ref DtPolyDetail pd = ref tile.data.detailMeshes[i];
for (int k = 0; k < pd.triCount; ++k)
{
int t = (pd.triBase + k) * 4;
@ -486,11 +485,11 @@ public class RecastDebugDraw : DebugDraw
{
float fx = chf.bmin.X + x * cs;
float fz = chf.bmin.Z + y * cs;
RcCompactCell c = chf.cells[x + y * chf.width];
ref RcCompactCell c = ref chf.cells[x + y * chf.width];
for (int i = c.index, ni = c.index + c.count; i < ni; ++i)
{
RcCompactSpan s = chf.spans[i];
ref RcCompactSpan s = ref chf.spans[i];
int area = chf.areas[i];
int color;
@ -859,11 +858,11 @@ public class RecastDebugDraw : DebugDraw
{
float fx = chf.bmin.X + x * cs;
float fz = chf.bmin.Z + y * cs;
RcCompactCell c = chf.cells[x + y * chf.width];
ref RcCompactCell c = ref chf.cells[x + y * chf.width];
for (int i = c.index, ni = c.index + c.count; i < ni; ++i)
{
RcCompactSpan s = chf.spans[i];
ref RcCompactSpan s = ref chf.spans[i];
float fy = chf.bmin.Y + (s.y) * ch;
int color;
if (s.reg != 0)
@ -912,11 +911,11 @@ public class RecastDebugDraw : DebugDraw
{
float fx = chf.bmin.X + x * cs;
float fz = chf.bmin.Z + y * cs;
RcCompactCell c = chf.cells[x + y * chf.width];
ref RcCompactCell c = ref chf.cells[x + y * chf.width];
for (int i = c.index, ni = c.index + c.count; i < ni; ++i)
{
RcCompactSpan s = chf.spans[i];
ref RcCompactSpan s = ref chf.spans[i];
float fy = chf.bmin.Y + (s.y + 1) * ch;
char cd = (char)(chf.dist[i] * dscale);
int color = DuRGBA(cd, cd, cd, 255);
@ -1201,45 +1200,39 @@ public class RecastDebugDraw : DebugDraw
float off = 0.5f;
Begin(DebugDrawPrimitives.POINTS, 4.0f);
foreach (List<DtNode> nodes in pool.GetNodeMap().Values)
foreach (DtNode node in pool.AsEnumerable())
{
foreach (DtNode node in nodes)
if (node == null)
{
if (node == null)
{
continue;
}
Vertex(node.pos.X, node.pos.Y + off, node.pos.Z, DuRGBA(255, 192, 0, 255));
continue;
}
Vertex(node.pos.X, node.pos.Y + off, node.pos.Z, DuRGBA(255, 192, 0, 255));
}
End();
Begin(DebugDrawPrimitives.LINES, 2.0f);
foreach (List<DtNode> nodes in pool.GetNodeMap().Values)
foreach (DtNode node in pool.AsEnumerable())
{
foreach (DtNode node in nodes)
if (node == null)
{
if (node == null)
{
continue;
}
if (node.pidx == 0)
{
continue;
}
DtNode parent = pool.GetNodeAtIdx(node.pidx);
if (parent == null)
{
continue;
}
Vertex(node.pos.X, node.pos.Y + off, node.pos.Z, DuRGBA(255, 192, 0, 128));
Vertex(parent.pos.X, parent.pos.Y + off, parent.pos.Z, DuRGBA(255, 192, 0, 128));
continue;
}
if (node.pidx == 0)
{
continue;
}
DtNode parent = pool.GetNodeAtIdx(node.pidx);
if (parent == null)
{
continue;
}
Vertex(node.pos.X, node.pos.Y + off, node.pos.Z, DuRGBA(255, 192, 0, 128));
Vertex(parent.pos.X, parent.pos.Y + off, parent.pos.Z, DuRGBA(255, 192, 0, 128));
}
End();
@ -1296,8 +1289,10 @@ public class RecastDebugDraw : DebugDraw
Begin(DebugDrawPrimitives.LINES, 2.0f);
// Connection arc.
AppendArc(con.pos[0], con.pos[1], con.pos[2], con.pos[3], con.pos[4], con.pos[5], 0.25f,
(con.flags & 1) != 0 ? 0.6f : 0.0f, 0.6f, c);
AppendArc(
con.pos[0].X, con.pos[0].Y, con.pos[0].Z,
con.pos[1].X, con.pos[1].Y, con.pos[1].Z,
0.25f, (con.flags & 1) != 0 ? 0.6f : 0.0f, 0.6f, c);
End();
}

View File

@ -28,7 +28,7 @@ public class DtVoxelTileLZ4DemoCompressor : IRcCompressor
byte[] compressed = LZ4Pickler.Pickle(data, LZ4Level.L12_MAX);
byte[] result = new byte[4 + compressed.Length];
RcByteUtils.PutInt(compressed.Length, result, 0, RcByteOrder.BIG_ENDIAN);
Array.Copy(compressed, 0, result, 4, compressed.Length);
RcArrays.Copy(compressed, 0, result, 4, compressed.Length);
return result;
}
}

View File

@ -43,7 +43,6 @@ using DotRecast.Recast.Demo.Messages;
using DotRecast.Recast.Toolset.Geom;
using DotRecast.Recast.Demo.Tools;
using DotRecast.Recast.Demo.UI;
using DotRecast.Recast.Toolset;
using MouseButton = Silk.NET.Input.MouseButton;
using Window = Silk.NET.Windowing.Window;
@ -59,6 +58,7 @@ public class RecastDemo : IRecastDemoChannel
private ImGuiController _imgui;
private RcCanvas _canvas;
private Vector2D<int> _resolution;
private int width = 1000;
private int height = 900;
@ -110,7 +110,8 @@ public class RecastDemo : IRecastDemoChannel
private bool markerPositionSet;
private RcVec3f markerPosition = new RcVec3f();
private RcToolsetView toolset;
private RcMenuView _menuView;
private RcToolsetView _toolsetView;
private RcSettingsView settingsView;
private RcLogView logView;
@ -250,17 +251,17 @@ public class RecastDemo : IRecastDemoChannel
private IWindow CreateWindow()
{
var monitor = Window.Platforms.First().GetMainMonitor();
var resolution = monitor.VideoMode.Resolution.Value;
_resolution = monitor.VideoMode.Resolution.Value;
float aspect = 16.0f / 9.0f;
width = Math.Min(resolution.X, (int)(resolution.Y * aspect)) - 100;
height = resolution.Y - 100;
width = Math.Min(_resolution.X, (int)(_resolution.Y * aspect)) - 100;
height = _resolution.Y - 100;
viewport = new int[] { 0, 0, width, height };
var options = WindowOptions.Default;
options.Title = title;
options.Size = new Vector2D<int>(width, height);
options.Position = new Vector2D<int>((resolution.X - width) / 2, (resolution.Y - height) / 2);
options.Position = new Vector2D<int>((_resolution.X - width) / 2, (_resolution.Y - height) / 2);
options.VSync = true;
options.ShouldSwapAutomatically = false;
options.PreferredDepthBufferBits = 24;
@ -318,7 +319,7 @@ public class RecastDemo : IRecastDemoChannel
if (null != mesh)
{
_sample.Update(_sample.GetInputGeom(), ImmutableArray<RcBuilderResult>.Empty, mesh);
toolset.SetEnabled(true);
_toolsetView.SetEnabled(true);
}
}
catch (Exception e)
@ -364,18 +365,26 @@ public class RecastDemo : IRecastDemoChannel
dd.Init(camr);
var scale = (float)_resolution.X / 1920;
int fontSize = Math.Max(10, (int)(16 * scale));
// for windows : Microsoft Visual C++ Redistributable Package
// link - https://learn.microsoft.com/en-us/cpp/windows/latest-supported-vc-redist
ImGuiFontConfig imGuiFontConfig = new(Path.Combine("resources\\fonts", "DroidSans.ttf"), 16, null);
var imGuiFontConfig = new ImGuiFontConfig(Path.Combine("resources\\fonts", "DroidSans.ttf"), fontSize, null);
_imgui = new ImGuiController(_gl, window, _input, imGuiFontConfig);
ImGui.GetStyle().ScaleAllSizes(scale);
//ImGui.GetIO().FontGlobalScale = 2.0f;
DemoInputGeomProvider geom = LoadInputMesh("nav_test.obj");
_sample = new DemoSample(geom, ImmutableArray<RcBuilderResult>.Empty, null);
_menuView = new RcMenuView();
settingsView = new RcSettingsView(this);
settingsView.SetSample(_sample);
toolset = new RcToolsetView(
_toolsetView = new RcToolsetView(
new TestNavmeshSampleTool(),
new TileSampleTool(),
new ObstacleSampleTool(),
@ -386,10 +395,10 @@ public class RecastDemo : IRecastDemoChannel
new JumpLinkBuilderSampleTool(),
new DynamicUpdateSampleTool()
);
toolset.SetEnabled(true);
_toolsetView.SetEnabled(true);
logView = new RcLogView();
_canvas = new RcCanvas(window, settingsView, toolset, logView);
_canvas = new RcCanvas(window, _menuView, settingsView, _toolsetView, logView);
var vendor = _gl.GetStringS(GLEnum.Vendor);
var version = _gl.GetStringS(GLEnum.Version);
@ -400,12 +409,16 @@ public class RecastDemo : IRecastDemoChannel
var workingDirectory = Directory.GetCurrentDirectory();
Logger.Information($"Working directory - {workingDirectory}");
Logger.Information($"ImGui.Net version - {ImGui.GetVersion()}");
Logger.Information($"ImGui.Net - version({ImGui.GetVersion()}) UI scale({scale}) fontSize({fontSize})");
Logger.Information($"Dotnet - {Environment.Version.ToString()} culture({currentCulture.Name})");
Logger.Information($"OS Version - {Environment.OSVersion} {bitness}");
Logger.Information($"{vendor} {rendererGl}");
Logger.Information($"gl version - {version}");
Logger.Information($"gl lang version - {glslString}");
Logger.Information($"gl version({version}) lang version({glslString})");
}
private float GetKeyValue(IKeyboard keyboard, Key primaryKey, Key secondaryKey)
{
return keyboard.IsKeyPressed(primaryKey) || keyboard.IsKeyPressed(secondaryKey) ? 1.0f : -1.0f;
}
private void UpdateKeyboard(float dt)
@ -415,17 +428,17 @@ public class RecastDemo : IRecastDemoChannel
// keyboard input
foreach (var keyboard in _input.Keyboards)
{
var tempMoveFront = keyboard.IsKeyPressed(Key.W) || keyboard.IsKeyPressed(Key.Up) ? 1.0f : -1f;
var tempMoveLeft = keyboard.IsKeyPressed(Key.A) || keyboard.IsKeyPressed(Key.Left) ? 1.0f : -1f;
var tempMoveBack = keyboard.IsKeyPressed(Key.S) || keyboard.IsKeyPressed(Key.Down) ? 1.0f : -1f;
var tempMoveRight = keyboard.IsKeyPressed(Key.D) || keyboard.IsKeyPressed(Key.Right) ? 1.0f : -1f;
var tempMoveUp = keyboard.IsKeyPressed(Key.Q) || keyboard.IsKeyPressed(Key.PageUp) ? 1.0f : -1f;
var tempMoveDown = keyboard.IsKeyPressed(Key.E) || keyboard.IsKeyPressed(Key.PageDown) ? 1.0f : -1f;
var tempMoveAccel = keyboard.IsKeyPressed(Key.ShiftLeft) || keyboard.IsKeyPressed(Key.ShiftRight) ? 1.0f : -1f;
var tempControl = keyboard.IsKeyPressed(Key.ControlLeft) || keyboard.IsKeyPressed(Key.ControlRight);
var tempMoveFront = GetKeyValue(keyboard, Key.W, Key.Up);
var tempMoveLeft = GetKeyValue(keyboard, Key.A, Key.Left);
var tempMoveBack = GetKeyValue(keyboard, Key.S, Key.Down);
var tempMoveRight = GetKeyValue(keyboard, Key.D, Key.Right);
var tempMoveUp = GetKeyValue(keyboard, Key.Q, Key.PageUp);
var tempMoveDown = GetKeyValue(keyboard, Key.E, Key.PageDown);
var tempMoveAccel = GetKeyValue(keyboard, Key.ShiftLeft, Key.ShiftRight);
var tempControl = GetKeyValue(keyboard, Key.ControlLeft, Key.ControlRight);
_modState |= tempControl ? (int)KeyModState.Control : (int)KeyModState.None;
_modState |= 0 < tempMoveAccel ? (int)KeyModState.Shift : (int)KeyModState.None;
_modState |= 0 < tempControl ? KeyModState.Control : KeyModState.None;
_modState |= 0 < tempMoveAccel ? KeyModState.Shift : KeyModState.None;
//Logger.Information($"{_modState}");
_moveFront = Math.Clamp(_moveFront + tempMoveFront * dt * 4.0f, 0, 2.0f);
@ -492,7 +505,7 @@ public class RecastDemo : IRecastDemoChannel
timeAcc -= DELTA_TIME;
if (simIter < 5 && _sample != null)
{
var tool = toolset.GetTool();
var tool = _toolsetView.GetTool();
if (null != tool)
{
tool.HandleUpdate(DELTA_TIME);
@ -579,7 +592,7 @@ public class RecastDemo : IRecastDemoChannel
}
_sample.SetChanged(false);
toolset.SetSample(_sample);
_toolsetView.SetSample(_sample);
}
if (_messages.TryDequeue(out var msg))
@ -608,7 +621,7 @@ public class RecastDemo : IRecastDemoChannel
dd.Fog(camr * 0.1f, camr * 1.25f);
renderer.Render(_sample, settingsView.GetDrawMode());
ISampleTool sampleTool = toolset.GetTool();
ISampleTool sampleTool = _toolsetView.GetTool();
if (sampleTool != null)
{
sampleTool.HandleRender(renderer);
@ -695,7 +708,7 @@ public class RecastDemo : IRecastDemoChannel
_sample.SetChanged(false);
settingsView.SetBuildTime((RcFrequency.Ticks - t) / TimeSpan.TicksPerMillisecond);
//settingsUI.SetBuildTelemetry(buildResult.Item1.Select(x => x.GetTelemetry()).ToList());
toolset.SetSample(_sample);
_toolsetView.SetSample(_sample);
Logger.Information($"build times");
Logger.Information($"-----------------------------------------");
@ -788,7 +801,7 @@ public class RecastDemo : IRecastDemoChannel
RcVec3f rayDir = new RcVec3f(rayEnd.X - rayStart.X, rayEnd.Y - rayStart.Y, rayEnd.Z - rayStart.Z);
rayDir = RcVec3f.Normalize(rayDir);
ISampleTool raySampleTool = toolset.GetTool();
ISampleTool raySampleTool = _toolsetView.GetTool();
if (raySampleTool != null)
{

View File

@ -160,15 +160,11 @@ public class ConvexVolumeSampleTool : ISampleTool
var geom = _sample.GetInputGeom();
if (shift)
{
_tool.RemoveByPos(geom, p);
_tool.TryRemove(geom, p, out var volume);
}
else
{
if (_tool.PlottingShape(p, out var pts, out var hull))
{
var vol = RcConvexVolumeTool.CreateConvexVolume(pts, hull, _areaType, _boxDescent, _boxHeight, _polyOffset);
_tool.Add(geom, vol);
}
_tool.TryAdd(geom, p, _areaType, _boxDescent, _boxHeight, _polyOffset, out var volume);
}
}

View File

@ -118,7 +118,11 @@ public class CrowdAgentProfilingSampleTool : ISampleTool
ImGui.Text($"{rtt.Key}: {rtt.Micros} us");
}
ImGui.Text($"Update Time: {_tool.GetCrowdUpdateTime()} ms");
ImGui.Text($"Sampling Time: {_tool.GetCrowdUpdateSamplingTime()} ms");
ImGui.Text($"Current Update Time: {_tool.GetCrowdUpdateTime()} ms");
ImGui.Text($"Avg Update Time: {_tool.GetCrowdUpdateAvgTime()} ms");
ImGui.Text($"Max Update Time: {_tool.GetCrowdUpdateMaxTime()} ms");
ImGui.Text($"Min Update Time: {_tool.GetCrowdUpdateMinTime()} ms");
}
}

View File

@ -33,7 +33,6 @@ public class ObstacleSampleTool : ISampleTool
if (buildResult.Success)
{
_sample.Update(_sample.GetInputGeom(), buildResult.RecastBuilderResults, buildResult.NavMesh);
_sample.SetChanged(false);
}
}

View File

@ -698,6 +698,7 @@ public class TestNavmeshSampleTool : ISampleTool
}
else if (_mode == RcTestNavmeshToolMode.RANDOM_POINTS_IN_CIRCLE)
{
_randomPoints.Clear();
_tool.FindRandomPointAroundCircle(navQuery, m_startRef, m_endRef, m_spos, m_epos, m_filter, _constrainByCircle, _randomPointCount, ref _randomPoints);
}
}

View File

@ -0,0 +1,59 @@
using DotRecast.Core;
using ImGuiNET;
namespace DotRecast.Recast.Demo.UI;
public class RcMenuView : IRcView
{
private RcCanvas _canvas;
public void Bind(RcCanvas canvas)
{
_canvas = canvas;
}
public bool IsHovered()
{
//throw new System.NotImplementedException();
return false;
}
public void Update(double dt)
{
//throw new System.NotImplementedException();
}
public void Draw(double dt)
{
if (ImGui.BeginMainMenuBar())
{
if (ImGui.BeginMenu("Help"))
{
if (ImGui.MenuItem("Repository"))
{
RcProcess.OpenUrl("https://github.com/ikpil/DotRecast");
}
if (ImGui.MenuItem("Nuget"))
{
RcProcess.OpenUrl("https://www.nuget.org/packages/DotRecast.Core/");
}
ImGui.Separator();
if (ImGui.MenuItem("Issue Tracker"))
{
RcProcess.OpenUrl("https://github.com/ikpil/DotRecast/issues");
}
if (ImGui.MenuItem("Release Notes"))
{
RcProcess.OpenUrl("https://github.com/ikpil/DotRecast/blob/main/CHANGELOG.md");
}
ImGui.EndMenu();
}
ImGui.EndMainMenuBar();
}
}
}

View File

@ -123,7 +123,7 @@ public class RcSettingsView : IRcView
ImGui.Text("Agent");
ImGui.Separator();
ImGui.SliderFloat("Height", ref settings.agentHeight, 0.1f, 5f, "%.1f");
ImGui.SliderFloat("Radius", ref settings.agentRadius, 0.1f, 5f, "%.1f");
ImGui.SliderFloat("Radius", ref settings.agentRadius, 0.0f, 5f, "%.1f");
ImGui.SliderFloat("Max Climb", ref settings.agentMaxClimb, 0.1f, 5f, "%.1f");
ImGui.SliderFloat("Max Slope", ref settings.agentMaxSlope, 1f, 90f, "%.0f");
ImGui.SliderFloat("Max Acceleration", ref settings.agentMaxAcceleration, 8f, 999f, "%.1f");

View File

@ -61,7 +61,15 @@ namespace DotRecast.Recast.Toolset.Builder
public static RcAreaModification OfValue(int value)
{
return Values.FirstOrDefault(x => x.Value == value) ?? SAMPLE_AREAMOD_GRASS;
foreach (var v in Values)
{
if (v.Value == value)
{
return v;
}
}
return SAMPLE_AREAMOD_GRASS;
}
}
}

View File

@ -1,15 +1,16 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>netstandard2.1;net6.0;net7.0</TargetFrameworks>
<TargetFrameworks>netstandard2.1;net6.0;net7.0;net8.0</TargetFrameworks>
<PackageId>DotRecast.Recast.Toolset</PackageId>
<PackageReadmeFile>README.md</PackageReadmeFile>
<Authors>ikpil</Authors>
<Description>DotRecast - a port of Recast Detour, navigation mesh toolset for games, Unity3D, servers, C#</Description>
<Description>DotRecast - a port of Recast Detour, Industry-standard navigation mesh toolset for .NET, C#, Unity3D, games, servers</Description>
<RepositoryType>git</RepositoryType>
<PackageProjectUrl>https://github.com/ikpil/DotRecast</PackageProjectUrl>
<RepositoryUrl>https://github.com/ikpil/DotRecast</RepositoryUrl>
<PackageTags>game gamedev ai csharp server unity navigation game-development unity3d pathfinding pathfinder recast detour navmesh crowd-simulation recastnavigation</PackageTags>
<PackageReleaseNotes>https://github.com/ikpil/DotRecast/blob/main/CHANGELOG.md</PackageReleaseNotes>
</PropertyGroup>
<ItemGroup>

View File

@ -74,7 +74,7 @@ namespace DotRecast.Recast.Toolset.Tools
}
public RcConvexVolume RemoveByPos(IInputGeomProvider geom, RcVec3f pos)
public bool TryRemove(IInputGeomProvider geom, RcVec3f pos, out RcConvexVolume volume)
{
// Delete
int nearestIndex = -1;
@ -90,26 +90,57 @@ namespace DotRecast.Recast.Toolset.Tools
// If end point close enough, delete it.
if (nearestIndex == -1)
return null;
{
volume = null;
return false;
}
var removal = geom.ConvexVolumes()[nearestIndex];
geom.ConvexVolumes().RemoveAt(nearestIndex);
return removal;
volume = removal;
return null != volume;
}
public void Add(IInputGeomProvider geom, RcConvexVolume volume)
public bool TryAdd(IInputGeomProvider geom, RcVec3f p, RcAreaModification areaType, float boxDescent, float boxHeight, float polyOffset, out RcConvexVolume volume)
{
geom.AddConvexVolume(volume);
// Create
// If clicked on that last pt, create the shape.
if (_pts.Count > 0 && RcVec3f.DistanceSquared(p, _pts[^1]) < 0.2f * 0.2f)
{
//
if (_hull.Count > 2)
{
volume = CreateConvexVolume(_pts, _hull, areaType, boxDescent, boxHeight, polyOffset);
geom.AddConvexVolume(volume);
}
_pts.Clear();
_hull.Clear();
}
else
{
// Add new point
_pts.Add(p);
// Update hull.
if (_pts.Count > 1)
{
_hull.Clear();
_hull.AddRange(RcConvexUtils.Convexhull(_pts));
}
else
{
_hull.Clear();
}
}
volume = null;
return false;
}
public static RcConvexVolume CreateConvexVolume(List<RcVec3f> pts, List<int> hull, RcAreaModification areaType, float boxDescent, float boxHeight, float polyOffset)
{
//
if (hull.Count <= 2)
{
return null;
}
// Create shape.
float[] verts = new float[hull.Count * 3];
for (int i = 0; i < hull.Count; ++i)
@ -134,7 +165,7 @@ namespace DotRecast.Recast.Toolset.Tools
int noffset = RcAreas.OffsetPoly(verts, hull.Count, polyOffset, offset, offset.Length);
if (noffset > 0)
{
verts = RcArrayUtils.CopyOf(offset, 0, noffset * 3);
verts = RcArrays.CopyOf(offset, 0, noffset * 3);
}
}

View File

@ -1,6 +1,9 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Numerics;
using DotRecast.Core;
using DotRecast.Core.Buffers;
using DotRecast.Core.Collections;
using DotRecast.Core.Numerics;
using DotRecast.Detour;
@ -14,20 +17,28 @@ namespace DotRecast.Recast.Toolset.Tools
private RcCrowdAgentProfilingToolConfig _cfg;
private DtCrowdConfig _crowdCfg;
private DtCrowd crowd;
private DtCrowd _crowd;
private readonly DtCrowdAgentConfig _agCfg;
private DtNavMesh navMesh;
private DtNavMesh _navMesh;
private RcRand rnd;
private IRcRand _rand;
private readonly List<DtPolyPoint> _polyPoints;
private long crowdUpdateTime;
private const int SamplingCount = 500;
private long _samplingUpdateTime;
private readonly RcCyclicBuffer<long> _updateTimes;
private long _curUpdateTime;
private long _avgUpdateTime;
private long _minUpdateTime;
private long _maxUpdateTime;
public RcCrowdAgentProfilingTool()
{
_cfg = new RcCrowdAgentProfilingToolConfig();
_agCfg = new DtCrowdAgentConfig();
_polyPoints = new List<DtPolyPoint>();
_updateTimes = new RcCyclicBuffer<long>(SamplingCount);
}
public string GetName()
@ -47,12 +58,12 @@ namespace DotRecast.Recast.Toolset.Tools
public DtCrowd GetCrowd()
{
return crowd;
return _crowd;
}
public void Setup(float maxAgentRadius, DtNavMesh nav)
{
navMesh = nav;
_navMesh = nav;
if (nav != null)
{
_crowdCfg = new DtCrowdConfig(maxAgentRadius);
@ -76,7 +87,7 @@ namespace DotRecast.Recast.Toolset.Tools
private DtStatus GetMobPosition(DtNavMeshQuery navquery, IDtQueryFilter filter, out RcVec3f randomPt)
{
return navquery.FindRandomPoint(filter, rnd, out var randomRef, out randomPt);
return navquery.FindRandomPoint(filter, _rand, out var randomRef, out randomPt);
}
private DtStatus GetVillagerPosition(DtNavMeshQuery navquery, IDtQueryFilter filter, out RcVec3f randomPt)
@ -86,8 +97,8 @@ namespace DotRecast.Recast.Toolset.Tools
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,
int zone = (int)(_rand.Next() * _polyPoints.Count);
return navquery.FindRandomPointWithinCircle(_polyPoints[zone].refs, _polyPoints[zone].pt, _cfg.zoneRadius, filter, _rand,
out var randomRef, out randomPt);
}
@ -95,13 +106,13 @@ namespace DotRecast.Recast.Toolset.Tools
{
_polyPoints.Clear();
IDtQueryFilter filter = new DtQueryDefaultFilter();
DtNavMeshQuery navquery = new DtNavMeshQuery(navMesh);
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);
var status = navquery.FindRandomPoint(filter, _rand, out var randomRef, out var randomPt);
if (status.Succeeded())
{
bool valid = true;
@ -126,57 +137,65 @@ namespace DotRecast.Recast.Toolset.Tools
private void CreateCrowd()
{
crowd = new DtCrowd(_crowdCfg, navMesh, __ => new DtQueryDefaultFilter(
_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));
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);
_crowd.SetObstacleAvoidanceParams(0, option);
// Medium (22)
option.velBias = 0.5f;
option.adaptiveDivs = 5;
option.adaptiveRings = 2;
option.adaptiveDepth = 2;
crowd.SetObstacleAvoidanceParams(1, option);
_crowd.SetObstacleAvoidanceParams(1, option);
// Good (45)
option.velBias = 0.5f;
option.adaptiveDivs = 7;
option.adaptiveRings = 2;
option.adaptiveDepth = 3;
crowd.SetObstacleAvoidanceParams(2, option);
_crowd.SetObstacleAvoidanceParams(2, option);
// High (66)
option.velBias = 0.5f;
option.adaptiveDivs = 7;
option.adaptiveRings = 3;
option.adaptiveDepth = 3;
crowd.SetObstacleAvoidanceParams(3, option);
_crowd.SetObstacleAvoidanceParams(3, option);
}
public void StartProfiling(float agentRadius, float agentHeight, float agentMaxAcceleration, float agentMaxSpeed)
{
if (null == navMesh)
if (null == _navMesh)
return;
rnd = new RcRand(_cfg.randomSeed);
// for benchmark
_updateTimes.Clear();
_samplingUpdateTime = 0;
_curUpdateTime = 0;
_avgUpdateTime = 0;
_minUpdateTime = 0;
_maxUpdateTime = 0;
_rand = new RcRand(_cfg.randomSeed);
CreateCrowd();
CreateZones();
DtNavMeshQuery navquery = new DtNavMeshQuery(navMesh);
DtNavMeshQuery navquery = new DtNavMeshQuery(_navMesh);
IDtQueryFilter filter = new DtQueryDefaultFilter();
for (int i = 0; i < _cfg.agents; i++)
{
float tr = rnd.Next();
float tr = _rand.Next();
RcCrowdAgentType type = RcCrowdAgentType.MOB;
float mobsPcnt = _cfg.percentMobs / 100f;
if (tr > mobsPcnt)
{
tr = rnd.Next();
tr = _rand.Next();
float travellerPcnt = _cfg.percentTravellers / 100f;
if (tr > travellerPcnt)
{
@ -213,19 +232,19 @@ namespace DotRecast.Recast.Toolset.Tools
public void Update(float dt)
{
long startTime = RcFrequency.Ticks;
if (crowd != null)
if (_crowd != null)
{
crowd.Config().pathQueueSize = _cfg.pathQueueSize;
crowd.Config().maxFindPathIterations = _cfg.maxIterations;
crowd.Update(dt, null);
_crowd.Config().pathQueueSize = _cfg.pathQueueSize;
_crowd.Config().maxFindPathIterations = _cfg.maxIterations;
_crowd.Update(dt, null);
}
long endTime = RcFrequency.Ticks;
if (crowd != null)
if (_crowd != null)
{
DtNavMeshQuery navquery = new DtNavMeshQuery(navMesh);
DtNavMeshQuery navquery = new DtNavMeshQuery(_navMesh);
IDtQueryFilter filter = new DtQueryDefaultFilter();
foreach (DtCrowdAgent ag in crowd.GetActiveAgents())
foreach (DtCrowdAgent ag in _crowd.GetActiveAgents())
{
if (NeedsNewTarget(ag))
{
@ -246,20 +265,28 @@ namespace DotRecast.Recast.Toolset.Tools
}
}
crowdUpdateTime = (endTime - startTime) / TimeSpan.TicksPerMillisecond;
var currentTime = endTime - startTime;
_updateTimes.PushBack(currentTime);
// for benchmark
_samplingUpdateTime = _updateTimes.Sum() / TimeSpan.TicksPerMillisecond;
_curUpdateTime = currentTime / TimeSpan.TicksPerMillisecond;
_avgUpdateTime = (long)(_updateTimes.Average() / TimeSpan.TicksPerMillisecond);
_minUpdateTime = _updateTimes.Min() / TimeSpan.TicksPerMillisecond;
_maxUpdateTime = _updateTimes.Max() / TimeSpan.TicksPerMillisecond;
}
private void MoveMob(DtNavMeshQuery navquery, IDtQueryFilter filter, DtCrowdAgent ag, RcCrowdAgentData crowAgentData)
{
// Move somewhere
var status = navquery.FindNearestPoly(ag.npos, crowd.GetQueryExtents(), filter, out var nearestRef, out var nearestPt, out var _);
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,
status = navquery.FindRandomPointAroundCircle(nearestRef, crowAgentData.home, _cfg.zoneRadius * 2f, filter, _rand,
out var randomRef, out var randomPt);
if (status.Succeeded())
{
crowd.RequestMoveTarget(ag, randomRef, randomPt);
_crowd.RequestMoveTarget(ag, randomRef, randomPt);
}
}
}
@ -267,14 +294,14 @@ namespace DotRecast.Recast.Toolset.Tools
private void MoveVillager(DtNavMeshQuery navquery, IDtQueryFilter filter, DtCrowdAgent ag, RcCrowdAgentData crowAgentData)
{
// Move somewhere close
var status = navquery.FindNearestPoly(ag.npos, crowd.GetQueryExtents(), filter, out var nearestRef, out var nearestPt, out var _);
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,
status = navquery.FindRandomPointAroundCircle(nearestRef, crowAgentData.home, _cfg.zoneRadius * 0.2f, filter, _rand,
out var randomRef, out var randomPt);
if (status.Succeeded())
{
crowd.RequestMoveTarget(ag, randomRef, randomPt);
_crowd.RequestMoveTarget(ag, randomRef, randomPt);
}
}
}
@ -294,7 +321,7 @@ namespace DotRecast.Recast.Toolset.Tools
if (0 < potentialTargets.Count)
{
potentialTargets.Shuffle();
crowd.RequestMoveTarget(ag, potentialTargets[0].refs, potentialTargets[0].pt);
_crowd.RequestMoveTarget(ag, potentialTargets[0].refs, potentialTargets[0].pt);
}
}
@ -321,14 +348,14 @@ namespace DotRecast.Recast.Toolset.Tools
{
DtCrowdAgentParams ap = GetAgentParams(agentRadius, agentHeight, agentMaxAcceleration, agentMaxSpeed);
ap.userData = new RcCrowdAgentData(type, p);
return crowd.AddAgent(p, ap);
return _crowd.AddAgent(p, ap);
}
public void UpdateAgentParams()
{
if (crowd != null)
if (_crowd != null)
{
foreach (DtCrowdAgent ag in crowd.GetActiveAgents())
foreach (DtCrowdAgent ag in _crowd.GetActiveAgents())
{
DtCrowdAgentParams option = new DtCrowdAgentParams();
option.radius = ag.option.radius;
@ -342,14 +369,34 @@ namespace DotRecast.Recast.Toolset.Tools
option.updateFlags = _agCfg.GetUpdateFlags();
option.obstacleAvoidanceType = _agCfg.obstacleAvoidanceType;
option.separationWeight = _agCfg.separationWeight;
crowd.UpdateAgentParameters(ag, option);
_crowd.UpdateAgentParameters(ag, option);
}
}
}
public long GetCrowdUpdateSamplingTime()
{
return _samplingUpdateTime;
}
public long GetCrowdUpdateTime()
{
return crowdUpdateTime;
return _curUpdateTime;
}
public long GetCrowdUpdateAvgTime()
{
return _avgUpdateTime;
}
public long GetCrowdUpdateMinTime()
{
return _minUpdateTime;
}
public long GetCrowdUpdateMaxTime()
{
return _maxUpdateTime;
}
}
}

View File

@ -16,8 +16,8 @@ namespace DotRecast.Recast.Toolset.Tools
TOGGLE_POLYS
);
public int Idx { get; }
public string Label { get; }
public readonly int Idx;
public readonly string Label;
private RcCrowdToolMode(int idx, string label)
{

View File

@ -12,8 +12,8 @@ namespace DotRecast.Recast.Toolset.Tools
BUILD, COLLIDERS, RAYCAST
);
public int Idx { get; }
public string Label { get; }
public readonly int Idx;
public readonly string Label;
private RcDynamicUpdateToolMode(int idx, string label)
{

View File

@ -22,30 +22,30 @@ namespace DotRecast.Recast.Toolset.Tools
}
public DtStatus FindFollowPath(DtNavMesh navMesh, DtNavMeshQuery navQuery, long startRef, long endRef, RcVec3f startPt, RcVec3f endPt, IDtQueryFilter filter, bool enableRaycast,
ref List<long> polys, ref List<RcVec3f> smoothPath)
ref List<long> pathIterPolys, ref List<RcVec3f> smoothPath)
{
if (startRef == 0 || endRef == 0)
{
polys?.Clear();
pathIterPolys?.Clear();
smoothPath?.Clear();
return DtStatus.DT_FAILURE;
}
polys ??= new List<long>();
pathIterPolys ??= new List<long>();
smoothPath ??= new List<RcVec3f>();
polys.Clear();
pathIterPolys.Clear();
smoothPath.Clear();
var opt = new DtFindPathOption(enableRaycast ? DtFindPathOptions.DT_FINDPATH_ANY_ANGLE : 0, float.MaxValue);
navQuery.FindPath(startRef, endRef, startPt, endPt, filter, ref polys, opt);
if (0 >= polys.Count)
navQuery.FindPath(startRef, endRef, startPt, endPt, filter, ref pathIterPolys, opt);
if (0 >= pathIterPolys.Count)
return DtStatus.DT_FAILURE;
// Iterate over the path to find smooth path on the detail mesh surface.
navQuery.ClosestPointOnPoly(startRef, startPt, out var iterPos, out var _);
navQuery.ClosestPointOnPoly(polys[polys.Count - 1], endPt, out var targetPos, out var _);
navQuery.ClosestPointOnPoly(pathIterPolys[pathIterPolys.Count - 1], endPt, out var targetPos, out var _);
float STEP_SIZE = 0.5f;
float SLOP = 0.01f;
@ -56,11 +56,11 @@ namespace DotRecast.Recast.Toolset.Tools
// Move towards target a small advancement at a time until target reached or
// when ran out of memory to store the path.
while (0 < polys.Count && smoothPath.Count < MAX_SMOOTH)
while (0 < pathIterPolys.Count && smoothPath.Count < MAX_SMOOTH)
{
// Find location to steer towards.
if (!DtPathUtils.GetSteerTarget(navQuery, iterPos, targetPos, SLOP,
polys, out var steerPos, out var steerPosFlag, out var steerPosRef))
pathIterPolys, out var steerPos, out var steerPosFlag, out var steerPosRef))
{
break;
}
@ -88,14 +88,14 @@ namespace DotRecast.Recast.Toolset.Tools
RcVec3f moveTgt = RcVecUtils.Mad(iterPos, delta, len);
// Move
navQuery.MoveAlongSurface(polys[0], iterPos, moveTgt, filter, out var result, ref visited);
navQuery.MoveAlongSurface(pathIterPolys[0], iterPos, moveTgt, filter, out var result, ref visited);
iterPos = result;
polys = DtPathUtils.MergeCorridorStartMoved(polys, visited);
polys = DtPathUtils.FixupShortcuts(polys, navQuery);
pathIterPolys = DtPathUtils.MergeCorridorStartMoved(pathIterPolys, pathIterPolys.Count, MAX_POLYS, visited);
pathIterPolys = DtPathUtils.FixupShortcuts(pathIterPolys, navQuery);
var status = navQuery.GetPolyHeight(polys[0], result, out var h);
var status = navQuery.GetPolyHeight(pathIterPolys[0], result, out var h);
if (status.Succeeded())
{
iterPos.Y = h;
@ -121,16 +121,16 @@ namespace DotRecast.Recast.Toolset.Tools
// Advance the path up to and over the off-mesh connection.
long prevRef = 0;
long polyRef = polys[0];
long polyRef = pathIterPolys[0];
int npos = 0;
while (npos < polys.Count && polyRef != steerPosRef)
while (npos < pathIterPolys.Count && polyRef != steerPosRef)
{
prevRef = polyRef;
polyRef = polys[npos];
polyRef = pathIterPolys[npos];
npos++;
}
polys = polys.GetRange(npos, polys.Count - npos);
pathIterPolys = pathIterPolys.GetRange(npos, pathIterPolys.Count - npos);
// Handle the connection.
var status2 = navMesh.GetOffMeshConnectionPolyEndPoints(prevRef, polyRef, ref startPos, ref endPos);
@ -148,7 +148,7 @@ namespace DotRecast.Recast.Toolset.Tools
// Move position at the other side of the off-mesh link.
iterPos = endPos;
navQuery.GetPolyHeight(polys[0], iterPos, out var eh);
navQuery.GetPolyHeight(pathIterPolys[0], iterPos, out var eh);
iterPos.Y = eh;
}
}
@ -160,7 +160,7 @@ namespace DotRecast.Recast.Toolset.Tools
}
}
return DtStatus.DT_SUCCSESS;
return DtStatus.DT_SUCCESS;
}
public DtStatus FindStraightPath(DtNavMeshQuery navQuery, long startRef, long endRef, RcVec3f startPt, RcVec3f endPt, IDtQueryFilter filter, bool enableRaycast,
@ -196,7 +196,7 @@ namespace DotRecast.Recast.Toolset.Tools
navQuery.FindStraightPath(startPt, epos, polys, ref straightPath, MAX_POLYS, straightPathOptions);
return DtStatus.DT_SUCCSESS;
return DtStatus.DT_SUCCESS;
}
public DtStatus InitSlicedFindPath(DtNavMeshQuery navQuery, long startRef, long endRef, RcVec3f startPos, RcVec3f endPos, IDtQueryFilter filter, bool enableRaycast)
@ -242,7 +242,7 @@ namespace DotRecast.Recast.Toolset.Tools
navQuery.FindStraightPath(startPos, epos, path, ref straightPath, MAX_POLYS, DtStraightPathOptions.DT_STRAIGHTPATH_ALL_CROSSINGS);
}
return DtStatus.DT_SUCCSESS;
return DtStatus.DT_SUCCESS;
}
@ -257,16 +257,17 @@ namespace DotRecast.Recast.Toolset.Tools
return DtStatus.DT_FAILURE;
}
var status = navQuery.Raycast(startRef, startPos, endPos, filter, 0, 0, out var rayHit);
var path = new List<long>();
var status = navQuery.Raycast(startRef, startPos, endPos, filter, out var t, out var hitNormal2, ref path);
if (!status.Succeeded())
{
return status;
}
// results ...
polys = rayHit.path;
polys = path;
if (rayHit.t > 1)
if (t > 1)
{
// No hit
hitPos = endPos;
@ -275,15 +276,15 @@ namespace DotRecast.Recast.Toolset.Tools
else
{
// Hit
hitPos = RcVec3f.Lerp(startPos, endPos, rayHit.t);
hitNormal = rayHit.hitNormal;
hitPos = RcVec3f.Lerp(startPos, endPos, t);
hitNormal = hitNormal2;
hitResult = true;
}
// Adjust height.
if (rayHit.path.Count > 0)
if (path.Count > 0)
{
var result = navQuery.GetPolyHeight(rayHit.path[rayHit.path.Count - 1], hitPos, out var h);
var result = navQuery.GetPolyHeight(path[path.Count - 1], hitPos, out var h);
if (result.Succeeded())
{
hitPos.Y = h;
@ -438,7 +439,7 @@ namespace DotRecast.Recast.Toolset.Tools
}
}
return DtStatus.DT_SUCCSESS;
return DtStatus.DT_SUCCESS;
}
}
}

View File

@ -27,8 +27,8 @@ namespace DotRecast.Recast.Toolset.Tools
);
public int Idx { get; }
public string Label { get; }
public readonly int Idx;
public readonly string Label;
private RcTestNavmeshToolMode(int idx, string label)
{

View File

@ -1,15 +1,16 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>netstandard2.1;net6.0;net7.0</TargetFrameworks>
<TargetFrameworks>netstandard2.1;net6.0;net7.0;net8.0</TargetFrameworks>
<PackageId>DotRecast.Recast</PackageId>
<PackageReadmeFile>README.md</PackageReadmeFile>
<Authors>ikpil</Authors>
<Description>DotRecast - a port of Recast Detour, navigation mesh toolset for games, Unity3D, servers, C#</Description>
<Description>DotRecast - a port of Recast Detour, Industry-standard navigation mesh toolset for .NET, C#, Unity3D, games, servers</Description>
<RepositoryType>git</RepositoryType>
<PackageProjectUrl>https://github.com/ikpil/DotRecast</PackageProjectUrl>
<RepositoryUrl>https://github.com/ikpil/DotRecast</RepositoryUrl>
<PackageTags>game gamedev ai csharp server unity navigation game-development unity3d pathfinding pathfinder recast detour navmesh crowd-simulation recastnavigation</PackageTags>
<PackageReleaseNotes>https://github.com/ikpil/DotRecast/blob/main/CHANGELOG.md</PackageReleaseNotes>
</PropertyGroup>
<ItemGroup>

View File

@ -20,7 +20,7 @@ freely, subject to the following restrictions:
namespace DotRecast.Recast
{
public class RcAreaModification
public readonly struct RcAreaModification
{
public const int RC_AREA_FLAGS_MASK = 0x3F;
@ -58,12 +58,12 @@ namespace DotRecast.Recast
Mask = other.Mask;
}
public int GetMaskedValue()
public readonly int GetMaskedValue()
{
return Value & Mask;
}
public int Apply(int area)
public readonly int Apply(int area)
{
return ((Value & Mask) | (area & ~Mask));
}

View File

@ -44,7 +44,7 @@ namespace DotRecast.Recast
/// @param[in] erosionRadius The radius of erosion. [Limits: 0 < value < 255] [Units: vx]
/// @param[in,out] compactHeightfield The populated compact heightfield to erode.
/// @returns True if the operation completed successfully.
public static void ErodeWalkableArea(RcTelemetry context, int erosionRadius, RcCompactHeightfield compactHeightfield)
public static void ErodeWalkableArea(RcContext context, int erosionRadius, RcCompactHeightfield compactHeightfield)
{
int xSize = compactHeightfield.width;
int zSize = compactHeightfield.height;
@ -60,7 +60,7 @@ namespace DotRecast.Recast
{
for (int x = 0; x < xSize; ++x)
{
RcCompactCell cell = compactHeightfield.cells[x + z * zStride];
ref RcCompactCell cell = ref compactHeightfield.cells[x + z * zStride];
for (int spanIndex = cell.index, maxSpanIndex = cell.index + cell.count; spanIndex < maxSpanIndex; ++spanIndex)
{
if (compactHeightfield.areas[spanIndex] == RC_NULL_AREA)
@ -69,13 +69,13 @@ namespace DotRecast.Recast
}
else
{
RcCompactSpan span = compactHeightfield.spans[spanIndex];
ref RcCompactSpan span = ref compactHeightfield.spans[spanIndex];
// Check that there is a non-null adjacent span in each of the 4 cardinal directions.
int neighborCount = 0;
for (int direction = 0; direction < 4; ++direction)
{
int neighborConnection = GetCon(span, direction);
int neighborConnection = GetCon(ref span, direction);
if (neighborConnection == RC_NOT_CONNECTED)
{
break;
@ -83,7 +83,7 @@ namespace DotRecast.Recast
int neighborX = x + GetDirOffsetX(direction);
int neighborZ = z + GetDirOffsetY(direction);
int neighborSpanIndex = compactHeightfield.cells[neighborX + neighborZ * zStride].index + GetCon(span, direction);
int neighborSpanIndex = compactHeightfield.cells[neighborX + neighborZ * zStride].index + GetCon(ref span, direction);
if (compactHeightfield.areas[neighborSpanIndex] == RC_NULL_AREA)
{
break;
@ -109,19 +109,19 @@ namespace DotRecast.Recast
{
for (int x = 0; x < xSize; ++x)
{
RcCompactCell cell = compactHeightfield.cells[x + z * zStride];
ref RcCompactCell cell = ref compactHeightfield.cells[x + z * zStride];
int maxSpanIndex = cell.index + cell.count;
for (int spanIndex = cell.index; spanIndex < maxSpanIndex; ++spanIndex)
{
RcCompactSpan span = compactHeightfield.spans[spanIndex];
ref RcCompactSpan span = ref compactHeightfield.spans[spanIndex];
if (GetCon(span, 0) != RC_NOT_CONNECTED)
if (GetCon(ref span, 0) != RC_NOT_CONNECTED)
{
// (-1,0)
int aX = x + GetDirOffsetX(0);
int aY = z + GetDirOffsetY(0);
int aIndex = compactHeightfield.cells[aX + aY * xSize].index + GetCon(span, 0);
RcCompactSpan aSpan = compactHeightfield.spans[aIndex];
int aIndex = compactHeightfield.cells[aX + aY * xSize].index + GetCon(ref span, 0);
ref RcCompactSpan aSpan = ref compactHeightfield.spans[aIndex];
newDistance = Math.Min(distanceToBoundary[aIndex] + 2, 255);
if (newDistance < distanceToBoundary[spanIndex])
{
@ -129,11 +129,11 @@ namespace DotRecast.Recast
}
// (-1,-1)
if (GetCon(aSpan, 3) != RC_NOT_CONNECTED)
if (GetCon(ref aSpan, 3) != RC_NOT_CONNECTED)
{
int bX = aX + GetDirOffsetX(3);
int bY = aY + GetDirOffsetY(3);
int bIndex = compactHeightfield.cells[bX + bY * xSize].index + GetCon(aSpan, 3);
int bIndex = compactHeightfield.cells[bX + bY * xSize].index + GetCon(ref aSpan, 3);
newDistance = Math.Min(distanceToBoundary[bIndex] + 3, 255);
if (newDistance < distanceToBoundary[spanIndex])
{
@ -142,13 +142,13 @@ namespace DotRecast.Recast
}
}
if (GetCon(span, 3) != RC_NOT_CONNECTED)
if (GetCon(ref span, 3) != RC_NOT_CONNECTED)
{
// (0,-1)
int aX = x + GetDirOffsetX(3);
int aY = z + GetDirOffsetY(3);
int aIndex = compactHeightfield.cells[aX + aY * xSize].index + GetCon(span, 3);
RcCompactSpan aSpan = compactHeightfield.spans[aIndex];
int aIndex = compactHeightfield.cells[aX + aY * xSize].index + GetCon(ref span, 3);
ref RcCompactSpan aSpan = ref compactHeightfield.spans[aIndex];
newDistance = Math.Min(distanceToBoundary[aIndex] + 2, 255);
if (newDistance < distanceToBoundary[spanIndex])
{
@ -156,11 +156,11 @@ namespace DotRecast.Recast
}
// (1,-1)
if (GetCon(aSpan, 2) != RC_NOT_CONNECTED)
if (GetCon(ref aSpan, 2) != RC_NOT_CONNECTED)
{
int bX = aX + GetDirOffsetX(2);
int bY = aY + GetDirOffsetY(2);
int bIndex = compactHeightfield.cells[bX + bY * xSize].index + GetCon(aSpan, 2);
int bIndex = compactHeightfield.cells[bX + bY * xSize].index + GetCon(ref aSpan, 2);
newDistance = Math.Min(distanceToBoundary[bIndex] + 3, 255);
if (newDistance < distanceToBoundary[spanIndex])
{
@ -177,19 +177,19 @@ namespace DotRecast.Recast
{
for (int x = xSize - 1; x >= 0; --x)
{
RcCompactCell cell = compactHeightfield.cells[x + z * zStride];
ref RcCompactCell cell = ref compactHeightfield.cells[x + z * zStride];
int maxSpanIndex = cell.index + cell.count;
for (int i = cell.index; i < maxSpanIndex; ++i)
{
RcCompactSpan span = compactHeightfield.spans[i];
ref RcCompactSpan span = ref compactHeightfield.spans[i];
if (GetCon(span, 2) != RC_NOT_CONNECTED)
if (GetCon(ref span, 2) != RC_NOT_CONNECTED)
{
// (1,0)
int aX = x + GetDirOffsetX(2);
int aY = z + GetDirOffsetY(2);
int aIndex = compactHeightfield.cells[aX + aY * xSize].index + GetCon(span, 2);
RcCompactSpan aSpan = compactHeightfield.spans[aIndex];
int aIndex = compactHeightfield.cells[aX + aY * xSize].index + GetCon(ref span, 2);
ref RcCompactSpan aSpan = ref compactHeightfield.spans[aIndex];
newDistance = Math.Min(distanceToBoundary[aIndex] + 2, 255);
if (newDistance < distanceToBoundary[i])
{
@ -197,11 +197,11 @@ namespace DotRecast.Recast
}
// (1,1)
if (GetCon(aSpan, 1) != RC_NOT_CONNECTED)
if (GetCon(ref aSpan, 1) != RC_NOT_CONNECTED)
{
int bX = aX + GetDirOffsetX(1);
int bY = aY + GetDirOffsetY(1);
int bIndex = compactHeightfield.cells[bX + bY * xSize].index + GetCon(aSpan, 1);
int bIndex = compactHeightfield.cells[bX + bY * xSize].index + GetCon(ref aSpan, 1);
newDistance = Math.Min(distanceToBoundary[bIndex] + 3, 255);
if (newDistance < distanceToBoundary[i])
{
@ -210,13 +210,13 @@ namespace DotRecast.Recast
}
}
if (GetCon(span, 1) != RC_NOT_CONNECTED)
if (GetCon(ref span, 1) != RC_NOT_CONNECTED)
{
// (0,1)
int aX = x + GetDirOffsetX(1);
int aY = z + GetDirOffsetY(1);
int aIndex = compactHeightfield.cells[aX + aY * xSize].index + GetCon(span, 1);
RcCompactSpan aSpan = compactHeightfield.spans[aIndex];
int aIndex = compactHeightfield.cells[aX + aY * xSize].index + GetCon(ref span, 1);
ref RcCompactSpan aSpan = ref compactHeightfield.spans[aIndex];
newDistance = Math.Min(distanceToBoundary[aIndex] + 2, 255);
if (newDistance < distanceToBoundary[i])
{
@ -224,11 +224,11 @@ namespace DotRecast.Recast
}
// (-1,1)
if (GetCon(aSpan, 0) != RC_NOT_CONNECTED)
if (GetCon(ref aSpan, 0) != RC_NOT_CONNECTED)
{
int bX = aX + GetDirOffsetX(0);
int bY = aY + GetDirOffsetY(0);
int bIndex = compactHeightfield.cells[bX + bY * xSize].index + GetCon(aSpan, 0);
int bIndex = compactHeightfield.cells[bX + bY * xSize].index + GetCon(ref aSpan, 0);
newDistance = Math.Min(distanceToBoundary[bIndex] + 3, 255);
if (newDistance < distanceToBoundary[i])
{
@ -261,7 +261,7 @@ namespace DotRecast.Recast
/// @param[in,out] context The build context to use during the operation.
/// @param[in,out] compactHeightfield A populated compact heightfield.
/// @returns True if the operation completed successfully.
public static bool MedianFilterWalkableArea(RcTelemetry context, RcCompactHeightfield compactHeightfield)
public static bool MedianFilterWalkableArea(RcContext context, RcCompactHeightfield compactHeightfield)
{
int xSize = compactHeightfield.width;
int zSize = compactHeightfield.height;
@ -275,11 +275,11 @@ namespace DotRecast.Recast
{
for (int x = 0; x < xSize; ++x)
{
RcCompactCell cell = compactHeightfield.cells[x + z * zStride];
ref RcCompactCell cell = ref compactHeightfield.cells[x + z * zStride];
int maxSpanIndex = cell.index + cell.count;
for (int spanIndex = cell.index; spanIndex < maxSpanIndex; ++spanIndex)
{
RcCompactSpan span = compactHeightfield.spans[spanIndex];
ref RcCompactSpan span = ref compactHeightfield.spans[spanIndex];
if (compactHeightfield.areas[spanIndex] == RC_NULL_AREA)
{
areas[spanIndex] = compactHeightfield.areas[spanIndex];
@ -294,27 +294,27 @@ namespace DotRecast.Recast
for (int dir = 0; dir < 4; ++dir)
{
if (GetCon(span, dir) == RC_NOT_CONNECTED)
if (GetCon(ref span, dir) == RC_NOT_CONNECTED)
{
continue;
}
int aX = x + GetDirOffsetX(dir);
int aZ = z + GetDirOffsetY(dir);
int aIndex = compactHeightfield.cells[aX + aZ * zStride].index + GetCon(span, dir);
int aIndex = compactHeightfield.cells[aX + aZ * zStride].index + GetCon(ref span, dir);
if (compactHeightfield.areas[aIndex] != RC_NULL_AREA)
{
neighborAreas[dir * 2 + 0] = compactHeightfield.areas[aIndex];
}
RcCompactSpan aSpan = compactHeightfield.spans[aIndex];
ref RcCompactSpan aSpan = ref compactHeightfield.spans[aIndex];
int dir2 = (dir + 1) & 0x3;
int neighborConnection2 = GetCon(aSpan, dir2);
int neighborConnection2 = GetCon(ref aSpan, dir2);
if (neighborConnection2 != RC_NOT_CONNECTED)
{
int bX = aX + GetDirOffsetX(dir2);
int bZ = aZ + GetDirOffsetY(dir2);
int bIndex = compactHeightfield.cells[bX + bZ * zStride].index + GetCon(aSpan, dir2);
int bIndex = compactHeightfield.cells[bX + bZ * zStride].index + GetCon(ref aSpan, dir2);
if (compactHeightfield.areas[bIndex] != RC_NULL_AREA)
{
neighborAreas[dir * 2 + 1] = compactHeightfield.areas[bIndex];
@ -344,7 +344,7 @@ namespace DotRecast.Recast
/// @param[in] boxMaxBounds The maximum extents of the bounding box. [(x, y, z)] [Units: wu]
/// @param[in] areaId The area id to apply. [Limit: <= #RC_WALKABLE_AREA]
/// @param[in,out] compactHeightfield A populated compact heightfield.
public static void MarkBoxArea(RcTelemetry context, float[] boxMinBounds, float[] boxMaxBounds, RcAreaModification areaId, RcCompactHeightfield compactHeightfield)
public static void MarkBoxArea(RcContext context, float[] boxMinBounds, float[] boxMaxBounds, RcAreaModification areaId, RcCompactHeightfield compactHeightfield)
{
using var timer = context.ScopedTimer(RcTimerLabel.RC_TIMER_MARK_BOX_AREA);
@ -404,11 +404,11 @@ namespace DotRecast.Recast
{
for (int x = minX; x <= maxX; ++x)
{
RcCompactCell cell = compactHeightfield.cells[x + z * zStride];
ref RcCompactCell cell = ref compactHeightfield.cells[x + z * zStride];
int maxSpanIndex = cell.index + cell.count;
for (int spanIndex = cell.index; spanIndex < maxSpanIndex; ++spanIndex)
{
RcCompactSpan span = compactHeightfield.spans[spanIndex];
ref RcCompactSpan span = ref compactHeightfield.spans[spanIndex];
// Skip if the span is outside the box extents.
if (span.y < minY || span.y > maxY)
@ -446,7 +446,7 @@ namespace DotRecast.Recast
/// @param[in] maxY The height of the top of the polygon. [Units: wu]
/// @param[in] areaId The area id to apply. [Limit: <= #RC_WALKABLE_AREA]
/// @param[in,out] compactHeightfield A populated compact heightfield.
public static void MarkConvexPolyArea(RcTelemetry context, float[] verts,
public static void MarkConvexPolyArea(RcContext context, float[] verts,
float minY, float maxY, RcAreaModification areaId,
RcCompactHeightfield compactHeightfield)
{
@ -523,11 +523,11 @@ namespace DotRecast.Recast
{
for (int x = minx; x <= maxx; ++x)
{
RcCompactCell cell = compactHeightfield.cells[x + z * zStride];
ref RcCompactCell cell = ref compactHeightfield.cells[x + z * zStride];
int maxSpanIndex = cell.index + cell.count;
for (int spanIndex = cell.index; spanIndex < maxSpanIndex; ++spanIndex)
{
RcCompactSpan span = compactHeightfield.spans[spanIndex];
ref RcCompactSpan span = ref compactHeightfield.spans[spanIndex];
// Skip if span is removed.
if (compactHeightfield.areas[spanIndex] == RC_NULL_AREA)
@ -567,7 +567,7 @@ namespace DotRecast.Recast
/// @param[in] height The height of the cylinder. [Units: wu] [Limit: > 0]
/// @param[in] areaId The area id to apply. [Limit: <= #RC_WALKABLE_AREA]
/// @param[in,out] compactHeightfield A populated compact heightfield.
public static void MarkCylinderArea(RcTelemetry context, float[] position, float radius, float height,
public static void MarkCylinderArea(RcContext context, float[] position, float radius, float height,
RcAreaModification areaId, RcCompactHeightfield compactHeightfield)
{
using var timer = context.ScopedTimer(RcTimerLabel.RC_TIMER_MARK_CYLINDER_AREA);
@ -644,7 +644,7 @@ namespace DotRecast.Recast
{
for (int x = minx; x <= maxx; ++x)
{
RcCompactCell cell = compactHeightfield.cells[x + z * zStride];
ref RcCompactCell cell = ref compactHeightfield.cells[x + z * zStride];
int maxSpanIndex = cell.index + cell.count;
float cellX = compactHeightfield.bmin.X + ((float)x + 0.5f) * compactHeightfield.cs;
@ -661,7 +661,7 @@ namespace DotRecast.Recast
// Mark all overlapping spans
for (int spanIndex = cell.index; spanIndex < maxSpanIndex; ++spanIndex)
{
RcCompactSpan span = compactHeightfield.spans[spanIndex];
ref RcCompactSpan span = ref compactHeightfield.spans[spanIndex];
// Skip if span is removed.
if (compactHeightfield.areas[spanIndex] == RC_NULL_AREA)

View File

@ -0,0 +1,9 @@
namespace DotRecast.Recast
{
public static class RcAxis
{
public const int RC_AXIS_X = 0;
public const int RC_AXIS_Y = 1;
public const int RC_AXIS_Z = 2;
};
}

View File

@ -162,62 +162,50 @@ namespace DotRecast.Recast
public RcBuilderResult Build(IInputGeomProvider geom, RcBuilderConfig builderCfg)
{
RcConfig cfg = builderCfg.cfg;
RcTelemetry ctx = new RcTelemetry();
RcContext ctx = new RcContext();
//
// Step 1. Rasterize input polygon soup.
//
RcHeightfield solid = RcVoxelizations.BuildSolidHeightfield(geom, builderCfg, ctx);
return Build(builderCfg.tileX, builderCfg.tileZ, geom, cfg, solid, ctx);
RcHeightfield solid = RcVoxelizations.BuildSolidHeightfield(ctx, geom, builderCfg);
return Build(ctx, builderCfg.tileX, builderCfg.tileZ, geom, cfg, solid);
}
public RcBuilderResult Build(int tileX, int tileZ, IInputGeomProvider geom, RcConfig cfg, RcHeightfield solid, RcTelemetry ctx)
public RcBuilderResult Build(RcContext ctx, int tileX, int tileZ, IInputGeomProvider geom, RcConfig cfg, RcHeightfield solid)
{
FilterHeightfield(solid, cfg, ctx);
RcCompactHeightfield chf = BuildCompactHeightfield(geom, cfg, ctx, solid);
FilterHeightfield(ctx, solid, cfg);
RcCompactHeightfield chf = BuildCompactHeightfield(ctx, geom, cfg, solid);
// Partition the heightfield so that we can use simple algorithm later
// to triangulate the walkable areas.
// Partition the heightfield so that we can use simple algorithm later to triangulate the walkable areas.
// There are 3 partitioning methods, each with some pros and cons:
// 1) Watershed partitioning
// - the classic Recast partitioning
// - creates the nicest tessellation
// - usually slowest
// - partitions the heightfield into nice regions without holes or
// overlaps
// - the are some corner cases where this method creates produces holes
// and overlaps
// - holes may appear when a small obstacles is close to large open area
// (triangulation can handle this)
// - overlaps may occur if you have narrow spiral corridors (i.e
// stairs), this make triangulation to fail
// * generally the best choice if you precompute the navmesh, use this
// if you have large open areas
// 2) Monotone partioning
// - fastest
// - partitions the heightfield into regions without holes and overlaps
// (guaranteed)
// - creates long thin polygons, which sometimes causes paths with
// detours
// * use this if you want fast navmesh generation
// - the classic Recast partitioning
// - creates the nicest tessellation
// - usually slowest
// - partitions the heightfield into nice regions without holes or overlaps
// - the are some corner cases where this method creates produces holes and overlaps
// - holes may appear when a small obstacles is close to large open area (triangulation can handle this)
// - overlaps may occur if you have narrow spiral corridors (i.e stairs), this make triangulation to fail
// * generally the best choice if you precompute the navmesh, use this if you have large open areas
// 2) Monotone partitioning
// - fastest
// - partitions the heightfield into regions without holes and overlaps (guaranteed)
// - creates long thin polygons, which sometimes causes paths with detours
// * use this if you want fast navmesh generation
// 3) Layer partitoining
// - quite fast
// - partitions the heighfield into non-overlapping regions
// - relies on the triangulation code to cope with holes (thus slower
// than monotone partitioning)
// - produces better triangles than monotone partitioning
// - does not have the corner cases of watershed partitioning
// - can be slow and create a bit ugly tessellation (still better than
// monotone)
// if you have large open areas with small obstacles (not a problem if
// you use tiles)
// * good choice to use for tiled navmesh with medium and small sized
// tiles
// - quite fast
// - partitions the heighfield into non-overlapping regions
// - relies on the triangulation code to cope with holes (thus slower than monotone partitioning)
// - produces better triangles than monotone partitioning
// - does not have the corner cases of watershed partitioning
// - can be slow and create a bit ugly tessellation (still better than monotone)
// if you have large open areas with small obstacles (not a problem if you use tiles)
// * good choice to use for tiled navmesh with medium and small sized tiles
if (cfg.Partition == RcPartitionType.WATERSHED.Value)
{
// Prepare for region partitioning, by calculating distance field
// along the walkable surface.
// Prepare for region partitioning, by calculating distance field along the walkable surface.
RcRegions.BuildDistanceField(ctx, chf);
// Partition the walkable surface into simple regions without holes.
RcRegions.BuildRegions(ctx, chf, cfg.MinRegionArea, cfg.MergeRegionArea);
}
@ -259,7 +247,7 @@ namespace DotRecast.Recast
/*
* Step 2. Filter walkable surfaces.
*/
private void FilterHeightfield(RcHeightfield solid, RcConfig cfg, RcTelemetry ctx)
private void FilterHeightfield(RcContext ctx, RcHeightfield solid, RcConfig cfg)
{
// Once all geometry is rasterized, we do initial pass of filtering to
// remove unwanted overhangs caused by the conservative rasterization
@ -283,7 +271,7 @@ namespace DotRecast.Recast
/*
* Step 3. Partition walkable surface to simple regions.
*/
private RcCompactHeightfield BuildCompactHeightfield(IInputGeomProvider geom, RcConfig cfg, RcTelemetry ctx, RcHeightfield solid)
private RcCompactHeightfield BuildCompactHeightfield(RcContext ctx, IInputGeomProvider geom, RcConfig cfg, RcHeightfield solid)
{
// Compact the heightfield so that it is faster to handle from now on.
// This will result more cache coherent data as well as the neighbours
@ -306,11 +294,13 @@ namespace DotRecast.Recast
public RcHeightfieldLayerSet BuildLayers(IInputGeomProvider geom, RcBuilderConfig builderCfg)
{
RcTelemetry ctx = new RcTelemetry();
RcHeightfield solid = RcVoxelizations.BuildSolidHeightfield(geom, builderCfg, ctx);
FilterHeightfield(solid, builderCfg.cfg, ctx);
RcCompactHeightfield chf = BuildCompactHeightfield(geom, builderCfg.cfg, ctx, solid);
return RcLayers.BuildHeightfieldLayers(ctx, chf, builderCfg.cfg.WalkableHeight);
RcContext ctx = new RcContext();
RcHeightfield solid = RcVoxelizations.BuildSolidHeightfield(ctx, geom, builderCfg);
FilterHeightfield(ctx, solid, builderCfg.cfg);
RcCompactHeightfield chf = BuildCompactHeightfield(ctx, geom, builderCfg.cfg, solid);
RcLayers.BuildHeightfieldLayers(ctx, chf, builderCfg.cfg.BorderSize, builderCfg.cfg.WalkableHeight, out var lset);
return lset;
}
}
}

View File

@ -12,9 +12,9 @@ namespace DotRecast.Recast
private readonly RcPolyMesh pmesh;
private readonly RcPolyMeshDetail dmesh;
private readonly RcHeightfield solid;
private readonly RcTelemetry telemetry;
private readonly RcContext _context;
public RcBuilderResult(int tileX, int tileZ, RcHeightfield solid, RcCompactHeightfield chf, RcContourSet cs, RcPolyMesh pmesh, RcPolyMeshDetail dmesh, RcTelemetry ctx)
public RcBuilderResult(int tileX, int tileZ, RcHeightfield solid, RcCompactHeightfield chf, RcContourSet cs, RcPolyMesh pmesh, RcPolyMeshDetail dmesh, RcContext ctx)
{
this.tileX = tileX;
this.tileZ = tileZ;
@ -23,7 +23,7 @@ namespace DotRecast.Recast
this.cs = cs;
this.pmesh = pmesh;
this.dmesh = dmesh;
telemetry = ctx;
_context = ctx;
}
public RcPolyMesh GetMesh()
@ -51,9 +51,9 @@ namespace DotRecast.Recast
return solid;
}
public RcTelemetry GetTelemetry()
public RcContext GetTelemetry()
{
return telemetry;
return _context;
}
}
}

View File

@ -36,7 +36,7 @@ namespace DotRecast.Recast
/// @param[in] span The span to update.
/// @param[in] direction The direction to set. [Limits: 0 <= value < 4]
/// @param[in] neighborIndex The index of the neighbor span.
public static void SetCon(RcCompactSpan span, int direction, int neighborIndex)
public static void SetCon(RcCompactSpanBuilder span, int direction, int neighborIndex)
{
int shift = direction * 6;
int con = span.con;
@ -47,7 +47,7 @@ namespace DotRecast.Recast
/// @param[in] span The span to check.
/// @param[in] direction The direction to check. [Limits: 0 <= value < 4]
/// @return The neighbor connection data for the specified direction, or #RC_NOT_CONNECTED if there is no connection.
public static int GetCon(RcCompactSpan s, int dir)
public static int GetCon(ref RcCompactSpan s, int dir)
{
int shift = dir * 6;
return (s.con >> shift) & 0x3f;
@ -119,7 +119,7 @@ namespace DotRecast.Recast
/// See the #rcConfig documentation for more information on the configuration parameters.
///
/// @see rcHeightfield, rcClearUnwalkableTriangles, rcRasterizeTriangles
public static int[] MarkWalkableTriangles(RcTelemetry ctx, float walkableSlopeAngle, float[] verts, int[] tris, int nt, RcAreaModification areaMod)
public static int[] MarkWalkableTriangles(RcContext ctx, float walkableSlopeAngle, float[] verts, int[] tris, int nt, RcAreaModification areaMod)
{
int[] areas = new int[nt];
float walkableThr = MathF.Cos(walkableSlopeAngle / 180.0f * MathF.PI);
@ -153,7 +153,7 @@ namespace DotRecast.Recast
/// See the #rcConfig documentation for more information on the configuration parameters.
///
/// @see rcHeightfield, rcClearUnwalkableTriangles, rcRasterizeTriangles
public static void ClearUnwalkableTriangles(RcTelemetry ctx, float walkableSlopeAngle, float[] verts, int nv, int[] tris, int nt, int[] areas)
public static void ClearUnwalkableTriangles(RcContext ctx, float walkableSlopeAngle, float[] verts, int nv, int[] tris, int nt, int[] areas)
{
float walkableThr = MathF.Cos(walkableSlopeAngle / 180.0f * MathF.PI);

View File

@ -21,18 +21,26 @@ freely, subject to the following restrictions:
namespace DotRecast.Recast
{
/** Represents a span of unobstructed space within a compact heightfield. */
public class RcCompactSpan
public readonly struct RcCompactSpan
{
/** The lower extent of the span. (Measured from the heightfield's base.) */
public int y;
public readonly int y;
/** The id of the region the span belongs to. (Or zero if not in a region.) */
public int reg;
public readonly int reg;
/** Packed neighbor connection data. */
public int con;
public readonly int con;
/** The height of the span. (Measured from #y.) */
public int h;
public readonly int h;
public RcCompactSpan(RcCompactSpanBuilder span)
{
y = span.y;
reg = span.reg;
con = span.con;
h = span.h;
}
}
}

View File

@ -0,0 +1,40 @@
namespace DotRecast.Recast
{
public class RcCompactSpanBuilder
{
public int y;
public int reg;
public int con;
public int h;
public static RcCompactSpanBuilder NewBuilder(ref RcCompactSpan span)
{
var builder = NewBuilder();
builder.y = span.y;
builder.reg = span.reg;
builder.con = span.con;
builder.h = span.h;
return builder;
}
public static RcCompactSpanBuilder NewBuilder()
{
return new RcCompactSpanBuilder();
}
private RcCompactSpanBuilder()
{
}
public RcCompactSpanBuilder WithReg(int reg)
{
this.reg = reg;
return this;
}
public RcCompactSpan Build()
{
return new RcCompactSpan(this);
}
}
}

View File

@ -18,6 +18,7 @@ freely, subject to the following restrictions:
*/
using System;
using System.Linq;
using DotRecast.Core;
using static DotRecast.Recast.RcConstants;
@ -29,10 +30,13 @@ namespace DotRecast.Recast
public static class RcCompacts
{
private const int MAX_LAYERS = RC_NOT_CONNECTED - 1;
private const int MAX_HEIGHT = RcConstants.SPAN_MAX_HEIGHT;
private const int MAX_HEIGHT = RcConstants.RC_SPAN_MAX_HEIGHT;
/// @par
/// @}
/// @name Compact Heightfield Functions
/// @see rcCompactHeightfield
/// @{
/// Builds a compact heightfield representing open space, from a heightfield representing solid space.
///
/// This is just the beginning of the process of fully building a compact heightfield.
/// Various filters may be applied, then the distance field and regions built.
@ -41,113 +45,123 @@ namespace DotRecast.Recast
/// See the #rcConfig documentation for more information on the configuration parameters.
///
/// @see rcAllocCompactHeightfield, rcHeightfield, rcCompactHeightfield, rcConfig
public static RcCompactHeightfield BuildCompactHeightfield(RcTelemetry ctx, int walkableHeight, int walkableClimb,
RcHeightfield hf)
/// @ingroup recast
///
/// @param[in,out] context The build context to use during the operation.
/// @param[in] walkableHeight Minimum floor to 'ceiling' height that will still allow the floor area
/// to be considered walkable. [Limit: >= 3] [Units: vx]
/// @param[in] walkableClimb Maximum ledge height that is considered to still be traversable.
/// [Limit: >=0] [Units: vx]
/// @param[in] heightfield The heightfield to be compacted.
/// @param[out] compactHeightfield The resulting compact heightfield. (Must be pre-allocated.)
/// @returns True if the operation completed successfully.
public static RcCompactHeightfield BuildCompactHeightfield(RcContext context, int walkableHeight, int walkableClimb, RcHeightfield heightfield)
{
using var timer = ctx.ScopedTimer(RcTimerLabel.RC_TIMER_BUILD_COMPACTHEIGHTFIELD);
using var timer = context.ScopedTimer(RcTimerLabel.RC_TIMER_BUILD_COMPACTHEIGHTFIELD);
RcCompactHeightfield chf = new RcCompactHeightfield();
int w = hf.width;
int h = hf.height;
int spanCount = GetHeightFieldSpanCount(hf);
int xSize = heightfield.width;
int zSize = heightfield.height;
int spanCount = GetHeightFieldSpanCount(context, heightfield);
// Fill in header.
chf.width = w;
chf.height = h;
chf.borderSize = hf.borderSize;
chf.spanCount = spanCount;
chf.walkableHeight = walkableHeight;
chf.walkableClimb = walkableClimb;
chf.maxRegions = 0;
chf.bmin = hf.bmin;
chf.bmax = hf.bmax;
chf.bmax.Y += walkableHeight * hf.ch;
chf.cs = hf.cs;
chf.ch = hf.ch;
chf.cells = new RcCompactCell[w * h];
chf.spans = new RcCompactSpan[spanCount];
chf.areas = new int[spanCount];
RcCompactHeightfield compactHeightfield = new RcCompactHeightfield();
compactHeightfield.width = xSize;
compactHeightfield.height = zSize;
compactHeightfield.borderSize = heightfield.borderSize;
compactHeightfield.spanCount = spanCount;
compactHeightfield.walkableHeight = walkableHeight;
compactHeightfield.walkableClimb = walkableClimb;
compactHeightfield.maxRegions = 0;
compactHeightfield.bmin = heightfield.bmin;
compactHeightfield.bmax = heightfield.bmax;
compactHeightfield.bmax.Y += walkableHeight * heightfield.ch;
compactHeightfield.cs = heightfield.cs;
compactHeightfield.ch = heightfield.ch;
compactHeightfield.cells = new RcCompactCell[xSize * zSize];
//chf.spans = new RcCompactSpan[spanCount];
compactHeightfield.areas = new int[spanCount];
for (int i = 0; i < chf.spans.Length; i++)
{
chf.spans[i] = new RcCompactSpan();
}
var tempSpans = Enumerable
.Range(0, spanCount)
.Select(x => RcCompactSpanBuilder.NewBuilder())
.ToArray();
// Fill in cells and spans.
int idx = 0;
for (int y = 0; y < h; ++y)
int currentCellIndex = 0;
int numColumns = xSize * zSize;
for (int columnIndex = 0; columnIndex < numColumns; ++columnIndex)
{
for (int x = 0; x < w; ++x)
RcSpan span = heightfield.spans[columnIndex];
// If there are no spans at this cell, just leave the data to index=0, count=0.
if (span == null)
continue;
int tmpIdx = currentCellIndex;
int tmpCount = 0;
for (; span != null; span = span.next)
{
RcSpan s = hf.spans[x + y * w];
// If there are no spans at this cell, just leave the data to index=0, count=0.
if (s == null)
continue;
int tmpIdx = idx;
int tmpCount = 0;
while (s != null)
if (span.area != RC_NULL_AREA)
{
if (s.area != RC_NULL_AREA)
{
int bot = s.smax;
int top = s.next != null ? (int)s.next.smin : MAX_HEIGHT;
chf.spans[idx].y = Math.Clamp(bot, 0, MAX_HEIGHT);
chf.spans[idx].h = Math.Clamp(top - bot, 0, MAX_HEIGHT);
chf.areas[idx] = s.area;
idx++;
tmpCount++;
}
s = s.next;
int bot = span.smax;
int top = span.next != null ? (int)span.next.smin : MAX_HEIGHT;
tempSpans[currentCellIndex].y = Math.Clamp(bot, 0, MAX_HEIGHT);
tempSpans[currentCellIndex].h = Math.Clamp(top - bot, 0, MAX_HEIGHT);
compactHeightfield.areas[currentCellIndex] = span.area;
currentCellIndex++;
tmpCount++;
}
chf.cells[x + y * w] = new RcCompactCell(tmpIdx, tmpCount);
}
compactHeightfield.cells[columnIndex] = new RcCompactCell(tmpIdx, tmpCount);
}
// Find neighbour connections.
int tooHighNeighbour = 0;
for (int y = 0; y < h; ++y)
const int MAX_LAYERS = RC_NOT_CONNECTED - 1;
int maxLayerIndex = 0;
int zStride = xSize; // for readability
for (int z = 0; z < zSize; ++z)
{
for (int x = 0; x < w; ++x)
for (int x = 0; x < xSize; ++x)
{
RcCompactCell c = chf.cells[x + y * w];
for (int i = c.index, ni = c.index + c.count; i < ni; ++i)
ref RcCompactCell cell = ref compactHeightfield.cells[x + z * zStride];
for (int i = cell.index, ni = cell.index + cell.count; i < ni; ++i)
{
RcCompactSpan s = chf.spans[i];
ref RcCompactSpanBuilder s = ref tempSpans[i];
for (int dir = 0; dir < 4; ++dir)
{
SetCon(s, dir, RC_NOT_CONNECTED);
int nx = x + GetDirOffsetX(dir);
int ny = y + GetDirOffsetY(dir);
int neighborX = x + GetDirOffsetX(dir);
int neighborZ = z + GetDirOffsetY(dir);
// First check that the neighbour cell is in bounds.
if (nx < 0 || ny < 0 || nx >= w || ny >= h)
if (neighborX < 0 || neighborZ < 0 || neighborX >= xSize || neighborZ >= zSize)
{
continue;
}
// Iterate over all neighbour spans and check if any of the is
// accessible from current cell.
RcCompactCell nc = chf.cells[nx + ny * w];
for (int k = nc.index, nk = nc.index + nc.count; k < nk; ++k)
ref RcCompactCell neighborCell = ref compactHeightfield.cells[neighborX + neighborZ * xSize];
for (int k = neighborCell.index, nk = neighborCell.index + neighborCell.count; k < nk; ++k)
{
RcCompactSpan ns = chf.spans[k];
int bot = Math.Max(s.y, ns.y);
int top = Math.Min(s.y + s.h, ns.y + ns.h);
ref RcCompactSpanBuilder neighborSpan = ref tempSpans[k];
int bot = Math.Max(s.y, neighborSpan.y);
int top = Math.Min(s.y + s.h, neighborSpan.y + neighborSpan.h);
// Check that the gap between the spans is walkable,
// and that the climb height between the gaps is not too high.
if ((top - bot) >= walkableHeight && MathF.Abs(ns.y - s.y) <= walkableClimb)
if ((top - bot) >= walkableHeight && MathF.Abs(neighborSpan.y - s.y) <= walkableClimb)
{
// Mark direction as walkable.
int lidx = k - nc.index;
if (lidx < 0 || lidx > MAX_LAYERS)
int layerIndex = k - neighborCell.index;
if (layerIndex < 0 || layerIndex > MAX_LAYERS)
{
tooHighNeighbour = Math.Max(tooHighNeighbour, lidx);
maxLayerIndex = Math.Max(maxLayerIndex, layerIndex);
continue;
}
SetCon(s, dir, lidx);
SetCon(s, dir, layerIndex);
break;
}
}
@ -156,28 +170,32 @@ namespace DotRecast.Recast
}
}
if (tooHighNeighbour > MAX_LAYERS)
if (maxLayerIndex > MAX_LAYERS)
{
throw new Exception("rcBuildCompactHeightfield: Heightfield has too many layers " + tooHighNeighbour
+ " (max: " + MAX_LAYERS + ")");
throw new Exception($"rcBuildCompactHeightfield: Heightfield has too many layers {maxLayerIndex} (max: {MAX_LAYERS})");
}
return chf;
compactHeightfield.spans = tempSpans.Select(x => x.Build()).ToArray();
return compactHeightfield;
}
private static int GetHeightFieldSpanCount(RcHeightfield hf)
/// Returns the number of spans contained in the specified heightfield.
/// @ingroup recast
/// @param[in,out] context The build context to use during the operation.
/// @param[in] heightfield An initialized heightfield.
/// @returns The number of spans in the heightfield.
private static int GetHeightFieldSpanCount(RcContext context, RcHeightfield heightfield)
{
int w = hf.width;
int h = hf.height;
int numCols = heightfield.width * heightfield.height;
int spanCount = 0;
for (int y = 0; y < h; ++y)
for (int columnIndex = 0; columnIndex < numCols; ++columnIndex)
{
for (int x = 0; x < w; ++x)
for (RcSpan span = heightfield.spans[columnIndex]; span != null; span = span.next)
{
for (RcSpan s = hf.spans[x + y * w]; s != null; s = s.next)
if (span.area != RC_NULL_AREA)
{
if (s.area != RC_NULL_AREA)
spanCount++;
spanCount++;
}
}
}

View File

@ -37,10 +37,14 @@ namespace DotRecast.Recast
public const int RC_NOT_CONNECTED = 0x3f;
/// Defines the number of bits allocated to rcSpan::smin and rcSpan::smax.
public const int SPAN_HEIGHT_BITS = 20;
public const int RC_SPAN_HEIGHT_BITS = 20;
/// Defines the maximum value for rcSpan::smin and rcSpan::smax.
public const int SPAN_MAX_HEIGHT = (1 << SPAN_HEIGHT_BITS) - 1;
public const int RC_SPAN_MAX_HEIGHT = (1 << RC_SPAN_HEIGHT_BITS) - 1;
/// The number of spans allocated per span spool.
/// @see rcSpanPool
public const int RC_SPANS_PER_POOL = 2048;
/// Heighfield border flag.
/// If a heightfield region ID has this bit set, then the region is a border

View File

@ -33,7 +33,7 @@ namespace DotRecast.Recast
{
isBorderVertex = false;
RcCompactSpan s = chf.spans[i];
ref RcCompactSpan s = ref chf.spans[i];
int ch = s.y;
int dirp = (dir + 1) & 0x3;
@ -46,39 +46,39 @@ namespace DotRecast.Recast
// border vertices which are in between two areas to be removed.
regs[0] = chf.spans[i].reg | (chf.areas[i] << 16);
if (GetCon(s, dir) != RC_NOT_CONNECTED)
if (GetCon(ref s, dir) != RC_NOT_CONNECTED)
{
int ax = x + GetDirOffsetX(dir);
int ay = y + GetDirOffsetY(dir);
int ai = chf.cells[ax + ay * chf.width].index + GetCon(s, dir);
RcCompactSpan @as = chf.spans[ai];
int ai = chf.cells[ax + ay * chf.width].index + GetCon(ref s, dir);
ref RcCompactSpan @as = ref chf.spans[ai];
ch = Math.Max(ch, @as.y);
regs[1] = chf.spans[ai].reg | (chf.areas[ai] << 16);
if (GetCon(@as, dirp) != RC_NOT_CONNECTED)
if (GetCon(ref @as, dirp) != RC_NOT_CONNECTED)
{
int ax2 = ax + GetDirOffsetX(dirp);
int ay2 = ay + GetDirOffsetY(dirp);
int ai2 = chf.cells[ax2 + ay2 * chf.width].index + GetCon(@as, dirp);
RcCompactSpan as2 = chf.spans[ai2];
int ai2 = chf.cells[ax2 + ay2 * chf.width].index + GetCon(ref @as, dirp);
ref RcCompactSpan as2 = ref chf.spans[ai2];
ch = Math.Max(ch, as2.y);
regs[2] = chf.spans[ai2].reg | (chf.areas[ai2] << 16);
}
}
if (GetCon(s, dirp) != RC_NOT_CONNECTED)
if (GetCon(ref s, dirp) != RC_NOT_CONNECTED)
{
int ax = x + GetDirOffsetX(dirp);
int ay = y + GetDirOffsetY(dirp);
int ai = chf.cells[ax + ay * chf.width].index + GetCon(s, dirp);
RcCompactSpan @as = chf.spans[ai];
int ai = chf.cells[ax + ay * chf.width].index + GetCon(ref s, dirp);
ref RcCompactSpan @as = ref chf.spans[ai];
ch = Math.Max(ch, @as.y);
regs[3] = chf.spans[ai].reg | (chf.areas[ai] << 16);
if (GetCon(@as, dir) != RC_NOT_CONNECTED)
if (GetCon(ref @as, dir) != RC_NOT_CONNECTED)
{
int ax2 = ax + GetDirOffsetX(dir);
int ay2 = ay + GetDirOffsetY(dir);
int ai2 = chf.cells[ax2 + ay2 * chf.width].index + GetCon(@as, dir);
RcCompactSpan as2 = chf.spans[ai2];
int ai2 = chf.cells[ax2 + ay2 * chf.width].index + GetCon(ref @as, dir);
ref RcCompactSpan as2 = ref chf.spans[ai2];
ch = Math.Max(ch, as2.y);
regs[2] = chf.spans[ai2].reg | (chf.areas[ai2] << 16);
}
@ -146,12 +146,12 @@ namespace DotRecast.Recast
}
int r = 0;
RcCompactSpan s = chf.spans[i];
if (GetCon(s, dir) != RC_NOT_CONNECTED)
ref RcCompactSpan s = ref chf.spans[i];
if (GetCon(ref s, dir) != RC_NOT_CONNECTED)
{
int ax = x + GetDirOffsetX(dir);
int ay = y + GetDirOffsetY(dir);
int ai = chf.cells[ax + ay * chf.width].index + GetCon(s, dir);
int ai = chf.cells[ax + ay * chf.width].index + GetCon(ref s, dir);
r = chf.spans[ai].reg;
if (area != chf.areas[ai])
isAreaBorder = true;
@ -174,11 +174,11 @@ namespace DotRecast.Recast
int ni = -1;
int nx = x + GetDirOffsetX(dir);
int ny = y + GetDirOffsetY(dir);
RcCompactSpan s = chf.spans[i];
if (GetCon(s, dir) != RC_NOT_CONNECTED)
ref RcCompactSpan s = ref chf.spans[i];
if (GetCon(ref s, dir) != RC_NOT_CONNECTED)
{
RcCompactCell nc = chf.cells[nx + ny * chf.width];
ni = nc.index + GetCon(s, dir);
ref RcCompactCell nc = ref chf.cells[nx + ny * chf.width];
ni = nc.index + GetCon(ref s, dir);
}
if (ni == -1)
@ -614,7 +614,7 @@ namespace DotRecast.Recast
return new int[] { minx, minz, leftmost };
}
private static void MergeRegionHoles(RcTelemetry ctx, RcContourRegion region)
private static void MergeRegionHoles(RcContext ctx, RcContourRegion region)
{
// Sort holes from left to right.
for (int i = 0; i < region.nholes; i++)
@ -632,10 +632,6 @@ namespace DotRecast.Recast
maxVerts += region.holes[i].contour.nverts;
RcPotentialDiagonal[] diags = new RcPotentialDiagonal[maxVerts];
for (int pd = 0; pd < maxVerts; pd++)
{
diags[pd] = new RcPotentialDiagonal();
}
RcContour outline = region.outline;
@ -664,8 +660,7 @@ namespace DotRecast.Recast
{
int dx = outline.verts[j * 4 + 0] - hole.verts[corner + 0];
int dz = outline.verts[j * 4 + 2] - hole.verts[corner + 2];
diags[ndiags].vert = j;
diags[ndiags].dist = dx * dx + dz * dz;
diags[ndiags] = new RcPotentialDiagonal(j, dx * dx + dz * dz);
ndiags++;
}
}
@ -720,7 +715,7 @@ namespace DotRecast.Recast
/// See the #rcConfig documentation for more information on the configuration parameters.
///
/// @see rcAllocContourSet, rcCompactHeightfield, rcContourSet, rcConfig
public static RcContourSet BuildContours(RcTelemetry ctx, RcCompactHeightfield chf, float maxError, int maxEdgeLen,
public static RcContourSet BuildContours(RcContext ctx, RcCompactHeightfield chf, float maxError, int maxEdgeLen,
int buildFlags)
{
int w = chf.width;
@ -758,11 +753,11 @@ namespace DotRecast.Recast
{
for (int x = 0; x < w; ++x)
{
RcCompactCell c = chf.cells[x + y * w];
ref RcCompactCell c = ref chf.cells[x + y * w];
for (int i = c.index, ni = c.index + c.count; i < ni; ++i)
{
int res = 0;
RcCompactSpan s = chf.spans[i];
ref RcCompactSpan s = ref chf.spans[i];
if (chf.spans[i].reg == 0 || (chf.spans[i].reg & RC_BORDER_REG) != 0)
{
flags[i] = 0;
@ -772,11 +767,11 @@ namespace DotRecast.Recast
for (int dir = 0; dir < 4; ++dir)
{
int r = 0;
if (GetCon(s, dir) != RC_NOT_CONNECTED)
if (GetCon(ref s, dir) != RC_NOT_CONNECTED)
{
int ax = x + GetDirOffsetX(dir);
int ay = y + GetDirOffsetY(dir);
int ai = chf.cells[ax + ay * w].index + GetCon(s, dir);
int ai = chf.cells[ax + ay * w].index + GetCon(ref s, dir);
r = chf.spans[ai].reg;
}
@ -798,7 +793,7 @@ namespace DotRecast.Recast
{
for (int x = 0; x < w; ++x)
{
RcCompactCell c = chf.cells[x + y * w];
ref RcCompactCell c = ref chf.cells[x + y * w];
for (int i = c.index, ni = c.index + c.count; i < ni; ++i)
{
if (flags[i] == 0 || flags[i] == 0xf)

View File

@ -0,0 +1,17 @@
namespace DotRecast.Recast
{
// Struct to keep track of entries in the region table that have been changed.
public readonly struct RcDirtyEntry
{
public readonly int index;
public readonly int region;
public readonly int distance2;
public RcDirtyEntry(int tempIndex, int tempRegion, int tempDistance2)
{
index = tempIndex;
region = tempRegion;
distance2 = tempDistance2;
}
}
}

View File

@ -30,7 +30,7 @@ namespace DotRecast.Recast
private const float EPSILON = 0.00001f;
private static readonly int[] BOX_EDGES = new[] { 0, 1, 0, 2, 0, 4, 1, 3, 1, 5, 2, 3, 2, 6, 3, 7, 4, 5, 4, 6, 5, 7, 6, 7 };
public static void RasterizeSphere(RcHeightfield hf, RcVec3f center, float radius, int area, int flagMergeThr, RcTelemetry ctx)
public static void RasterizeSphere(RcHeightfield hf, RcVec3f center, float radius, int area, int flagMergeThr, RcContext ctx)
{
using var timer = ctx.ScopedTimer(RcTimerLabel.RC_TIMER_RASTERIZE_SPHERE);
float[] bounds =
@ -42,7 +42,7 @@ namespace DotRecast.Recast
rectangle => IntersectSphere(rectangle, center, radius * radius));
}
public static void RasterizeCapsule(RcHeightfield hf, RcVec3f start, RcVec3f end, float radius, int area, int flagMergeThr, RcTelemetry ctx)
public static void RasterizeCapsule(RcHeightfield hf, RcVec3f start, RcVec3f end, float radius, int area, int flagMergeThr, RcContext ctx)
{
using var timer = ctx.ScopedTimer(RcTimerLabel.RC_TIMER_RASTERIZE_CAPSULE);
float[] bounds =
@ -56,7 +56,7 @@ namespace DotRecast.Recast
rectangle => IntersectCapsule(rectangle, start, end, axis, radius * radius));
}
public static void RasterizeCylinder(RcHeightfield hf, RcVec3f start, RcVec3f end, float radius, int area, int flagMergeThr, RcTelemetry ctx)
public static void RasterizeCylinder(RcHeightfield hf, RcVec3f start, RcVec3f end, float radius, int area, int flagMergeThr, RcContext ctx)
{
using var timer = ctx.ScopedTimer(RcTimerLabel.RC_TIMER_RASTERIZE_CYLINDER);
float[] bounds =
@ -70,7 +70,7 @@ namespace DotRecast.Recast
rectangle => IntersectCylinder(rectangle, start, end, axis, radius * radius));
}
public static void RasterizeBox(RcHeightfield hf, RcVec3f center, RcVec3f[] halfEdges, int area, int flagMergeThr, RcTelemetry ctx)
public static void RasterizeBox(RcHeightfield hf, RcVec3f center, RcVec3f[] halfEdges, int area, int flagMergeThr, RcContext ctx)
{
using var timer = ctx.ScopedTimer(RcTimerLabel.RC_TIMER_RASTERIZE_BOX);
RcVec3f[] normals =
@ -105,7 +105,7 @@ namespace DotRecast.Recast
bounds[5] = Math.Max(bounds[5], vertices[i * 3 + 2]);
}
float[][] planes = RcArrayUtils.Of<float>(6, 4);
float[][] planes = RcArrays.Of<float>(6, 4);
for (int i = 0; i < 6; i++)
{
float m = i < 3 ? -1 : 1;
@ -120,7 +120,7 @@ namespace DotRecast.Recast
RasterizationFilledShape(hf, bounds, area, flagMergeThr, rectangle => IntersectBox(rectangle, vertices, planes));
}
public static void RasterizeConvex(RcHeightfield hf, float[] vertices, int[] triangles, int area, int flagMergeThr, RcTelemetry ctx)
public static void RasterizeConvex(RcHeightfield hf, float[] vertices, int[] triangles, int area, int flagMergeThr, RcContext ctx)
{
using var timer = ctx.ScopedTimer(RcTimerLabel.RC_TIMER_RASTERIZE_CONVEX);
float[] bounds = new float[] { vertices[0], vertices[1], vertices[2], vertices[0], vertices[1], vertices[2] };
@ -135,8 +135,8 @@ namespace DotRecast.Recast
}
float[][] planes = RcArrayUtils.Of<float>(triangles.Length, 4);
float[][] triBounds = RcArrayUtils.Of<float>(triangles.Length / 3, 4);
float[][] planes = RcArrays.Of<float>(triangles.Length, 4);
float[][] triBounds = RcArrays.Of<float>(triangles.Length / 3, 4);
for (int i = 0, j = 0; i < triangles.Length; i += 3, j++)
{
int a = triangles[i] * 3;
@ -221,8 +221,8 @@ namespace DotRecast.Recast
int smax = (int)MathF.Ceiling((h[1] - hf.bmin.Y) * ich);
if (smin != smax)
{
int ismin = Math.Clamp(smin, 0, SPAN_MAX_HEIGHT);
int ismax = Math.Clamp(smax, ismin + 1, SPAN_MAX_HEIGHT);
int ismin = Math.Clamp(smin, 0, RC_SPAN_MAX_HEIGHT);
int ismax = Math.Clamp(smax, ismin + 1, RC_SPAN_MAX_HEIGHT);
RcRasterizations.AddSpan(hf, x, z, ismin, ismax, area, flagMergeThr);
}
}

View File

@ -28,172 +28,218 @@ namespace DotRecast.Recast
public static class RcFilters
{
/// @par
/// Marks non-walkable spans as walkable if their maximum is within @p walkableClimb of the span below them.
///
/// Allows the formation of walkable regions that will flow over low lying
/// objects such as curbs, and up structures such as stairways.
/// This removes small obstacles that the agent would be able to walk over such as curbs, and also allows agents to move up structures such as stairs.
/// This removes small obstacles and rasterization artifacts that the agent would be able to walk over
/// such as curbs. It also allows agents to move up terraced structures like stairs.
///
/// Two neighboring spans are walkable if: <tt>RcAbs(currentSpan.smax - neighborSpan.smax) < walkableClimb</tt>
/// Obstacle spans are marked walkable if: <tt>obstacleSpan.smax - walkableSpan.smax < walkableClimb</tt>
///
/// @warning Will override the effect of #rcFilterLedgeSpans. So if both filters are used, call
/// #rcFilterLedgeSpans after calling this filter.
/// @warning Will override the effect of #rcFilterLedgeSpans. If both filters are used, call #rcFilterLedgeSpans only after applying this filter.
///
/// @see rcHeightfield, rcConfig
public static void FilterLowHangingWalkableObstacles(RcTelemetry ctx, int walkableClimb, RcHeightfield solid)
///
/// @ingroup recast
/// @param[in,out] context The build context to use during the operation.
/// @param[in] walkableClimb Maximum ledge height that is considered to still be traversable.
/// [Limit: >=0] [Units: vx]
/// @param[in,out] heightfield A fully built heightfield. (All spans have been added.)
public static void FilterLowHangingWalkableObstacles(RcContext context, int walkableClimb, RcHeightfield heightfield)
{
using var timer = ctx.ScopedTimer(RcTimerLabel.RC_TIMER_FILTER_LOW_OBSTACLES);
using var timer = context.ScopedTimer(RcTimerLabel.RC_TIMER_FILTER_LOW_OBSTACLES);
int w = solid.width;
int h = solid.height;
int xSize = heightfield.width;
int zSize = heightfield.height;
for (int y = 0; y < h; ++y)
for (int z = 0; z < zSize; ++z)
{
for (int x = 0; x < w; ++x)
for (int x = 0; x < xSize; ++x)
{
RcSpan ps = null;
bool previousWalkable = false;
int previousArea = RC_NULL_AREA;
RcSpan previousSpan = null;
bool previousWasWalkable = false;
int previousAreaID = RC_NULL_AREA;
for (RcSpan s = solid.spans[x + y * w]; s != null; ps = s, s = s.next)
// For each span in the column...
for (RcSpan span = heightfield.spans[x + z * xSize]; span != null; previousSpan = span, span = span.next)
{
bool walkable = s.area != RC_NULL_AREA;
// If current span is not walkable, but there is walkable
// span just below it, mark the span above it walkable too.
if (!walkable && previousWalkable)
bool walkable = span.area != RC_NULL_AREA;
// If current span is not walkable, but there is walkable span just below it and the height difference
// is small enough for the agent to walk over, mark the current span as walkable too.
if (!walkable && previousWasWalkable && span.smax - previousSpan.smax <= walkableClimb)
{
if (MathF.Abs(s.smax - ps.smax) <= walkableClimb)
s.area = previousArea;
span.area = previousAreaID;
}
// Copy walkable flag so that it cannot propagate
// past multiple non-walkable objects.
previousWalkable = walkable;
previousArea = s.area;
// Copy the original walkable value regardless of whether we changed it.
// This prevents multiple consecutive non-walkable spans from being erroneously marked as walkable.
previousWasWalkable = walkable;
previousAreaID = span.area;
}
}
}
}
/// @par
/// Marks spans that are ledges as not-walkable.
///
/// A ledge is a span with one or more neighbors whose maximum is further away than @p walkableClimb
/// from the current span's maximum.
/// This method removes the impact of the overestimation of conservative voxelization
/// so the resulting mesh will not have regions hanging in the air over ledges.
///
/// A span is a ledge if: <tt>RcAbs(currentSpan.smax - neighborSpan.smax) > walkableClimb</tt>
/// A span is a ledge if: <tt>rcAbs(currentSpan.smax - neighborSpan.smax) > walkableClimb</tt>
///
/// @see rcHeightfield, rcConfig
public static void FilterLedgeSpans(RcTelemetry ctx, int walkableHeight, int walkableClimb, RcHeightfield solid)
///
/// @ingroup recast
/// @param[in,out] context The build context to use during the operation.
/// @param[in] walkableHeight Minimum floor to 'ceiling' height that will still allow the floor area to
/// be considered walkable. [Limit: >= 3] [Units: vx]
/// @param[in] walkableClimb Maximum ledge height that is considered to still be traversable.
/// [Limit: >=0] [Units: vx]
/// @param[in,out] heightfield A fully built heightfield. (All spans have been added.)
public static void FilterLedgeSpans(RcContext context, int walkableHeight, int walkableClimb, RcHeightfield heightfield)
{
using var timer = ctx.ScopedTimer(RcTimerLabel.RC_TIMER_FILTER_BORDER);
using var timer = context.ScopedTimer(RcTimerLabel.RC_TIMER_FILTER_BORDER);
int w = solid.width;
int h = solid.height;
int xSize = heightfield.width;
int zSize = heightfield.height;
// Mark border spans.
for (int y = 0; y < h; ++y)
// Mark spans that are adjacent to a ledge as unwalkable..
for (int z = 0; z < zSize; ++z)
{
for (int x = 0; x < w; ++x)
for (int x = 0; x < xSize; ++x)
{
for (RcSpan s = solid.spans[x + y * w]; s != null; s = s.next)
for (RcSpan span = heightfield.spans[x + z * xSize]; span != null; span = span.next)
{
// Skip non walkable spans.
if (s.area == RC_NULL_AREA)
// Skip non-walkable spans.
if (span.area == RC_NULL_AREA)
{
continue;
}
int bot = (s.smax);
int top = s.next != null ? s.next.smin : SPAN_MAX_HEIGHT;
int floor = (span.smax);
int ceiling = span.next != null ? span.next.smin : RC_SPAN_MAX_HEIGHT;
// Find neighbours minimum height.
int minh = SPAN_MAX_HEIGHT;
// The difference between this walkable area and the lowest neighbor walkable area.
// This is the difference between the current span and all neighbor spans that have
// enough space for an agent to move between, but not accounting at all for surface slope.
int lowestNeighborFloorDifference = RC_SPAN_MAX_HEIGHT;
// Min and max height of accessible neighbours.
int asmin = s.smax;
int asmax = s.smax;
int lowestTraversableNeighborFloor = span.smax;
int highestTraversableNeighborFloor = span.smax;
for (int dir = 0; dir < 4; ++dir)
for (int direction = 0; direction < 4; ++direction)
{
int dx = x + GetDirOffsetX(dir);
int dy = y + GetDirOffsetY(dir);
int neighborX = x + GetDirOffsetX(direction);
int neighborZ = z + GetDirOffsetY(direction);
// Skip neighbours which are out of bounds.
if (dx < 0 || dy < 0 || dx >= w || dy >= h)
if (neighborX < 0 || neighborZ < 0 || neighborX >= xSize || neighborZ >= zSize)
{
minh = Math.Min(minh, -walkableClimb - bot);
continue;
lowestNeighborFloorDifference = (-walkableClimb - 1);
break;
}
// From minus infinity to the first span.
RcSpan ns = solid.spans[dx + dy * w];
int nbot = -walkableClimb;
int ntop = ns != null ? ns.smin : SPAN_MAX_HEIGHT;
RcSpan neighborSpan = heightfield.spans[neighborX + neighborZ * xSize];
// The most we can step down to the neighbor is the walkableClimb distance.
// Start with the area under the neighbor span
int neighborCeiling = neighborSpan != null ? neighborSpan.smin : RC_SPAN_MAX_HEIGHT;
// Skip neightbour if the gap between the spans is too small.
if (Math.Min(top, ntop) - Math.Max(bot, nbot) > walkableHeight)
minh = Math.Min(minh, nbot - bot);
// Rest of the spans.
for (ns = solid.spans[dx + dy * w]; ns != null; ns = ns.next)
if (Math.Min(ceiling, neighborCeiling) - floor >= walkableHeight)
{
nbot = ns.smax;
ntop = ns.next != null ? ns.next.smin : SPAN_MAX_HEIGHT;
// Skip neightbour if the gap between the spans is too small.
if (Math.Min(top, ntop) - Math.Max(bot, nbot) > walkableHeight)
{
minh = Math.Min(minh, nbot - bot);
lowestNeighborFloorDifference = (-walkableClimb - 1);
break;
}
// Find min/max accessible neighbour height.
if (MathF.Abs(nbot - bot) <= walkableClimb)
{
if (nbot < asmin)
asmin = nbot;
if (nbot > asmax)
asmax = nbot;
}
// For each span in the neighboring column...
for (; neighborSpan != null; neighborSpan = neighborSpan.next)
{
int neighborFloor = neighborSpan.smax;
neighborCeiling = neighborSpan.next != null ? neighborSpan.next.smin : RC_SPAN_MAX_HEIGHT;
// Only consider neighboring areas that have enough overlap to be potentially traversable.
if (Math.Min(ceiling, neighborCeiling) - Math.Max(floor, neighborFloor) < walkableHeight)
{
// No space to traverse between them.
continue;
}
int neighborFloorDifference = neighborFloor - floor;
lowestNeighborFloorDifference = Math.Min(lowestNeighborFloorDifference, neighborFloorDifference);
// Find min/max accessible neighbor height.
// Only consider neighbors that are at most walkableClimb away.
if (MathF.Abs(neighborFloorDifference) <= walkableClimb)
{
// There is space to move to the neighbor cell and the slope isn't too much.
lowestTraversableNeighborFloor = Math.Min(lowestTraversableNeighborFloor, neighborFloor);
highestTraversableNeighborFloor = Math.Max(highestTraversableNeighborFloor, neighborFloor);
}
else if (neighborFloorDifference < -walkableClimb)
{
// We already know this will be considered a ledge span so we can early-out
break;
}
}
}
// The current span is close to a ledge if the drop to any
// neighbour span is less than the walkableClimb.
if (minh < -walkableClimb)
s.area = RC_NULL_AREA;
// If the difference between all neighbours is too large,
// we are at steep slope, mark the span as ledge.
if ((asmax - asmin) > walkableClimb)
// The current span is close to a ledge if the magnitude of the drop to any neighbour span is greater than the walkableClimb distance.
// That is, there is a gap that is large enough to let an agent move between them, but the drop (surface slope) is too large to allow it.
// (If this is the case, then biggestNeighborStepDown will be negative, so compare against the negative walkableClimb as a means of checking
// the magnitude of the delta)
if (lowestNeighborFloorDifference < -walkableClimb)
{
s.area = RC_NULL_AREA;
span.area = RC_NULL_AREA;
}
// If the difference between all neighbor floors is too large, this is a steep slope, so mark the span as an unwalkable ledge.
else if ((highestTraversableNeighborFloor - lowestTraversableNeighborFloor) > walkableClimb)
{
span.area = RC_NULL_AREA;
}
}
}
}
}
/// @par
/// Marks walkable spans as not walkable if the clearance above the span is less than the specified walkableHeight.
///
/// For this filter, the clearance above the span is the distance from the span's
/// maximum to the next higher span's minimum. (Same grid column.)
/// maximum to the minimum of the next higher span in the same column.
/// If there is no higher span in the column, the clearance is computed as the
/// distance from the top of the span to the maximum heightfield height.
///
/// @see rcHeightfield, rcConfig
public static void FilterWalkableLowHeightSpans(RcTelemetry ctx, int walkableHeight, RcHeightfield solid)
/// @ingroup recast
///
/// @param[in,out] context The build context to use during the operation.
/// @param[in] walkableHeight Minimum floor to 'ceiling' height that will still allow the floor area to
/// be considered walkable. [Limit: >= 3] [Units: vx]
/// @param[in,out] heightfield A fully built heightfield. (All spans have been added.)
public static void FilterWalkableLowHeightSpans(RcContext context, int walkableHeight, RcHeightfield heightfield)
{
using var timer = ctx.ScopedTimer(RcTimerLabel.RC_TIMER_FILTER_WALKABLE);
using var timer = context.ScopedTimer(RcTimerLabel.RC_TIMER_FILTER_WALKABLE);
int w = solid.width;
int h = solid.height;
int xSize = heightfield.width;
int zSize = heightfield.height;
// Remove walkable flag from spans which do not have enough
// space above them for the agent to stand there.
for (int y = 0; y < h; ++y)
for (int z = 0; z < zSize; ++z)
{
for (int x = 0; x < w; ++x)
for (int x = 0; x < xSize; ++x)
{
for (RcSpan s = solid.spans[x + y * w]; s != null; s = s.next)
for (RcSpan span = heightfield.spans[x + z * xSize]; span != null; span = span.next)
{
int bot = (s.smax);
int top = s.next != null ? s.next.smin : SPAN_MAX_HEIGHT;
if ((top - bot) < walkableHeight)
s.area = RC_NULL_AREA;
int floor = (span.smax);
int ceiling = span.next != null ? span.next.smin : RC_SPAN_MAX_HEIGHT;
if ((ceiling - floor) < walkableHeight)
{
span.area = RC_NULL_AREA;
}
}
}
}

View File

@ -6,48 +6,20 @@ namespace DotRecast.Recast
/// @see rcHeightfieldLayerSet
public class RcHeightfieldLayer
{
public RcVec3f bmin = new RcVec3f();
/// < The minimum bounds in world space. [(x, y, z)]
public RcVec3f bmax = new RcVec3f();
/// < The maximum bounds in world space. [(x, y, z)]
public float cs;
/// < The size of each cell. (On the xz-plane.)
public float ch;
/// < The height of each cell. (The minimum increment along the y-axis.)
public int width;
/// < The width of the heightfield. (Along the x-axis in cell units.)
public int height;
/// < The height of the heightfield. (Along the z-axis in cell units.)
public int minx;
/// < The minimum x-bounds of usable data.
public int maxx;
/// < The maximum x-bounds of usable data.
public int miny;
/// < The minimum y-bounds of usable data. (Along the z-axis.)
public int maxy;
/// < The maximum y-bounds of usable data. (Along the z-axis.)
public int hmin;
/// < The minimum height bounds of usable data. (Along the y-axis.)
public int hmax;
/// < The maximum height bounds of usable data. (Along the y-axis.)
public int[] heights;
/// < The heightfield. [Size: width * height]
public int[] areas;
/// < Area ids. [Size: Same as #heights]
public int[] cons; /// < Packed neighbor connection information. [Size: Same as #heights]
public RcVec3f bmin = new RcVec3f(); // < The minimum bounds in world space. [(x, y, z)]
public RcVec3f bmax = new RcVec3f(); // < The maximum bounds in world space. [(x, y, z)]
public float cs; // < The size of each cell. (On the xz-plane.)
public float ch; // < The height of each cell. (The minimum increment along the y-axis.)
public int width; // < The width of the heightfield. (Along the x-axis in cell units.)
public int height; // < The height of the heightfield. (Along the z-axis in cell units.)
public int minx; // < The minimum x-bounds of usable data.
public int maxx; // < The maximum x-bounds of usable data.
public int miny; // < The minimum y-bounds of usable data. (Along the z-axis.)
public int maxy; // < The maximum y-bounds of usable data. (Along the z-axis.)
public int hmin; // < The minimum height bounds of usable data. (Along the y-axis.)
public int hmax; // < The maximum height bounds of usable data. (Along the y-axis.)
public int[] heights; // < The heightfield. [Size: width * height]
public int[] areas; // < Area ids. [Size: Same as #heights]
public int[] cons; // < Packed neighbor connection information. [Size: Same as #heights]
}
}

View File

@ -18,7 +18,6 @@ freely, subject to the following restrictions:
3. This notice may not be removed or altered from any source distribution.
*/
using DotRecast.Core.Numerics;
namespace DotRecast.Recast
{

View File

@ -5,11 +5,11 @@ namespace DotRecast.Recast
public class RcLayerRegion
{
public int id;
public int layerId;
public bool @base;
public int layerId; // Layer ID
public bool @base; // Flag indicating if the region is the base of merged regions.
public int ymin, ymax;
public List<int> layers;
public List<int> neis;
public List<int> layers; // Layer count
public List<int> neis; // Neighbour count
public RcLayerRegion(int i)
{

View File

@ -1,6 +1,6 @@
namespace DotRecast.Detour.TileCache
namespace DotRecast.Recast
{
public class DtLayerSweepSpan
public class RcLayerSweepSpan
{
public int ns; // number samples
public int id; // region id

View File

@ -52,49 +52,73 @@ namespace DotRecast.Recast
return (amin > bmax || amax < bmin) ? false : true;
}
public static RcHeightfieldLayerSet BuildHeightfieldLayers(RcTelemetry ctx, RcCompactHeightfield chf, int walkableHeight)
/// @par
///
/// See the #rcConfig documentation for more information on the configuration parameters.
///
/// @see rcAllocHeightfieldLayerSet, rcCompactHeightfield, rcHeightfieldLayerSet, rcConfig
/// @}
/// @name Layer, Contour, Polymesh, and Detail Mesh Functions
/// @see rcHeightfieldLayer, rcContourSet, rcPolyMesh, rcPolyMeshDetail
/// @{
/// Builds a layer set from the specified compact heightfield.
/// @ingroup recast
/// @param[in,out] ctx The build context to use during the operation.
/// @param[in] chf A fully built compact heightfield.
/// @param[in] borderSize The size of the non-navigable border around the heightfield. [Limit: >=0]
/// [Units: vx]
/// @param[in] walkableHeight Minimum floor to 'ceiling' height that will still allow the floor area
/// to be considered walkable. [Limit: >= 3] [Units: vx]
/// @param[out] lset The resulting layer set. (Must be pre-allocated.)
/// @returns True if the operation completed successfully.
public static bool BuildHeightfieldLayers(RcContext ctx, RcCompactHeightfield chf, int borderSize, int walkableHeight, out RcHeightfieldLayerSet lset)
{
lset = null;
using var timer = ctx.ScopedTimer(RcTimerLabel.RC_TIMER_BUILD_LAYERS);
int w = chf.width;
int h = chf.height;
int borderSize = chf.borderSize;
int[] srcReg = new int[chf.spanCount];
Array.Fill(srcReg, 0xFF);
int nsweeps = chf.width; // Math.Max(chf.width, chf.height);
RcSweepSpan[] sweeps = new RcSweepSpan[nsweeps];
RcLayerSweepSpan[] sweeps = new RcLayerSweepSpan[nsweeps];
for (int i = 0; i < sweeps.Length; i++)
{
sweeps[i] = new RcSweepSpan();
sweeps[i] = new RcLayerSweepSpan();
}
// Partition walkable area into monotone regions.
int[] prevCount = new int[256];
int regId = 0;
// Sweep one line at a time.
for (int y = borderSize; y < h - borderSize; ++y)
{
// Collect spans from this row.
Array.Fill(prevCount, 0, 0, (regId) - (0));
Array.Fill(prevCount, 0);
int sweepId = 0;
for (int x = borderSize; x < w - borderSize; ++x)
{
RcCompactCell c = chf.cells[x + y * w];
ref RcCompactCell c = ref chf.cells[x + y * w];
for (int i = c.index, ni = c.index + c.count; i < ni; ++i)
{
RcCompactSpan s = chf.spans[i];
ref RcCompactSpan s = ref chf.spans[i];
if (chf.areas[i] == RC_NULL_AREA)
continue;
int sid = 0xFF;
// -x
if (GetCon(s, 0) != RC_NOT_CONNECTED)
int sid = 0xFF;
// -x
if (GetCon(ref s, 0) != RC_NOT_CONNECTED)
{
int ax = x + GetDirOffsetX(0);
int ay = y + GetDirOffsetY(0);
int ai = chf.cells[ax + ay * w].index + GetCon(s, 0);
int ai = chf.cells[ax + ay * w].index + GetCon(ref s, 0);
if (chf.areas[ai] != RC_NULL_AREA && srcReg[ai] != 0xff)
sid = srcReg[ai];
}
@ -107,16 +131,15 @@ namespace DotRecast.Recast
}
// -y
if (GetCon(s, 3) != RC_NOT_CONNECTED)
if (GetCon(ref s, 3) != RC_NOT_CONNECTED)
{
int ax = x + GetDirOffsetX(3);
int ay = y + GetDirOffsetY(3);
int ai = chf.cells[ax + ay * w].index + GetCon(s, 3);
int ai = chf.cells[ax + ay * w].index + GetCon(ref s, 3);
int nr = srcReg[ai];
if (nr != 0xff)
{
// Set neighbour when first valid neighbour is
// encoutered.
// Set neighbour when first valid neighbour is encoutered.
if (sweeps[sid].ns == 0)
sweeps[sid].nei = nr;
@ -128,8 +151,7 @@ namespace DotRecast.Recast
}
else
{
// This is hit if there is nore than one
// neighbour.
// This is hit if there is nore than one neighbour.
// Invalidate the neighbour.
sweeps[sid].nei = 0xff;
}
@ -143,10 +165,8 @@ namespace DotRecast.Recast
// Create unique ID.
for (int i = 0; i < sweepId; ++i)
{
// If the neighbour is set and there is only one continuous
// connection to it,
// the sweep will be merged with the previous one, else new
// region is created.
// If the neighbour is set and there is only one continuous connection to it,
// the sweep will be merged with the previous one, else new region is created.
if (sweeps[i].nei != 0xff && prevCount[sweeps[i].nei] == sweeps[i].ns)
{
sweeps[i].id = sweeps[i].nei;
@ -156,6 +176,7 @@ namespace DotRecast.Recast
if (regId == 255)
{
throw new Exception("rcBuildHeightfieldLayers: Region ID overflow.");
return false;
}
sweeps[i].id = regId++;
@ -165,7 +186,7 @@ namespace DotRecast.Recast
// Remap local sweep ids to region ids.
for (int x = borderSize; x < w - borderSize; ++x)
{
RcCompactCell c = chf.cells[x + y * w];
ref RcCompactCell c = ref chf.cells[x + y * w];
for (int i = c.index, ni = c.index + c.count; i < ni; ++i)
{
if (srcReg[i] != 0xff)
@ -174,6 +195,7 @@ namespace DotRecast.Recast
}
}
// Allocate and init layer regions.
int nregs = regId;
RcLayerRegion[] regs = new RcLayerRegion[nregs];
@ -189,13 +211,13 @@ namespace DotRecast.Recast
{
for (int x = 0; x < w; ++x)
{
RcCompactCell c = chf.cells[x + y * w];
ref RcCompactCell c = ref chf.cells[x + y * w];
lregs.Clear();
for (int i = c.index, ni = c.index + c.count; i < ni; ++i)
{
RcCompactSpan s = chf.spans[i];
ref RcCompactSpan s = ref chf.spans[i];
int ri = srcReg[i];
if (ri == 0xff)
continue;
@ -209,14 +231,19 @@ namespace DotRecast.Recast
// Update neighbours
for (int dir = 0; dir < 4; ++dir)
{
if (GetCon(s, dir) != RC_NOT_CONNECTED)
if (GetCon(ref s, dir) != RC_NOT_CONNECTED)
{
int ax = x + GetDirOffsetX(dir);
int ay = y + GetDirOffsetY(dir);
int ai = chf.cells[ax + ay * w].index + GetCon(s, dir);
int ai = chf.cells[ax + ay * w].index + GetCon(ref s, dir);
int rai = srcReg[ai];
if (rai != 0xff && rai != ri)
{
// Don't check return value -- if we cannot add the neighbor
// it will just cause a few more regions to be created, which
// is fine.
AddUnique(regs[ri].neis, rai);
}
}
}
}
@ -269,9 +296,11 @@ namespace DotRecast.Recast
// Skip already visited.
if (regn.layerId != 0xff)
continue;
// Skip if the neighbour is overlapping root region.
if (Contains(root.layers, nei))
continue;
// Skip if the height range would become too large.
int ymin = Math.Min(root.ymin, regn.ymin);
int ymax = Math.Max(root.ymax, regn.ymax);
@ -285,7 +314,10 @@ namespace DotRecast.Recast
regn.layerId = layerId;
// Merge current layers to root.
foreach (int layer in regn.layers)
{
AddUnique(root.layers, layer);
}
root.ymin = Math.Min(root.ymin, regn.ymin);
root.ymax = Math.Max(root.ymax, regn.ymax);
}
@ -326,11 +358,9 @@ namespace DotRecast.Recast
if ((ymax - ymin) >= 255)
continue;
// Make sure that there is no overlap when merging 'ri' and
// 'rj'.
// Make sure that there is no overlap when merging 'ri' and 'rj'.
bool overlap = false;
// Iterate over all regions which have the same layerId as
// 'rj'
// Iterate over all regions which have the same layerId as 'rj'
for (int k = 0; k < nregs; ++k)
{
if (regs[k].layerId != rj.layerId)
@ -368,7 +398,10 @@ namespace DotRecast.Recast
rj.layerId = newId;
// Add overlaid layers from 'rj' to 'ri'.
foreach (int layer in rj.layers)
{
AddUnique(ri.layers, layer);
}
// Update height bounds.
ri.ymin = Math.Min(ri.ymin, rj.ymin);
ri.ymax = Math.Max(ri.ymax, rj.ymax);
@ -394,13 +427,14 @@ namespace DotRecast.Recast
// Remap ids.
for (int i = 0; i < nregs; ++i)
{
regs[i].layerId = remap[regs[i].layerId];
}
// No layers, return empty.
if (layerId == 0)
{
// ctx.Stop(RC_TIMER_BUILD_LAYERS);
return null;
return true;
}
// Create layers.
@ -417,7 +451,7 @@ namespace DotRecast.Recast
bmax.X -= borderSize * chf.cs;
bmax.Z -= borderSize * chf.cs;
RcHeightfieldLayerSet lset = new RcHeightfieldLayerSet();
lset = new RcHeightfieldLayerSet();
lset.layers = new RcHeightfieldLayer[layerId];
for (int i = 0; i < lset.layers.Length; i++)
{
@ -475,10 +509,10 @@ namespace DotRecast.Recast
{
int cx = borderSize + x;
int cy = borderSize + y;
RcCompactCell c = chf.cells[cx + cy * w];
ref RcCompactCell c = ref chf.cells[cx + cy * w];
for (int j = c.index, nj = c.index + c.count; j < nj; ++j)
{
RcCompactSpan s = chf.spans[j];
ref RcCompactSpan s = ref chf.spans[j];
// Skip unassigned regions.
if (srcReg[j] == 0xff)
continue;
@ -503,19 +537,18 @@ namespace DotRecast.Recast
char con = (char)0;
for (int dir = 0; dir < 4; ++dir)
{
if (GetCon(s, dir) != RC_NOT_CONNECTED)
if (GetCon(ref s, dir) != RC_NOT_CONNECTED)
{
int ax = cx + GetDirOffsetX(dir);
int ay = cy + GetDirOffsetY(dir);
int ai = chf.cells[ax + ay * w].index + GetCon(s, dir);
int ai = chf.cells[ax + ay * w].index + GetCon(ref s, dir);
int alid = srcReg[ai] != 0xff ? regs[srcReg[ai]].layerId : 0xff;
// Portal mask
if (chf.areas[ai] != RC_NULL_AREA && lid != alid)
{
portal |= (char)(1 << dir);
// Update height so that it matches on both
// sides of the portal.
RcCompactSpan @as = chf.spans[ai];
// Update height so that it matches on both sides of the portal.
ref RcCompactSpan @as = ref chf.spans[ai];
if (@as.y > hmin)
layer.heights[idx] = Math.Max(layer.heights[idx], (char)(@as.y - hmin));
}
@ -542,8 +575,7 @@ namespace DotRecast.Recast
layer.miny = layer.maxy = 0;
}
// ctx->StopTimer(RC_TIMER_BUILD_LAYERS);
return lset;
return true;
}
}
}

View File

@ -0,0 +1,16 @@
namespace DotRecast.Recast
{
public readonly struct RcLevelStackEntry
{
public readonly int x;
public readonly int y;
public readonly int index;
public RcLevelStackEntry(int tempX, int tempY, int tempIndex)
{
x = tempX;
y = tempY;
index = tempIndex;
}
}
}

View File

@ -35,7 +35,7 @@ namespace DotRecast.Recast
public const int MAX_TRIS = 255; // Max tris for delaunay is 2n-2-k (n=num verts, k=num hull verts).
public const int MAX_VERTS_PER_EDGE = 32;
public const int RC_UNSET_HEIGHT = RcConstants.SPAN_MAX_HEIGHT;
public const int RC_UNSET_HEIGHT = RcConstants.RC_SPAN_MAX_HEIGHT;
public const int EV_UNDEF = -1;
public const int EV_HULL = -2;
@ -439,7 +439,7 @@ namespace DotRecast.Recast
return EV_UNDEF;
}
private static void AddEdge(RcTelemetry ctx, List<int> edges, int maxEdges, int s, int t, int l, int r)
private static void AddEdge(RcContext ctx, List<int> edges, int maxEdges, int s, int t, int l, int r)
{
if (edges.Count / 4 >= maxEdges)
{
@ -507,7 +507,7 @@ namespace DotRecast.Recast
return false;
}
static int CompleteFacet(RcTelemetry ctx, float[] pts, int npts, List<int> edges, int maxEdges, int nfaces, int e)
static int CompleteFacet(RcContext ctx, float[] pts, int npts, List<int> edges, int maxEdges, int nfaces, int e)
{
const float EPS = 1e-5f;
@ -624,7 +624,7 @@ namespace DotRecast.Recast
return nfaces;
}
private static void DelaunayHull(RcTelemetry ctx, int npts, float[] pts, int nhull, int[] hull, List<int> tris)
private static void DelaunayHull(RcContext ctx, int npts, float[] pts, int nhull, int[] hull, List<int> tris)
{
int nfaces = 0;
int maxEdges = npts * 10;
@ -828,7 +828,7 @@ namespace DotRecast.Recast
return (((i * 0xd8163841) & 0xffff) / 65535.0f * 2.0f) - 1.0f;
}
static int BuildPolyDetail(RcTelemetry ctx, float[] @in, int nin, float sampleDist, float sampleMaxError,
static int BuildPolyDetail(RcContext ctx, float[] @in, int nin, float sampleDist, float sampleMaxError,
int heightSearchRadius, RcCompactHeightfield chf, RcHeightPatch hp, float[] verts, List<int> tris)
{
List<int> samples = new List<int>(512);
@ -869,9 +869,7 @@ namespace DotRecast.Recast
{
if (@in[vj + 2] > @in[vi + 2])
{
int temp = vi;
vi = vj;
vj = temp;
(vi, vj) = (vj, vi);
swapped = true;
}
}
@ -879,9 +877,7 @@ namespace DotRecast.Recast
{
if (@in[vj + 0] > @in[vi + 0])
{
int temp = vi;
vi = vj;
vj = temp;
(vi, vj) = (vj, vi);
swapped = true;
}
}
@ -982,6 +978,7 @@ namespace DotRecast.Recast
if (minExtent < sampleDist * 2)
{
TriangulateHull(nverts, verts, nhull, hull, nin, tris);
SetTriFlags(tris, nhull, hull);
return nverts;
}
@ -1101,14 +1098,49 @@ namespace DotRecast.Recast
List<int> subList = tris.GetRange(0, MAX_TRIS * 4);
tris.Clear();
tris.AddRange(subList);
throw new Exception(
"rcBuildPolyMeshDetail: Shrinking triangle count from " + ntris + " to max " + MAX_TRIS);
throw new Exception("rcBuildPolyMeshDetail: Shrinking triangle count from " + ntris + " to max " + MAX_TRIS);
}
SetTriFlags(tris, nhull, hull);
return nverts;
}
static void SeedArrayWithPolyCenter(RcTelemetry ctx, RcCompactHeightfield chf, int[] meshpoly, int poly, int npoly,
static bool OnHull(int a, int b, int nhull, int[] hull)
{
// All internal sampled points come after the hull so we can early out for those.
if (a >= nhull || b >= nhull)
return false;
for (int j = nhull - 1, i = 0; i < nhull; j = i++)
{
if (a == hull[j] && b == hull[i])
return true;
}
return false;
}
// Find edges that lie on hull and mark them as such.
static void SetTriFlags(List<int> tris, int nhull, int[] hull)
{
// Matches DT_DETAIL_EDGE_BOUNDARY
const int DETAIL_EDGE_BOUNDARY = 0x1;
for (int i = 0; i < tris.Count; i += 4)
{
int a = tris[i];
int b = tris[i + 1];
int c = tris[i + 2];
int flags = 0;
flags |= (OnHull(a, b, nhull, hull) ? DETAIL_EDGE_BOUNDARY : 0) << 0;
flags |= (OnHull(b, c, nhull, hull) ? DETAIL_EDGE_BOUNDARY : 0) << 2;
flags |= (OnHull(c, a, nhull, hull) ? DETAIL_EDGE_BOUNDARY : 0) << 4;
tris[i + 3] = flags;
}
}
static void SeedArrayWithPolyCenter(RcContext ctx, RcCompactHeightfield chf, int[] meshpoly, int poly, int npoly,
int[] verts, int bs, RcHeightPatch hp, List<int> array)
{
// Note: Reads to the compact heightfield are offset by border size (bs)
@ -1131,10 +1163,10 @@ namespace DotRecast.Recast
continue;
}
RcCompactCell c = chf.cells[(ax + bs) + (az + bs) * chf.width];
ref RcCompactCell c = ref chf.cells[(ax + bs) + (az + bs) * chf.width];
for (int i = c.index, ni = c.index + c.count; i < ni && dmin > 0; ++i)
{
RcCompactSpan s = chf.spans[i];
ref RcCompactSpan s = ref chf.spans[i];
int d = Math.Abs(ay - s.y);
if (d < dmin)
{
@ -1210,12 +1242,12 @@ namespace DotRecast.Recast
dirs[3] = dirs[directDir];
dirs[directDir] = tmp;
RcCompactSpan cs = chf.spans[ci];
ref RcCompactSpan cs = ref chf.spans[ci];
for (int i = 0; i < 4; ++i)
{
int dir = dirs[i];
if (GetCon(cs, dir) == RC_NOT_CONNECTED)
if (GetCon(ref cs, dir) == RC_NOT_CONNECTED)
{
continue;
}
@ -1239,7 +1271,7 @@ namespace DotRecast.Recast
array.Add(newX);
array.Add(newY);
array.Add(chf.cells[(newX + bs) + (newY + bs) * chf.width].index + GetCon(cs, dir));
array.Add(chf.cells[(newX + bs) + (newY + bs) * chf.width].index + GetCon(ref cs, dir));
}
tmp = dirs[3];
@ -1253,7 +1285,7 @@ namespace DotRecast.Recast
array.Add(cy + bs);
array.Add(ci);
Array.Fill(hp.data, RC_UNSET_HEIGHT, 0, (hp.width * hp.height) - (0));
RcCompactSpan cs2 = chf.spans[ci];
ref RcCompactSpan cs2 = ref chf.spans[ci];
hp.data[cx - hp.xmin + (cy - hp.ymin) * hp.width] = cs2.y;
}
@ -1266,7 +1298,7 @@ namespace DotRecast.Recast
queue.Add(v3);
}
static void GetHeightData(RcTelemetry ctx, RcCompactHeightfield chf, int[] meshpolys, int poly, int npoly, int[] verts,
static void GetHeightData(RcContext ctx, RcCompactHeightfield chf, int[] meshpolys, int poly, int npoly, int[] verts,
int bs, RcHeightPatch hp, int region)
{
// Note: Reads to the compact heightfield are offset by border size (bs)
@ -1290,10 +1322,10 @@ namespace DotRecast.Recast
for (int hx = 0; hx < hp.width; hx++)
{
int x = hp.xmin + hx + bs;
RcCompactCell c = chf.cells[x + y * chf.width];
ref RcCompactCell c = ref chf.cells[x + y * chf.width];
for (int i = c.index, ni = c.index + c.count; i < ni; ++i)
{
RcCompactSpan s = chf.spans[i];
ref RcCompactSpan s = ref chf.spans[i];
if (s.reg == region)
{
// Store height
@ -1304,12 +1336,12 @@ namespace DotRecast.Recast
bool border = false;
for (int dir = 0; dir < 4; ++dir)
{
if (GetCon(s, dir) != RC_NOT_CONNECTED)
if (GetCon(ref s, dir) != RC_NOT_CONNECTED)
{
int ax = x + GetDirOffsetX(dir);
int ay = y + GetDirOffsetY(dir);
int ai = chf.cells[ax + ay * chf.width].index + GetCon(s, dir);
RcCompactSpan @as = chf.spans[ai];
int ai = chf.cells[ax + ay * chf.width].index + GetCon(ref s, dir);
ref RcCompactSpan @as = ref chf.spans[ai];
if (@as.reg != region)
{
border = true;
@ -1355,10 +1387,10 @@ namespace DotRecast.Recast
queue = queue.GetRange(RETRACT_SIZE * 3, queue.Count - (RETRACT_SIZE * 3));
}
RcCompactSpan cs = chf.spans[ci];
ref RcCompactSpan cs = ref chf.spans[ci];
for (int dir = 0; dir < 4; ++dir)
{
if (GetCon(cs, dir) == RC_NOT_CONNECTED)
if (GetCon(ref cs, dir) == RC_NOT_CONNECTED)
{
continue;
}
@ -1378,8 +1410,8 @@ namespace DotRecast.Recast
continue;
}
int ai = chf.cells[ax + ay * chf.width].index + GetCon(cs, dir);
RcCompactSpan @as = chf.spans[ai];
int ai = chf.cells[ax + ay * chf.width].index + GetCon(ref cs, dir);
ref RcCompactSpan @as = ref chf.spans[ai];
hp.data[hx + hy * hp.width] = @as.y;
Push3(queue, ax, ay, ai);
@ -1387,38 +1419,12 @@ namespace DotRecast.Recast
}
}
static int GetEdgeFlags(float[] verts, int va, int vb, float[] vpoly, int npoly)
{
// The flag returned by this function matches getDetailTriEdgeFlags in Detour.
// Figure out if edge (va,vb) is part of the polygon boundary.
float thrSqr = 0.001f * 0.001f;
for (int i = 0, j = npoly - 1; i < npoly; j = i++)
{
if (DistancePtSeg2d(verts, va, vpoly, j * 3, i * 3) < thrSqr
&& DistancePtSeg2d(verts, vb, vpoly, j * 3, i * 3) < thrSqr)
{
return 1;
}
}
return 0;
}
static int GetTriFlags(float[] verts, int va, int vb, int vc, float[] vpoly, int npoly)
{
int flags = 0;
flags |= GetEdgeFlags(verts, va, vb, vpoly, npoly) << 0;
flags |= GetEdgeFlags(verts, vb, vc, vpoly, npoly) << 2;
flags |= GetEdgeFlags(verts, vc, va, vpoly, npoly) << 4;
return flags;
}
/// @par
///
/// See the #rcConfig documentation for more information on the configuration parameters.
///
/// @see rcAllocPolyMeshDetail, rcPolyMesh, rcCompactHeightfield, rcPolyMeshDetail, rcConfig
public static RcPolyMeshDetail BuildPolyMeshDetail(RcTelemetry ctx, RcPolyMesh mesh, RcCompactHeightfield chf,
public static RcPolyMeshDetail BuildPolyMeshDetail(RcContext ctx, RcPolyMesh mesh, RcCompactHeightfield chf,
float sampleDist, float sampleMaxError)
{
using var timer = ctx.ScopedTimer(RcTimerLabel.RC_TIMER_BUILD_POLYMESHDETAIL);
@ -1562,7 +1568,7 @@ namespace DotRecast.Recast
float[] newv = new float[vcap * 3];
if (dmesh.nverts != 0)
{
Array.Copy(dmesh.verts, 0, newv, 0, 3 * dmesh.nverts);
RcArrays.Copy(dmesh.verts, 0, newv, 0, 3 * dmesh.nverts);
}
dmesh.verts = newv;
@ -1587,7 +1593,7 @@ namespace DotRecast.Recast
int[] newt = new int[tcap * 4];
if (dmesh.ntris != 0)
{
Array.Copy(dmesh.tris, 0, newt, 0, 4 * dmesh.ntris);
RcArrays.Copy(dmesh.tris, 0, newt, 0, 4 * dmesh.ntris);
}
dmesh.tris = newt;
@ -1599,8 +1605,7 @@ namespace DotRecast.Recast
dmesh.tris[dmesh.ntris * 4 + 0] = tris[t + 0];
dmesh.tris[dmesh.ntris * 4 + 1] = tris[t + 1];
dmesh.tris[dmesh.ntris * 4 + 2] = tris[t + 2];
dmesh.tris[dmesh.ntris * 4 + 3] = GetTriFlags(verts, tris[t + 0] * 3, tris[t + 1] * 3,
tris[t + 2] * 3, poly, npoly);
dmesh.tris[dmesh.ntris * 4 + 3] = tris[t + 3];
dmesh.ntris++;
}
}
@ -1609,7 +1614,7 @@ namespace DotRecast.Recast
}
/// @see rcAllocPolyMeshDetail, rcPolyMeshDetail
private static RcPolyMeshDetail MergePolyMeshDetails(RcTelemetry ctx, RcPolyMeshDetail[] meshes, int nmeshes)
private static RcPolyMeshDetail MergePolyMeshDetails(RcContext ctx, RcPolyMeshDetail[] meshes, int nmeshes)
{
using var timer = ctx.ScopedTimer(RcTimerLabel.RC_TIMER_MERGE_POLYMESHDETAIL);

View File

@ -558,7 +558,7 @@ namespace DotRecast.Recast
n++;
}
Array.Copy(polys, tmp, polys, pa, nvp);
RcArrays.Copy(polys, tmp, polys, pa, nvp);
}
private static int PushFront(int v, int[] arr, int an)
@ -577,7 +577,7 @@ namespace DotRecast.Recast
return an;
}
private static bool CanRemoveVertex(RcTelemetry ctx, RcPolyMesh mesh, int rem)
private static bool CanRemoveVertex(RcContext ctx, RcPolyMesh mesh, int rem)
{
int nvp = mesh.nvp;
@ -680,7 +680,7 @@ namespace DotRecast.Recast
return true;
}
private static void RemoveVertex(RcTelemetry ctx, RcPolyMesh mesh, int rem, int maxTris)
private static void RemoveVertex(RcContext ctx, RcPolyMesh mesh, int rem, int maxTris)
{
int nvp = mesh.nvp;
@ -737,7 +737,7 @@ namespace DotRecast.Recast
int p2 = (mesh.npolys - 1) * nvp * 2;
if (p != p2)
{
Array.Copy(mesh.polys, p2, mesh.polys, p, nvp);
RcArrays.Copy(mesh.polys, p2, mesh.polys, p, nvp);
}
Array.Fill(mesh.polys, RC_MESH_NULL_IDX, p + nvp, (p + nvp + nvp) - (p + nvp));
@ -927,7 +927,7 @@ namespace DotRecast.Recast
int last = (npolys - 1) * nvp;
if (pb != last)
{
Array.Copy(polys, last, polys, pb, nvp);
RcArrays.Copy(polys, last, polys, pb, nvp);
}
pregs[bestPb] = pregs[npolys - 1];
@ -967,7 +967,7 @@ namespace DotRecast.Recast
/// limit must be restricted to <= #DT_VERTS_PER_POLYGON.
///
/// @see rcAllocPolyMesh, rcContourSet, rcPolyMesh, rcConfig
public static RcPolyMesh BuildPolyMesh(RcTelemetry ctx, RcContourSet cset, int nvp)
public static RcPolyMesh BuildPolyMesh(RcContext ctx, RcContourSet cset, int nvp)
{
using var timer = ctx.ScopedTimer(RcTimerLabel.RC_TIMER_BUILD_POLYMESH);
@ -1109,7 +1109,7 @@ namespace DotRecast.Recast
int lastPoly = (npolys - 1) * nvp;
if (pb != lastPoly)
{
Array.Copy(polys, lastPoly, polys, pb, nvp);
RcArrays.Copy(polys, lastPoly, polys, pb, nvp);
}
npolys--;
@ -1212,7 +1212,7 @@ namespace DotRecast.Recast
}
/// @see rcAllocPolyMesh, rcPolyMesh
public static RcPolyMesh MergePolyMeshes(RcTelemetry ctx, RcPolyMesh[] meshes, int nmeshes)
public static RcPolyMesh MergePolyMeshes(RcContext ctx, RcPolyMesh[] meshes, int nmeshes)
{
if (nmeshes == 0 || meshes == null)
return null;
@ -1340,7 +1340,7 @@ namespace DotRecast.Recast
return mesh;
}
public static RcPolyMesh CopyPolyMesh(RcTelemetry ctx, RcPolyMesh src)
public static RcPolyMesh CopyPolyMesh(RcContext ctx, RcPolyMesh src)
{
RcPolyMesh dst = new RcPolyMesh();
@ -1356,15 +1356,15 @@ namespace DotRecast.Recast
dst.maxEdgeError = src.maxEdgeError;
dst.verts = new int[src.nverts * 3];
Array.Copy(src.verts, 0, dst.verts, 0, dst.verts.Length);
RcArrays.Copy(src.verts, 0, dst.verts, 0, dst.verts.Length);
dst.polys = new int[src.npolys * 2 * src.nvp];
Array.Copy(src.polys, 0, dst.polys, 0, dst.polys.Length);
RcArrays.Copy(src.polys, 0, dst.polys, 0, dst.polys.Length);
dst.regs = new int[src.npolys];
Array.Copy(src.regs, 0, dst.regs, 0, dst.regs.Length);
RcArrays.Copy(src.regs, 0, dst.regs, 0, dst.regs.Length);
dst.areas = new int[src.npolys];
Array.Copy(src.areas, 0, dst.areas, 0, dst.areas.Length);
RcArrays.Copy(src.areas, 0, dst.areas, 0, dst.areas.Length);
dst.flags = new int[src.npolys];
Array.Copy(src.flags, 0, dst.flags, 0, dst.flags.Length);
RcArrays.Copy(src.flags, 0, dst.flags, 0, dst.flags.Length);
return dst;
}
}

View File

@ -22,52 +22,26 @@ using DotRecast.Core.Numerics;
namespace DotRecast.Recast
{
/** Represents a polygon mesh suitable for use in building a navigation mesh. */
/// Represents a polygon mesh suitable for use in building a navigation mesh.
/// @ingroup recast
public class RcPolyMesh
{
/** The mesh vertices. [Form: (x, y, z) coordinates * #nverts] */
public int[] verts;
public int[] verts; // The mesh vertices. [Form: (x, y, z) coordinates * #nverts]
public int[] polys; // Polygon and neighbor data. [Length: #maxpolys * 2 * #nvp]
public int[] regs; // The region id assigned to each polygon. [Length: #maxpolys]
public int[] areas; // The area id assigned to each polygon. [Length: #maxpolys]
public int nverts; // The number of vertices.
public int npolys; // The number of polygons.
public int nvp; // The maximum number of vertices per polygon.
public int maxpolys; // The number of allocated polygons.
public int[] flags; // The user defined flags for each polygon. [Length: #maxpolys]
public RcVec3f bmin = new RcVec3f(); // The minimum bounds in world space. [(x, y, z)]
public RcVec3f bmax = new RcVec3f(); // The maximum bounds in world space. [(x, y, z)]
/** Polygon and neighbor data. [Length: #maxpolys * 2 * #nvp] */
public int[] polys;
public float cs; // The size of each cell. (On the xz-plane.)
public float ch; // The height of each cell. (The minimum increment along the y-axis.)
/** The region id assigned to each polygon. [Length: #maxpolys] */
public int[] regs;
/** The area id assigned to each polygon. [Length: #maxpolys] */
public int[] areas;
/** The number of vertices. */
public int nverts;
/** The number of polygons. */
public int npolys;
/** The maximum number of vertices per polygon. */
public int nvp;
/** The number of allocated polygons. */
public int maxpolys;
/** The user defined flags for each polygon. [Length: #maxpolys] */
public int[] flags;
/** The minimum bounds in world space. [(x, y, z)] */
public RcVec3f bmin = new RcVec3f();
/** The maximum bounds in world space. [(x, y, z)] */
public RcVec3f bmax = new RcVec3f();
/** The size of each cell. (On the xz-plane.) */
public float cs;
/** The height of each cell. (The minimum increment along the y-axis.) */
public float ch;
/** The AABB border size used to generate the source data from which the mesh was derived. */
public int borderSize;
/** The max error of the polygon edges in the mesh. */
public float maxEdgeError;
public int borderSize; // The AABB border size used to generate the source data from which the mesh was derived.
public float maxEdgeError; // The max error of the polygon edges in the mesh.
}
}

View File

@ -20,28 +20,16 @@ freely, subject to the following restrictions:
namespace DotRecast.Recast
{
/**
* Contains triangle meshes that represent detailed height data associated with the polygons in its associated polygon
* mesh object.
*/
/// Contains triangle meshes that represent detailed height data associated
/// with the polygons in its associated polygon mesh object.
/// @ingroup recast
public class RcPolyMeshDetail
{
/** The sub-mesh data. [Size: 4*#nmeshes] */
public int[] meshes;
/** The mesh vertices. [Size: 3*#nverts] */
public float[] verts;
/** The mesh triangles. [Size: 4*#ntris] */
public int[] tris;
/** The number of sub-meshes defined by #meshes. */
public int nmeshes;
/** The number of vertices in #verts. */
public int nverts;
/** The number of triangles in #tris. */
public int ntris;
public int[] meshes; //< The sub-mesh data. [Size: 4*#nmeshes]
public float[] verts; //< The mesh vertices. [Size: 3*#nverts]
public int[] tris; //< The mesh triangles. [Size: 4*#ntris]
public int nmeshes; //< The number of sub-meshes defined by #meshes.
public int nverts; //< The number of vertices in #verts.
public int ntris; //< The number of triangles in #tris.
}
}

View File

@ -1,8 +1,14 @@
namespace DotRecast.Recast
namespace DotRecast.Recast
{
public class RcPotentialDiagonal
public readonly struct RcPotentialDiagonal
{
public int dist;
public int vert;
public readonly int vert;
public readonly int dist;
public RcPotentialDiagonal(int vert, int dist)
{
this.vert = vert;
this.dist = dist;
}
}
}

View File

@ -27,35 +27,19 @@ namespace DotRecast.Recast
{
public static class RcRasterizations
{
/**
* Check whether two bounding boxes overlap
*
* @param amin
* Min axis extents of bounding box A
* @param amax
* Max axis extents of bounding box A
* @param bmin
* Min axis extents of bounding box B
* @param bmax
* Max axis extents of bounding box B
* @returns true if the two bounding boxes overlap. False otherwise
*/
private static bool OverlapBounds(float[] amin, float[] amax, float[] bmin, float[] bmax)
/// Check whether two bounding boxes overlap
///
/// @param[in] aMin Min axis extents of bounding box A
/// @param[in] aMax Max axis extents of bounding box A
/// @param[in] bMin Min axis extents of bounding box B
/// @param[in] bMax Max axis extents of bounding box B
/// @returns true if the two bounding boxes overlap. False otherwise.
private static bool OverlapBounds(RcVec3f aMin, RcVec3f aMax, RcVec3f bMin, RcVec3f bMax)
{
bool overlap = true;
overlap = (amin[0] > bmax[0] || amax[0] < bmin[0]) ? false : overlap;
overlap = (amin[1] > bmax[1] || amax[1] < bmin[1]) ? false : overlap;
overlap = (amin[2] > bmax[2] || amax[2] < bmin[2]) ? false : overlap;
return overlap;
}
private static bool OverlapBounds(RcVec3f amin, RcVec3f amax, RcVec3f bmin, RcVec3f bmax)
{
bool overlap = true;
overlap = (amin.X > bmax.X || amax.X < bmin.X) ? false : overlap;
overlap = (amin.Y > bmax.Y || amax.Y < bmin.Y) ? false : overlap;
overlap = (amin.Z > bmax.Z || amax.Z < bmin.Z) ? false : overlap;
return overlap;
return
aMin.X <= bMax.X && aMax.X >= bMin.X &&
aMin.Y <= bMax.Y && aMax.Y >= bMin.Y &&
aMin.Z <= bMax.Z && aMax.Z >= bMin.Z;
}
@ -69,72 +53,89 @@ namespace DotRecast.Recast
/// @param[in] max The new span's maximum cell index
/// @param[in] areaID The new span's area type ID
/// @param[in] flagMergeThreshold How close two spans maximum extents need to be to merge area type IDs
public static void AddSpan(RcHeightfield heightfield, int x, int y, int spanMin, int spanMax, int areaId, int flagMergeThreshold)
public static void AddSpan(RcHeightfield heightfield, int x, int z, int min, int max, int areaID, int flagMergeThreshold)
{
int idx = x + y * heightfield.width;
// Create the new span.
RcSpan newSpan = new RcSpan();
newSpan.smin = min;
newSpan.smax = max;
newSpan.area = areaID;
newSpan.next = null;
RcSpan s = new RcSpan();
s.smin = spanMin;
s.smax = spanMax;
s.area = areaId;
s.next = null;
int columnIndex = x + z * heightfield.width;
// Empty cell, add the first span.
if (heightfield.spans[idx] == null)
if (heightfield.spans[columnIndex] == null)
{
heightfield.spans[idx] = s;
heightfield.spans[columnIndex] = newSpan;
return;
}
RcSpan prev = null;
RcSpan cur = heightfield.spans[idx];
RcSpan previousSpan = null;
RcSpan currentSpan = heightfield.spans[columnIndex];
// Insert and merge spans.
while (cur != null)
// Insert the new span, possibly merging it with existing spans.
while (currentSpan != null)
{
if (cur.smin > s.smax)
if (currentSpan.smin > newSpan.smax)
{
// Current span is further than the new span, break.
break;
}
else if (cur.smax < s.smin)
if (currentSpan.smax < newSpan.smin)
{
// Current span is before the new span advance.
prev = cur;
cur = cur.next;
// Current span is completely before the new span. Keep going.
previousSpan = currentSpan;
currentSpan = currentSpan.next;
}
else
{
// Merge spans.
if (cur.smin < s.smin)
s.smin = cur.smin;
if (cur.smax > s.smax)
s.smax = cur.smax;
// The new span overlaps with an existing span. Merge them.
if (currentSpan.smin < newSpan.smin)
{
newSpan.smin = currentSpan.smin;
}
if (currentSpan.smax > newSpan.smax)
{
newSpan.smax = currentSpan.smax;
}
// Merge flags.
if (MathF.Abs(s.smax - cur.smax) <= flagMergeThreshold)
s.area = Math.Max(s.area, cur.area);
if (MathF.Abs(newSpan.smax - currentSpan.smax) <= flagMergeThreshold)
{
// Higher area ID numbers indicate higher resolution priority.
newSpan.area = Math.Max(newSpan.area, currentSpan.area);
}
// Remove current span.
RcSpan next = cur.next;
if (prev != null)
prev.next = next;
// Remove the current span since it's now merged with newSpan.
// Keep going because there might be other overlapping spans that also need to be merged.
RcSpan next = currentSpan.next;
if (previousSpan != null)
{
previousSpan.next = next;
}
else
heightfield.spans[idx] = next;
cur = next;
{
heightfield.spans[columnIndex] = next;
}
currentSpan = next;
}
}
// Insert new span.
if (prev != null)
// Insert new span after prev
if (previousSpan != null)
{
s.next = prev.next;
prev.next = s;
newSpan.next = previousSpan.next;
previousSpan.next = newSpan;
}
else
{
s.next = heightfield.spans[idx];
heightfield.spans[idx] = s;
// This span should go before the others in the list
newSpan.next = heightfield.spans[columnIndex];
heightfield.spans[columnIndex] = newSpan;
}
}
@ -154,54 +155,53 @@ namespace DotRecast.Recast
int outVerts2, out int outVerts2Count,
float axisOffset, int axis)
{
float[] d = new float[12];
// How far positive or negative away from the separating axis is each vertex.
float[] inVertAxisDelta = new float[12];
for (int inVert = 0; inVert < inVertsCount; ++inVert)
{
d[inVert] = axisOffset - inVerts[inVertsOffset + inVert * 3 + axis];
inVertAxisDelta[inVert] = axisOffset - inVerts[inVertsOffset + inVert * 3 + axis];
}
int poly1Vert = 0;
int poly2Vert = 0;
for (int inVertA = 0, inVertB = inVertsCount - 1; inVertA < inVertsCount; inVertB = inVertA, ++inVertA)
{
bool ina = d[inVertB] >= 0;
bool inb = d[inVertA] >= 0;
if (ina != inb)
// If the two vertices are on the same side of the separating axis
bool sameSide = (inVertAxisDelta[inVertA] >= 0) == (inVertAxisDelta[inVertB] >= 0);
if (!sameSide)
{
float s = d[inVertB] / (d[inVertB] - d[inVertA]);
inVerts[outVerts1 + poly1Vert * 3 + 0] = inVerts[inVertsOffset + inVertB * 3 + 0] +
(inVerts[inVertsOffset + inVertA * 3 + 0] - inVerts[inVertsOffset + inVertB * 3 + 0]) * s;
inVerts[outVerts1 + poly1Vert * 3 + 1] = inVerts[inVertsOffset + inVertB * 3 + 1] +
(inVerts[inVertsOffset + inVertA * 3 + 1] - inVerts[inVertsOffset + inVertB * 3 + 1]) * s;
inVerts[outVerts1 + poly1Vert * 3 + 2] = inVerts[inVertsOffset + inVertB * 3 + 2] +
(inVerts[inVertsOffset + inVertA * 3 + 2] - inVerts[inVertsOffset + inVertB * 3 + 2]) * s;
float s = inVertAxisDelta[inVertB] / (inVertAxisDelta[inVertB] - inVertAxisDelta[inVertA]);
inVerts[outVerts1 + poly1Vert * 3 + 0] = inVerts[inVertsOffset + inVertB * 3 + 0] + (inVerts[inVertsOffset + inVertA * 3 + 0] - inVerts[inVertsOffset + inVertB * 3 + 0]) * s;
inVerts[outVerts1 + poly1Vert * 3 + 1] = inVerts[inVertsOffset + inVertB * 3 + 1] + (inVerts[inVertsOffset + inVertA * 3 + 1] - inVerts[inVertsOffset + inVertB * 3 + 1]) * s;
inVerts[outVerts1 + poly1Vert * 3 + 2] = inVerts[inVertsOffset + inVertB * 3 + 2] + (inVerts[inVertsOffset + inVertA * 3 + 2] - inVerts[inVertsOffset + inVertB * 3 + 2]) * s;
RcVecUtils.Copy(inVerts, outVerts2 + poly2Vert * 3, inVerts, outVerts1 + poly1Vert * 3);
poly1Vert++;
poly2Vert++;
// add the i'th point to the right polygon. Do NOT add points that are on the dividing line
// since these were already added above
if (d[inVertA] > 0)
if (inVertAxisDelta[inVertA] > 0)
{
RcVecUtils.Copy(inVerts, outVerts1 + poly1Vert * 3, inVerts, inVertsOffset + inVertA * 3);
poly1Vert++;
}
else if (d[inVertA] < 0)
else if (inVertAxisDelta[inVertA] < 0)
{
RcVecUtils.Copy(inVerts, outVerts2 + poly2Vert * 3, inVerts, inVertsOffset + inVertA * 3);
poly2Vert++;
}
}
else // same side
else
{
// add the i'th point to the right polygon. Addition is done even for points on the dividing line
if (d[inVertA] >= 0)
if (inVertAxisDelta[inVertA] >= 0)
{
RcVecUtils.Copy(inVerts, outVerts1 + poly1Vert * 3, inVerts, inVertsOffset + inVertA * 3);
poly1Vert++;
if (d[inVertA] != 0)
if (inVertAxisDelta[inVertA] != 0)
{
continue;
}
}
RcVecUtils.Copy(inVerts, outVerts2 + poly2Vert * 3, inVerts, inVertsOffset + inVertA * 3);
@ -229,31 +229,35 @@ namespace DotRecast.Recast
/// @param[in] inverseCellHeight 1 / cellHeight
/// @param[in] flagMergeThreshold The threshold in which area flags will be merged
/// @returns true if the operation completes successfully. false if there was an error adding spans to the heightfield.
private static void RasterizeTri(float[] verts, int v0, int v1, int v2, int area, RcHeightfield heightfield,
private static bool RasterizeTri(float[] verts, int v0, int v1, int v2,
int areaID, RcHeightfield heightfield,
RcVec3f heightfieldBBMin, RcVec3f heightfieldBBMax,
float cellSize, float inverseCellSize, float inverseCellHeight,
int flagMergeThreshold)
{
float by = heightfieldBBMax.Y - heightfieldBBMin.Y;
// Calculate the bounding box of the triangle.
RcVec3f tmin = RcVecUtils.Create(verts, v0 * 3);
RcVec3f tmax = RcVecUtils.Create(verts, v0 * 3);
tmin = RcVecUtils.Min(tmin, verts, v1 * 3);
tmin = RcVecUtils.Min(tmin, verts, v2 * 3);
tmax = RcVecUtils.Max(tmax, verts, v1 * 3);
tmax = RcVecUtils.Max(tmax, verts, v2 * 3);
RcVec3f triBBMin = RcVecUtils.Create(verts, v0 * 3);
triBBMin = RcVecUtils.Min(triBBMin, verts, v1 * 3);
triBBMin = RcVecUtils.Min(triBBMin, verts, v2 * 3);
// If the triangle does not touch the bbox of the heightfield, skip the triagle.
if (!OverlapBounds(heightfieldBBMin, heightfieldBBMax, tmin, tmax))
return;
RcVec3f triBBMax = RcVecUtils.Create(verts, v0 * 3);
triBBMax = RcVecUtils.Max(triBBMax, verts, v1 * 3);
triBBMax = RcVecUtils.Max(triBBMax, verts, v2 * 3);
// Calculate the footprint of the triangle on the grid's y-axis
int z0 = (int)((tmin.Z - heightfieldBBMin.Z) * inverseCellSize);
int z1 = (int)((tmax.Z - heightfieldBBMin.Z) * inverseCellSize);
// If the triangle does not touch the bounding box of the heightfield, skip the triangle.
if (!OverlapBounds(triBBMin, triBBMax, heightfieldBBMin, heightfieldBBMax))
{
return true;
}
int w = heightfield.width;
int h = heightfield.height;
float by = heightfieldBBMax.Y - heightfieldBBMin.Y;
// Calculate the footprint of the triangle on the grid's y-axis
int z0 = (int)((triBBMin.Z - heightfieldBBMin.Z) * inverseCellSize);
int z1 = (int)((triBBMax.Z - heightfieldBBMin.Z) * inverseCellSize);
// use -1 rather than 0 to cut the polygon properly at the start of the tile
z0 = Math.Clamp(z0, -1, h - 1);
z1 = Math.Clamp(z1, 0, h - 1);
@ -268,25 +272,29 @@ namespace DotRecast.Recast
RcVecUtils.Copy(buf, 0, verts, v0 * 3);
RcVecUtils.Copy(buf, 3, verts, v1 * 3);
RcVecUtils.Copy(buf, 6, verts, v2 * 3);
int nvRow, nvIn = 3;
int nvRow;
int nvIn = 3;
for (int z = z0; z <= z1; ++z)
{
// Clip polygon to row. Store the remaining polygon as well
float cellZ = heightfieldBBMin.Z + z * cellSize;
DividePoly(buf, @in, nvIn, inRow, out nvRow, p1, out nvIn, cellZ + cellSize, 2);
DividePoly(buf, @in, nvIn, inRow, out nvRow, p1, out nvIn, cellZ + cellSize, RcAxis.RC_AXIS_Z);
(@in, p1) = (p1, @in);
if (nvRow < 3)
{
continue;
}
if (z < 0)
{
continue;
}
// find the horizontal bounds in the row
float minX = buf[inRow], maxX = buf[inRow];
// find X-axis bounds of the row
float minX = buf[inRow];
float maxX = buf[inRow];
for (int i = 1; i < nvRow; ++i)
{
float v = buf[inRow + i * 3];
@ -304,16 +312,19 @@ namespace DotRecast.Recast
x0 = Math.Clamp(x0, -1, w - 1);
x1 = Math.Clamp(x1, 0, w - 1);
int nv, nv2 = nvRow;
int nv;
int nv2 = nvRow;
for (int x = x0; x <= x1; ++x)
{
// Clip polygon to column. store the remaining polygon as well
float cx = heightfieldBBMin.X + x * cellSize;
DividePoly(buf, inRow, nv2, p1, out nv, p2, out nv2, cx + cellSize, 0);
DividePoly(buf, inRow, nv2, p1, out nv, p2, out nv2, cx + cellSize, RcAxis.RC_AXIS_X);
(inRow, p2) = (p2, inRow);
if (nv < 3)
{
continue;
}
if (x < 0)
{
@ -331,80 +342,89 @@ namespace DotRecast.Recast
spanMin -= heightfieldBBMin.Y;
spanMax -= heightfieldBBMin.Y;
// Skip the span if it is outside the heightfield bbox
if (spanMax < 0.0f)
{
continue;
}
if (spanMin > by)
{
continue;
}
// Clamp the span to the heightfield bbox.
if (spanMin < 0.0f)
{
spanMin = 0;
}
if (spanMax > by)
{
spanMax = by;
}
// Snap the span to the heightfield height grid.
int spanMinCellIndex = Math.Clamp((int)MathF.Floor(spanMin * inverseCellHeight), 0, SPAN_MAX_HEIGHT);
int spanMaxCellIndex = Math.Clamp((int)MathF.Ceiling(spanMax * inverseCellHeight), spanMinCellIndex + 1, SPAN_MAX_HEIGHT);
int spanMinCellIndex = Math.Clamp((int)MathF.Floor(spanMin * inverseCellHeight), 0, RC_SPAN_MAX_HEIGHT);
int spanMaxCellIndex = Math.Clamp((int)MathF.Ceiling(spanMax * inverseCellHeight), spanMinCellIndex + 1, RC_SPAN_MAX_HEIGHT);
AddSpan(heightfield, x, z, spanMinCellIndex, spanMaxCellIndex, area, flagMergeThreshold);
AddSpan(heightfield, x, z, spanMinCellIndex, spanMaxCellIndex, areaID, flagMergeThreshold);
}
}
return true;
}
/**
* Rasterizes a single triangle into the specified heightfield. Calling this for each triangle in a mesh is less
* efficient than calling rasterizeTriangles. No spans will be added if the triangle does not overlap the
* heightfield grid.
*
* @param heightfield
* An initialized heightfield.
* @param verts
* An array with vertex coordinates [(x, y, z) * N]
* @param v0
* Index of triangle vertex 0, will be multiplied by 3 to get vertex coordinates
* @param v1
* Triangle vertex 1 index
* @param v2
* Triangle vertex 2 index
* @param areaId
* The area id of the triangle. [Limit: <= WALKABLE_AREA)
* @param flagMergeThreshold
* The distance where the walkable flag is favored over the non-walkable flag. [Limit: >= 0] [Units: vx]
* @see Heightfield
*/
public static void RasterizeTriangle(RcHeightfield heightfield, float[] verts, int v0, int v1, int v2, int area,
int flagMergeThreshold, RcTelemetry ctx)
/// Rasterizes a single triangle into the specified heightfield.
///
/// Calling this for each triangle in a mesh is less efficient than calling rcRasterizeTriangles
///
/// No spans will be added if the triangle does not overlap the heightfield grid.
///
/// @see rcHeightfield
/// @ingroup recast
/// @param[in,out] context The build context to use during the operation.
/// @param[in] v0 Triangle vertex 0 [(x, y, z)]
/// @param[in] v1 Triangle vertex 1 [(x, y, z)]
/// @param[in] v2 Triangle vertex 2 [(x, y, z)]
/// @param[in] areaID The area id of the triangle. [Limit: <= #RC_WALKABLE_AREA]
/// @param[in,out] heightfield An initialized heightfield.
/// @param[in] flagMergeThreshold The distance where the walkable flag is favored over the non-walkable flag.
/// [Limit: >= 0] [Units: vx]
/// @returns True if the operation completed successfully.
public static void RasterizeTriangle(RcContext context, float[] verts, int v0, int v1, int v2, int areaID,
RcHeightfield heightfield, int flagMergeThreshold)
{
using var timer = ctx.ScopedTimer(RcTimerLabel.RC_TIMER_RASTERIZE_TRIANGLES);
using var timer = context.ScopedTimer(RcTimerLabel.RC_TIMER_RASTERIZE_TRIANGLES);
// Rasterize the single triangle.
float inverseCellSize = 1.0f / heightfield.cs;
float inverseCellHeight = 1.0f / heightfield.ch;
RasterizeTri(verts, v0, v1, v2, area, heightfield, heightfield.bmin, heightfield.bmax, heightfield.cs, inverseCellSize,
RasterizeTri(verts, v0, v1, v2, areaID, heightfield, heightfield.bmin, heightfield.bmax, heightfield.cs, inverseCellSize,
inverseCellHeight, flagMergeThreshold);
}
/**
* Rasterizes an indexed triangle mesh into the specified heightfield. Spans will only be added for triangles that
* overlap the heightfield grid.
*
* @param heightfield
* An initialized heightfield.
* @param verts
* The vertices. [(x, y, z) * N]
* @param tris
* The triangle indices. [(vertA, vertB, vertC) * nt]
* @param areaIds
* The area id's of the triangles. [Limit: <= WALKABLE_AREA] [Size: numTris]
* @param numTris
* The number of triangles.
* @param flagMergeThreshold
* The distance where the walkable flag is favored over the non-walkable flag. [Limit: >= 0] [Units: vx]
* @see Heightfield
*/
public static void RasterizeTriangles(RcHeightfield heightfield, float[] verts, int[] tris, int[] areaIds, int numTris,
int flagMergeThreshold, RcTelemetry ctx)
/// Rasterizes an indexed triangle mesh into the specified heightfield.
///
/// Spans will only be added for triangles that overlap the heightfield grid.
///
/// @see rcHeightfield
/// @ingroup recast
/// @param[in,out] context The build context to use during the operation.
/// @param[in] verts The vertices. [(x, y, z) * @p nv]
/// @param[in] numVerts The number of vertices. (unused) TODO (graham): Remove in next major release
/// @param[in] tris The triangle indices. [(vertA, vertB, vertC) * @p nt]
/// @param[in] triAreaIDs The area id's of the triangles. [Limit: <= #RC_WALKABLE_AREA] [Size: @p nt]
/// @param[in] numTris The number of triangles.
/// @param[in,out] heightfield An initialized heightfield.
/// @param[in] flagMergeThreshold The distance where the walkable flag is favored over the non-walkable flag.
/// [Limit: >= 0] [Units: vx]
/// @returns True if the operation completed successfully.
public static void RasterizeTriangles(RcContext context, float[] verts, int[] tris, int[] triAreaIDs, int numTris,
RcHeightfield heightfield, int flagMergeThreshold)
{
using var timer = ctx.ScopedTimer(RcTimerLabel.RC_TIMER_RASTERIZE_TRIANGLES);
using var timer = context.ScopedTimer(RcTimerLabel.RC_TIMER_RASTERIZE_TRIANGLES);
float inverseCellSize = 1.0f / heightfield.cs;
float inverseCellHeight = 1.0f / heightfield.ch;
@ -413,33 +433,30 @@ namespace DotRecast.Recast
int v0 = tris[triIndex * 3 + 0];
int v1 = tris[triIndex * 3 + 1];
int v2 = tris[triIndex * 3 + 2];
RasterizeTri(verts, v0, v1, v2, areaIds[triIndex], heightfield, heightfield.bmin, heightfield.bmax, heightfield.cs,
RasterizeTri(verts, v0, v1, v2, triAreaIDs[triIndex], heightfield, heightfield.bmin, heightfield.bmax, heightfield.cs,
inverseCellSize, inverseCellHeight, flagMergeThreshold);
}
}
/**
* Rasterizes a triangle list into the specified heightfield. Expects each triangle to be specified as three
* sequential vertices of 3 floats. Spans will only be added for triangles that overlap the heightfield grid.
*
* @param heightfield
* An initialized heightfield.
* @param verts
* The vertices. [(x, y, z) * numVerts]
* @param areaIds
* The area id's of the triangles. [Limit: <= WALKABLE_AREA] [Size: numTris]
* @param tris
* The triangle indices. [(vertA, vertB, vertC) * nt]
* @param numTris
* The number of triangles.
* @param flagMergeThreshold
* The distance where the walkable flag is favored over the non-walkable flag. [Limit: >= 0] [Units: vx]
* @see Heightfield
*/
public static void RasterizeTriangles(RcHeightfield heightfield, float[] verts, int[] areaIds, int numTris,
int flagMergeThreshold, RcTelemetry ctx)
/// Rasterizes a triangle list into the specified heightfield.
///
/// Expects each triangle to be specified as three sequential vertices of 3 floats.
///
/// Spans will only be added for triangles that overlap the heightfield grid.
///
/// @see rcHeightfield
/// @ingroup recast
/// @param[in,out] context The build context to use during the operation.
/// @param[in] verts The triangle vertices. [(ax, ay, az, bx, by, bz, cx, by, cx) * @p nt]
/// @param[in] triAreaIDs The area id's of the triangles. [Limit: <= #RC_WALKABLE_AREA] [Size: @p nt]
/// @param[in] numTris The number of triangles.
/// @param[in,out] heightfield An initialized heightfield.
/// @param[in] flagMergeThreshold The distance where the walkable flag is favored over the non-walkable flag.
/// [Limit: >= 0] [Units: vx]
/// @returns True if the operation completed successfully.
public static void RasterizeTriangles(RcContext context, float[] verts, int[] triAreaIDs, int numTris, RcHeightfield heightfield, int flagMergeThreshold)
{
using var timer = ctx.ScopedTimer(RcTimerLabel.RC_TIMER_RASTERIZE_TRIANGLES);
using var timer = context.ScopedTimer(RcTimerLabel.RC_TIMER_RASTERIZE_TRIANGLES);
float inverseCellSize = 1.0f / heightfield.cs;
float inverseCellHeight = 1.0f / heightfield.ch;
@ -448,7 +465,7 @@ namespace DotRecast.Recast
int v0 = (triIndex * 3 + 0);
int v1 = (triIndex * 3 + 1);
int v2 = (triIndex * 3 + 2);
RasterizeTri(verts, v0, v1, v2, areaIds[triIndex], heightfield, heightfield.bmin, heightfield.bmax, heightfield.cs,
RasterizeTri(verts, v0, v1, v2, triAreaIDs[triIndex], heightfield, heightfield.bmin, heightfield.bmax, heightfield.cs,
inverseCellSize, inverseCellHeight, flagMergeThreshold);
}
}

View File

@ -49,20 +49,20 @@ namespace DotRecast.Recast
{
for (int x = 0; x < w; ++x)
{
RcCompactCell c = chf.cells[x + y * w];
ref RcCompactCell c = ref chf.cells[x + y * w];
for (int i = c.index, ni = c.index + c.count; i < ni; ++i)
{
RcCompactSpan s = chf.spans[i];
ref RcCompactSpan s = ref chf.spans[i];
int area = chf.areas[i];
int nc = 0;
for (int dir = 0; dir < 4; ++dir)
{
if (GetCon(s, dir) != RC_NOT_CONNECTED)
if (GetCon(ref s, dir) != RC_NOT_CONNECTED)
{
int ax = x + GetDirOffsetX(dir);
int ay = y + GetDirOffsetY(dir);
int ai = chf.cells[ax + ay * w].index + GetCon(s, dir);
int ai = chf.cells[ax + ay * w].index + GetCon(ref s, dir);
if (area == chf.areas[ai])
{
nc++;
@ -83,29 +83,29 @@ namespace DotRecast.Recast
{
for (int x = 0; x < w; ++x)
{
RcCompactCell c = chf.cells[x + y * w];
ref RcCompactCell c = ref chf.cells[x + y * w];
for (int i = c.index, ni = c.index + c.count; i < ni; ++i)
{
RcCompactSpan s = chf.spans[i];
ref RcCompactSpan s = ref chf.spans[i];
if (GetCon(s, 0) != RC_NOT_CONNECTED)
if (GetCon(ref s, 0) != RC_NOT_CONNECTED)
{
// (-1,0)
int ax = x + GetDirOffsetX(0);
int ay = y + GetDirOffsetY(0);
int ai = chf.cells[ax + ay * w].index + GetCon(s, 0);
RcCompactSpan @as = chf.spans[ai];
int ai = chf.cells[ax + ay * w].index + GetCon(ref s, 0);
ref RcCompactSpan @as = ref chf.spans[ai];
if (src[ai] + 2 < src[i])
{
src[i] = src[ai] + 2;
}
// (-1,-1)
if (GetCon(@as, 3) != RC_NOT_CONNECTED)
if (GetCon(ref @as, 3) != RC_NOT_CONNECTED)
{
int aax = ax + GetDirOffsetX(3);
int aay = ay + GetDirOffsetY(3);
int aai = chf.cells[aax + aay * w].index + GetCon(@as, 3);
int aai = chf.cells[aax + aay * w].index + GetCon(ref @as, 3);
if (src[aai] + 3 < src[i])
{
src[i] = src[aai] + 3;
@ -113,24 +113,24 @@ namespace DotRecast.Recast
}
}
if (GetCon(s, 3) != RC_NOT_CONNECTED)
if (GetCon(ref s, 3) != RC_NOT_CONNECTED)
{
// (0,-1)
int ax = x + GetDirOffsetX(3);
int ay = y + GetDirOffsetY(3);
int ai = chf.cells[ax + ay * w].index + GetCon(s, 3);
RcCompactSpan @as = chf.spans[ai];
int ai = chf.cells[ax + ay * w].index + GetCon(ref s, 3);
ref RcCompactSpan @as = ref chf.spans[ai];
if (src[ai] + 2 < src[i])
{
src[i] = src[ai] + 2;
}
// (1,-1)
if (GetCon(@as, 2) != RC_NOT_CONNECTED)
if (GetCon(ref @as, 2) != RC_NOT_CONNECTED)
{
int aax = ax + GetDirOffsetX(2);
int aay = ay + GetDirOffsetY(2);
int aai = chf.cells[aax + aay * w].index + GetCon(@as, 2);
int aai = chf.cells[aax + aay * w].index + GetCon(ref @as, 2);
if (src[aai] + 3 < src[i])
{
src[i] = src[aai] + 3;
@ -146,29 +146,29 @@ namespace DotRecast.Recast
{
for (int x = w - 1; x >= 0; --x)
{
RcCompactCell c = chf.cells[x + y * w];
ref RcCompactCell c = ref chf.cells[x + y * w];
for (int i = c.index, ni = c.index + c.count; i < ni; ++i)
{
RcCompactSpan s = chf.spans[i];
ref RcCompactSpan s = ref chf.spans[i];
if (GetCon(s, 2) != RC_NOT_CONNECTED)
if (GetCon(ref s, 2) != RC_NOT_CONNECTED)
{
// (1,0)
int ax = x + GetDirOffsetX(2);
int ay = y + GetDirOffsetY(2);
int ai = chf.cells[ax + ay * w].index + GetCon(s, 2);
RcCompactSpan @as = chf.spans[ai];
int ai = chf.cells[ax + ay * w].index + GetCon(ref s, 2);
ref RcCompactSpan @as = ref chf.spans[ai];
if (src[ai] + 2 < src[i])
{
src[i] = src[ai] + 2;
}
// (1,1)
if (GetCon(@as, 1) != RC_NOT_CONNECTED)
if (GetCon(ref @as, 1) != RC_NOT_CONNECTED)
{
int aax = ax + GetDirOffsetX(1);
int aay = ay + GetDirOffsetY(1);
int aai = chf.cells[aax + aay * w].index + GetCon(@as, 1);
int aai = chf.cells[aax + aay * w].index + GetCon(ref @as, 1);
if (src[aai] + 3 < src[i])
{
src[i] = src[aai] + 3;
@ -176,24 +176,24 @@ namespace DotRecast.Recast
}
}
if (GetCon(s, 1) != RC_NOT_CONNECTED)
if (GetCon(ref s, 1) != RC_NOT_CONNECTED)
{
// (0,1)
int ax = x + GetDirOffsetX(1);
int ay = y + GetDirOffsetY(1);
int ai = chf.cells[ax + ay * w].index + GetCon(s, 1);
RcCompactSpan @as = chf.spans[ai];
int ai = chf.cells[ax + ay * w].index + GetCon(ref s, 1);
ref RcCompactSpan @as = ref chf.spans[ai];
if (src[ai] + 2 < src[i])
{
src[i] = src[ai] + 2;
}
// (-1,1)
if (GetCon(@as, 0) != RC_NOT_CONNECTED)
if (GetCon(ref @as, 0) != RC_NOT_CONNECTED)
{
int aax = ax + GetDirOffsetX(0);
int aay = ay + GetDirOffsetY(0);
int aai = chf.cells[aax + aay * w].index + GetCon(@as, 0);
int aai = chf.cells[aax + aay * w].index + GetCon(ref @as, 0);
if (src[aai] + 3 < src[i])
{
src[i] = src[aai] + 3;
@ -225,10 +225,10 @@ namespace DotRecast.Recast
{
for (int x = 0; x < w; ++x)
{
RcCompactCell c = chf.cells[x + y * w];
ref RcCompactCell c = ref chf.cells[x + y * w];
for (int i = c.index, ni = c.index + c.count; i < ni; ++i)
{
RcCompactSpan s = chf.spans[i];
ref RcCompactSpan s = ref chf.spans[i];
int cd = src[i];
if (cd <= thr)
{
@ -239,20 +239,20 @@ namespace DotRecast.Recast
int d = cd;
for (int dir = 0; dir < 4; ++dir)
{
if (GetCon(s, dir) != RC_NOT_CONNECTED)
if (GetCon(ref s, dir) != RC_NOT_CONNECTED)
{
int ax = x + GetDirOffsetX(dir);
int ay = y + GetDirOffsetY(dir);
int ai = chf.cells[ax + ay * w].index + GetCon(s, dir);
int ai = chf.cells[ax + ay * w].index + GetCon(ref s, dir);
d += src[ai];
RcCompactSpan @as = chf.spans[ai];
ref RcCompactSpan @as = ref chf.spans[ai];
int dir2 = (dir + 1) & 0x3;
if (GetCon(@as, dir2) != RC_NOT_CONNECTED)
if (GetCon(ref @as, dir2) != RC_NOT_CONNECTED)
{
int ax2 = ax + GetDirOffsetX(dir2);
int ay2 = ay + GetDirOffsetY(dir2);
int ai2 = chf.cells[ax2 + ay2 * w].index + GetCon(@as, dir2);
int ai2 = chf.cells[ax2 + ay2 * w].index + GetCon(ref @as, dir2);
d += src[ai2];
}
else
@ -274,8 +274,11 @@ namespace DotRecast.Recast
return dst;
}
private static bool FloodRegion(int x, int y, int i, int level, int r, RcCompactHeightfield chf, int[] srcReg,
int[] srcDist, List<int> stack)
private static bool FloodRegion(int x, int y, int i,
int level, int r,
RcCompactHeightfield chf,
int[] srcReg, int[] srcDist,
List<RcLevelStackEntry> stack)
{
int w = chf.width;
@ -283,9 +286,7 @@ namespace DotRecast.Recast
// Flood fill mark region.
stack.Clear();
stack.Add(x);
stack.Add(y);
stack.Add(i);
stack.Add(new RcLevelStackEntry(x, y, i));
srcReg[i] = r;
srcDist[i] = 0;
@ -294,28 +295,25 @@ namespace DotRecast.Recast
while (stack.Count > 0)
{
int ci = stack[^1];
stack.RemoveAt(stack.Count - 1);
int cy = stack[^1];
stack.RemoveAt(stack.Count - 1);
int cx = stack[^1];
RcLevelStackEntry back = stack[^1];
int cx = back.x;
int cy = back.y;
int ci = back.index;
stack.RemoveAt(stack.Count - 1);
RcCompactSpan cs = chf.spans[ci];
ref RcCompactSpan cs = ref chf.spans[ci];
// Check if any of the neighbours already have a valid region set.
int ar = 0;
for (int dir = 0; dir < 4; ++dir)
{
// 8 connected
if (GetCon(cs, dir) != RC_NOT_CONNECTED)
if (GetCon(ref cs, dir) != RC_NOT_CONNECTED)
{
int ax = cx + GetDirOffsetX(dir);
int ay = cy + GetDirOffsetY(dir);
int ai = chf.cells[ax + ay * w].index + GetCon(cs, dir);
int ai = chf.cells[ax + ay * w].index + GetCon(ref cs, dir);
if (chf.areas[ai] != area)
{
continue;
@ -333,14 +331,14 @@ namespace DotRecast.Recast
break;
}
RcCompactSpan @as = chf.spans[ai];
ref RcCompactSpan @as = ref chf.spans[ai];
int dir2 = (dir + 1) & 0x3;
if (GetCon(@as, dir2) != RC_NOT_CONNECTED)
if (GetCon(ref @as, dir2) != RC_NOT_CONNECTED)
{
int ax2 = ax + GetDirOffsetX(dir2);
int ay2 = ay + GetDirOffsetY(dir2);
int ai2 = chf.cells[ax2 + ay2 * w].index + GetCon(@as, dir2);
int ai2 = chf.cells[ax2 + ay2 * w].index + GetCon(ref @as, dir2);
if (chf.areas[ai2] != area)
{
continue;
@ -367,11 +365,11 @@ namespace DotRecast.Recast
// Expand neighbours.
for (int dir = 0; dir < 4; ++dir)
{
if (GetCon(cs, dir) != RC_NOT_CONNECTED)
if (GetCon(ref cs, dir) != RC_NOT_CONNECTED)
{
int ax = cx + GetDirOffsetX(dir);
int ay = cy + GetDirOffsetY(dir);
int ai = chf.cells[ax + ay * w].index + GetCon(cs, dir);
int ai = chf.cells[ax + ay * w].index + GetCon(ref cs, dir);
if (chf.areas[ai] != area)
{
continue;
@ -381,9 +379,7 @@ namespace DotRecast.Recast
{
srcReg[ai] = r;
srcDist[ai] = 0;
stack.Add(ax);
stack.Add(ay);
stack.Add(ai);
stack.Add(new RcLevelStackEntry(ax, ay, ai));
}
}
}
@ -392,8 +388,11 @@ namespace DotRecast.Recast
return count > 0;
}
private static int[] ExpandRegions(int maxIter, int level, RcCompactHeightfield chf, int[] srcReg, int[] srcDist,
List<int> stack, bool fillStack)
private static void ExpandRegions(int maxIter, int level,
RcCompactHeightfield chf,
int[] srcReg, int[] srcDist,
List<RcLevelStackEntry> stack,
bool fillStack)
{
int w = chf.width;
int h = chf.height;
@ -406,14 +405,12 @@ namespace DotRecast.Recast
{
for (int x = 0; x < w; ++x)
{
RcCompactCell c = chf.cells[x + y * w];
ref RcCompactCell c = ref chf.cells[x + y * w];
for (int i = c.index, ni = c.index + c.count; i < ni; ++i)
{
if (chf.dist[i] >= level && srcReg[i] == 0 && chf.areas[i] != RC_NULL_AREA)
{
stack.Add(x);
stack.Add(y);
stack.Add(i);
stack.Add(new RcLevelStackEntry(x, y, i));
}
}
}
@ -422,28 +419,28 @@ namespace DotRecast.Recast
else // use cells in the input stack
{
// mark all cells which already have a region
for (int j = 0; j < stack.Count; j += 3)
for (int j = 0; j < stack.Count; j++)
{
int i = stack[j + 2];
int i = stack[j].index;
if (srcReg[i] != 0)
{
stack[j + 2] = -1;
stack[j] = new RcLevelStackEntry(stack[j].x, stack[j].y, -1);
}
}
}
List<int> dirtyEntries = new List<int>();
List<RcDirtyEntry> dirtyEntries = new List<RcDirtyEntry>();
int iter = 0;
while (stack.Count > 0)
{
int failed = 0;
dirtyEntries.Clear();
for (int j = 0; j < stack.Count; j += 3)
for (int j = 0; j < stack.Count; j++)
{
int x = stack[j + 0];
int y = stack[j + 1];
int i = stack[j + 2];
int x = stack[j].x;
int y = stack[j].y;
int i = stack[j].index;
if (i < 0)
{
failed++;
@ -453,17 +450,17 @@ namespace DotRecast.Recast
int r = srcReg[i];
int d2 = 0xffff;
int area = chf.areas[i];
RcCompactSpan s = chf.spans[i];
ref RcCompactSpan s = ref chf.spans[i];
for (int dir = 0; dir < 4; ++dir)
{
if (GetCon(s, dir) == RC_NOT_CONNECTED)
if (GetCon(ref s, dir) == RC_NOT_CONNECTED)
{
continue;
}
int ax = x + GetDirOffsetX(dir);
int ay = y + GetDirOffsetY(dir);
int ai = chf.cells[ax + ay * w].index + GetCon(s, dir);
int ai = chf.cells[ax + ay * w].index + GetCon(ref s, dir);
if (chf.areas[ai] != area)
{
continue;
@ -481,10 +478,8 @@ namespace DotRecast.Recast
if (r != 0)
{
stack[j + 2] = -1; // mark as used
dirtyEntries.Add(i);
dirtyEntries.Add(r);
dirtyEntries.Add(d2);
stack[j] = new RcLevelStackEntry(stack[j].x, stack[j].y, -1); // mark as used
dirtyEntries.Add(new RcDirtyEntry(i, r, d2));
}
else
{
@ -493,14 +488,14 @@ namespace DotRecast.Recast
}
// Copy entries that differ between src and dst to keep them in sync.
for (int i = 0; i < dirtyEntries.Count; i += 3)
for (int i = 0; i < dirtyEntries.Count; i++)
{
int idx = dirtyEntries[i];
srcReg[idx] = dirtyEntries[i + 1];
srcDist[idx] = dirtyEntries[i + 2];
int idx = dirtyEntries[i].index;
srcReg[idx] = dirtyEntries[i].region;
srcDist[idx] = dirtyEntries[i].distance2;
}
if (failed * 3 == stack.Count())
if (failed == stack.Count())
{
break;
}
@ -514,12 +509,13 @@ namespace DotRecast.Recast
}
}
}
return srcReg;
}
private static void SortCellsByLevel(int startLevel, RcCompactHeightfield chf, int[] srcReg, int nbStacks,
List<List<int>> stacks, int loglevelsPerStack) // the levels per stack (2 in our case) as a bit shift
private static void SortCellsByLevel(int startLevel,
RcCompactHeightfield chf,
int[] srcReg,
int nbStacks, List<List<RcLevelStackEntry>> stacks,
int loglevelsPerStack) // the levels per stack (2 in our case) as a bit shift
{
int w = chf.width;
int h = chf.height;
@ -535,7 +531,7 @@ namespace DotRecast.Recast
{
for (int x = 0; x < w; ++x)
{
RcCompactCell c = chf.cells[x + y * w];
ref RcCompactCell c = ref chf.cells[x + y * w];
for (int i = c.index, ni = c.index + c.count; i < ni; ++i)
{
if (chf.areas[i] == RC_NULL_AREA || srcReg[i] != 0)
@ -555,27 +551,25 @@ namespace DotRecast.Recast
sId = 0;
}
stacks[sId].Add(x);
stacks[sId].Add(y);
stacks[sId].Add(i);
stacks[sId].Add(new RcLevelStackEntry(x, y, i));
}
}
}
}
private static void AppendStacks(List<int> srcStack, List<int> dstStack, int[] srcReg)
private static void AppendStacks(List<RcLevelStackEntry> srcStack,
List<RcLevelStackEntry> dstStack,
int[] srcReg)
{
for (int j = 0; j < srcStack.Count; j += 3)
for (int j = 0; j < srcStack.Count; j++)
{
int i = srcStack[j + 2];
int i = srcStack[j].index;
if ((i < 0) || (srcReg[i] != 0))
{
continue;
}
dstStack.Add(srcStack[j]);
dstStack.Add(srcStack[j + 1]);
dstStack.Add(srcStack[j + 2]);
}
}
@ -739,13 +733,13 @@ namespace DotRecast.Recast
private static bool IsSolidEdge(RcCompactHeightfield chf, int[] srcReg, int x, int y, int i, int dir)
{
RcCompactSpan s = chf.spans[i];
ref RcCompactSpan s = ref chf.spans[i];
int r = 0;
if (GetCon(s, dir) != RC_NOT_CONNECTED)
if (GetCon(ref s, dir) != RC_NOT_CONNECTED)
{
int ax = x + GetDirOffsetX(dir);
int ay = y + GetDirOffsetY(dir);
int ai = chf.cells[ax + ay * chf.width].index + GetCon(s, dir);
int ai = chf.cells[ax + ay * chf.width].index + GetCon(ref s, dir);
r = srcReg[ai];
}
@ -763,13 +757,13 @@ namespace DotRecast.Recast
int startDir = dir;
int starti = i;
RcCompactSpan ss = chf.spans[i];
ref RcCompactSpan ss = ref chf.spans[i];
int curReg = 0;
if (GetCon(ss, dir) != RC_NOT_CONNECTED)
if (GetCon(ref ss, dir) != RC_NOT_CONNECTED)
{
int ax = x + GetDirOffsetX(dir);
int ay = y + GetDirOffsetY(dir);
int ai = chf.cells[ax + ay * chf.width].index + GetCon(ss, dir);
int ai = chf.cells[ax + ay * chf.width].index + GetCon(ref ss, dir);
curReg = srcReg[ai];
}
@ -778,17 +772,17 @@ namespace DotRecast.Recast
int iter = 0;
while (++iter < 40000)
{
RcCompactSpan s = chf.spans[i];
ref RcCompactSpan s = ref chf.spans[i];
if (IsSolidEdge(chf, srcReg, x, y, i, dir))
{
// Choose the edge corner
int r = 0;
if (GetCon(s, dir) != RC_NOT_CONNECTED)
if (GetCon(ref s, dir) != RC_NOT_CONNECTED)
{
int ax = x + GetDirOffsetX(dir);
int ay = y + GetDirOffsetY(dir);
int ai = chf.cells[ax + ay * chf.width].index + GetCon(s, dir);
int ai = chf.cells[ax + ay * chf.width].index + GetCon(ref s, dir);
r = srcReg[ai];
}
@ -805,10 +799,10 @@ namespace DotRecast.Recast
int ni = -1;
int nx = x + GetDirOffsetX(dir);
int ny = y + GetDirOffsetY(dir);
if (GetCon(s, dir) != RC_NOT_CONNECTED)
if (GetCon(ref s, dir) != RC_NOT_CONNECTED)
{
RcCompactCell nc = chf.cells[nx + ny * chf.width];
ni = nc.index + GetCon(s, dir);
ref RcCompactCell nc = ref chf.cells[nx + ny * chf.width];
ni = nc.index + GetCon(ref s, dir);
}
if (ni == -1)
@ -847,7 +841,7 @@ namespace DotRecast.Recast
}
}
private static int MergeAndFilterRegions(RcTelemetry ctx, int minRegionArea, int mergeRegionSize, int maxRegionId,
private static int MergeAndFilterRegions(RcContext ctx, int minRegionArea, int mergeRegionSize, int maxRegionId,
RcCompactHeightfield chf, int[] srcReg, List<int> overlaps)
{
int w = chf.width;
@ -867,7 +861,7 @@ namespace DotRecast.Recast
{
for (int x = 0; x < w; ++x)
{
RcCompactCell c = chf.cells[x + y * w];
ref RcCompactCell c = ref chf.cells[x + y * w];
for (int i = c.index, ni = c.index + c.count; i < ni; ++i)
{
int r = srcReg[i];
@ -1169,8 +1163,7 @@ namespace DotRecast.Recast
}
}
private static int MergeAndFilterLayerRegions(RcTelemetry ctx, int minRegionArea, int maxRegionId,
RcCompactHeightfield chf, int[] srcReg, List<int> overlaps)
private static bool MergeAndFilterLayerRegions(RcContext ctx, int minRegionArea, ref int maxRegionId, RcCompactHeightfield chf, int[] srcReg)
{
int w = chf.width;
int h = chf.height;
@ -1190,13 +1183,14 @@ namespace DotRecast.Recast
{
for (int x = 0; x < w; ++x)
{
RcCompactCell c = chf.cells[x + y * w];
ref RcCompactCell c = ref chf.cells[x + y * w];
lregs.Clear();
for (int i = c.index, ni = c.index + c.count; i < ni; ++i)
{
RcCompactSpan s = chf.spans[i];
ref RcCompactSpan s = ref chf.spans[i];
int area = chf.areas[i];
int ri = srcReg[i];
if (ri == 0 || ri >= nreg)
{
@ -1206,20 +1200,22 @@ namespace DotRecast.Recast
RcRegion reg = regions[ri];
reg.spanCount++;
reg.areaType = chf.areas[i];
reg.areaType = area;
reg.ymin = Math.Min(reg.ymin, s.y);
reg.ymax = Math.Max(reg.ymax, s.y);
// Collect all region layers.
lregs.Add(ri);
// Update neighbours
for (int dir = 0; dir < 4; ++dir)
{
if (GetCon(s, dir) != RC_NOT_CONNECTED)
if (GetCon(ref s, dir) != RC_NOT_CONNECTED)
{
int ax = x + GetDirOffsetX(dir);
int ay = y + GetDirOffsetY(dir);
int ai = chf.cells[ax + ay * w].index + GetCon(s, dir);
int ai = chf.cells[ax + ay * w].index + GetCon(ref s, dir);
int rai = srcReg[ai];
if (rai > 0 && rai < nreg && rai != ri)
{
@ -1402,7 +1398,7 @@ namespace DotRecast.Recast
}
}
return maxRegionId;
return true;
}
/// @par
@ -1415,7 +1411,7 @@ namespace DotRecast.Recast
/// and rcCompactHeightfield::dist fields.
///
/// @see rcCompactHeightfield, rcBuildRegions, rcBuildRegionsMonotone
public static void BuildDistanceField(RcTelemetry ctx, RcCompactHeightfield chf)
public static void BuildDistanceField(RcContext ctx, RcCompactHeightfield chf)
{
using var timer = ctx.ScopedTimer(RcTimerLabel.RC_TIMER_BUILD_DISTANCEFIELD);
@ -1445,7 +1441,7 @@ namespace DotRecast.Recast
{
for (int x = minx; x < maxx; ++x)
{
RcCompactCell c = chf.cells[x + y * w];
ref RcCompactCell c = ref chf.cells[x + y * w];
for (int i = c.index, ni = c.index + c.count; i < ni; ++i)
{
if (chf.areas[i] != RC_NULL_AREA)
@ -1476,7 +1472,7 @@ namespace DotRecast.Recast
/// @warning The distance field must be created using #rcBuildDistanceField before attempting to build regions.
///
/// @see rcCompactHeightfield, rcCompactSpan, rcBuildDistanceField, rcBuildRegionsMonotone, rcConfig
public static void BuildRegionsMonotone(RcTelemetry ctx, RcCompactHeightfield chf, int minRegionArea,
public static void BuildRegionsMonotone(RcContext ctx, RcCompactHeightfield chf, int minRegionArea,
int mergeRegionArea)
{
using var timer = ctx.ScopedTimer(RcTimerLabel.RC_TIMER_BUILD_REGIONS);
@ -1531,11 +1527,11 @@ namespace DotRecast.Recast
for (int x = borderSize; x < w - borderSize; ++x)
{
RcCompactCell c = chf.cells[x + y * w];
ref RcCompactCell c = ref chf.cells[x + y * w];
for (int i = c.index, ni = c.index + c.count; i < ni; ++i)
{
RcCompactSpan s = chf.spans[i];
ref RcCompactSpan s = ref chf.spans[i];
if (chf.areas[i] == RC_NULL_AREA)
{
continue;
@ -1543,11 +1539,11 @@ namespace DotRecast.Recast
// -x
int previd = 0;
if (GetCon(s, 0) != RC_NOT_CONNECTED)
if (GetCon(ref s, 0) != RC_NOT_CONNECTED)
{
int ax = x + GetDirOffsetX(0);
int ay = y + GetDirOffsetY(0);
int ai = chf.cells[ax + ay * w].index + GetCon(s, 0);
int ai = chf.cells[ax + ay * w].index + GetCon(ref s, 0);
if ((srcReg[ai] & RC_BORDER_REG) == 0 && chf.areas[i] == chf.areas[ai])
{
previd = srcReg[ai];
@ -1563,11 +1559,11 @@ namespace DotRecast.Recast
}
// -y
if (GetCon(s, 3) != RC_NOT_CONNECTED)
if (GetCon(ref s, 3) != RC_NOT_CONNECTED)
{
int ax = x + GetDirOffsetX(3);
int ay = y + GetDirOffsetY(3);
int ai = chf.cells[ax + ay * w].index + GetCon(s, 3);
int ai = chf.cells[ax + ay * w].index + GetCon(ref s, 3);
if (srcReg[ai] != 0 && (srcReg[ai] & RC_BORDER_REG) == 0 && chf.areas[i] == chf.areas[ai])
{
int nr = srcReg[ai];
@ -1609,7 +1605,7 @@ namespace DotRecast.Recast
// Remap IDs
for (int x = borderSize; x < w - borderSize; ++x)
{
RcCompactCell c = chf.cells[x + y * w];
ref RcCompactCell c = ref chf.cells[x + y * w];
for (int i = c.index, ni = c.index + c.count; i < ni; ++i)
{
@ -1632,7 +1628,10 @@ namespace DotRecast.Recast
// Store the result out.
for (int i = 0; i < chf.spanCount; ++i)
{
chf.spans[i].reg = srcReg[i];
chf.spans[i] = RcCompactSpanBuilder
.NewBuilder(ref chf.spans[i])
.WithReg(srcReg[i])
.Build();
}
}
@ -1655,7 +1654,7 @@ namespace DotRecast.Recast
/// @warning The distance field must be created using #rcBuildDistanceField before attempting to build regions.
///
/// @see rcCompactHeightfield, rcCompactSpan, rcBuildDistanceField, rcBuildRegionsMonotone, rcConfig
public static void BuildRegions(RcTelemetry ctx, RcCompactHeightfield chf, int minRegionArea,
public static void BuildRegions(RcContext ctx, RcCompactHeightfield chf, int minRegionArea,
int mergeRegionArea)
{
using var timer = ctx.ScopedTimer(RcTimerLabel.RC_TIMER_BUILD_REGIONS);
@ -1668,13 +1667,13 @@ namespace DotRecast.Recast
int LOG_NB_STACKS = 3;
int NB_STACKS = 1 << LOG_NB_STACKS;
List<List<int>> lvlStacks = new List<List<int>>();
List<List<RcLevelStackEntry>> lvlStacks = new List<List<RcLevelStackEntry>>();
for (int i = 0; i < NB_STACKS; ++i)
{
lvlStacks.Add(new List<int>(1024));
lvlStacks.Add(new List<RcLevelStackEntry>(256));
}
List<int> stack = new List<int>(1024);
List<RcLevelStackEntry> stack = new List<RcLevelStackEntry>(256);
int[] srcReg = new int[chf.spanCount];
int[] srcDist = new int[chf.spanCount];
@ -1735,11 +1734,12 @@ namespace DotRecast.Recast
ctx.StartTimer(RcTimerLabel.RC_TIMER_BUILD_REGIONS_FLOOD);
// Mark new regions with IDs.
for (int j = 0; j < lvlStacks[sId].Count; j += 3)
for (int j = 0; j < lvlStacks[sId].Count; j++)
{
int x = lvlStacks[sId][j];
int y = lvlStacks[sId][j + 1];
int i = lvlStacks[sId][j + 2];
RcLevelStackEntry current = lvlStacks[sId][j];
int x = current.x;
int y = current.y;
int i = current.index;
if (i >= 0 && srcReg[i] == 0)
{
if (FloodRegion(x, y, i, level, regionId, chf, srcReg, srcDist, stack))
@ -1774,11 +1774,14 @@ namespace DotRecast.Recast
// Write the result out.
for (int i = 0; i < chf.spanCount; ++i)
{
chf.spans[i].reg = srcReg[i];
chf.spans[i] = RcCompactSpanBuilder
.NewBuilder(ref chf.spans[i])
.WithReg(srcReg[i])
.Build();
}
}
public static void BuildLayerRegions(RcTelemetry ctx, RcCompactHeightfield chf, int minRegionArea)
public static bool BuildLayerRegions(RcContext ctx, RcCompactHeightfield chf, int minRegionArea)
{
using var timer = ctx.ScopedTimer(RcTimerLabel.RC_TIMER_BUILD_REGIONS);
@ -1831,11 +1834,11 @@ namespace DotRecast.Recast
for (int x = borderSize; x < w - borderSize; ++x)
{
RcCompactCell c = chf.cells[x + y * w];
ref RcCompactCell c = ref chf.cells[x + y * w];
for (int i = c.index, ni = c.index + c.count; i < ni; ++i)
{
RcCompactSpan s = chf.spans[i];
ref RcCompactSpan s = ref chf.spans[i];
if (chf.areas[i] == RC_NULL_AREA)
{
continue;
@ -1843,11 +1846,11 @@ namespace DotRecast.Recast
// -x
int previd = 0;
if (GetCon(s, 0) != RC_NOT_CONNECTED)
if (GetCon(ref s, 0) != RC_NOT_CONNECTED)
{
int ax = x + GetDirOffsetX(0);
int ay = y + GetDirOffsetY(0);
int ai = chf.cells[ax + ay * w].index + GetCon(s, 0);
int ai = chf.cells[ax + ay * w].index + GetCon(ref s, 0);
if ((srcReg[ai] & RC_BORDER_REG) == 0 && chf.areas[i] == chf.areas[ai])
{
previd = srcReg[ai];
@ -1863,11 +1866,11 @@ namespace DotRecast.Recast
}
// -y
if (GetCon(s, 3) != RC_NOT_CONNECTED)
if (GetCon(ref s, 3) != RC_NOT_CONNECTED)
{
int ax = x + GetDirOffsetX(3);
int ay = y + GetDirOffsetY(3);
int ai = chf.cells[ax + ay * w].index + GetCon(s, 3);
int ai = chf.cells[ax + ay * w].index + GetCon(ref s, 3);
if (srcReg[ai] != 0 && (srcReg[ai] & RC_BORDER_REG) == 0 && chf.areas[i] == chf.areas[ai])
{
int nr = srcReg[ai];
@ -1909,7 +1912,7 @@ namespace DotRecast.Recast
// Remap IDs
for (int x = borderSize; x < w - borderSize; ++x)
{
RcCompactCell c = chf.cells[x + y * w];
ref RcCompactCell c = ref chf.cells[x + y * w];
for (int i = c.index, ni = c.index + c.count; i < ni; ++i)
{
@ -1921,19 +1924,26 @@ namespace DotRecast.Recast
}
}
ctx.StartTimer(RcTimerLabel.RC_TIMER_BUILD_REGIONS_FILTER);
// Merge monotone regions to layers and remove small regions.
List<int> overlaps = new List<int>();
chf.maxRegions = MergeAndFilterLayerRegions(ctx, minRegionArea, id, chf, srcReg, overlaps);
ctx.StopTimer(RcTimerLabel.RC_TIMER_BUILD_REGIONS_FILTER);
using (var timerFilter = ctx.ScopedTimer(RcTimerLabel.RC_TIMER_BUILD_REGIONS_FILTER))
{
// Merge monotone regions to layers and remove small regions.
chf.maxRegions = id;
if (!MergeAndFilterLayerRegions(ctx, minRegionArea, ref chf.maxRegions, chf, srcReg))
{
return false;
}
}
// Store the result out.
for (int i = 0; i < chf.spanCount; ++i)
{
chf.spans[i].reg = srcReg[i];
chf.spans[i] = RcCompactSpanBuilder
.NewBuilder(ref chf.spans[i])
.WithReg(srcReg[i])
.Build();
}
return true;
}
}
}

View File

@ -25,7 +25,7 @@ namespace DotRecast.Recast
{
public static class RcVoxelizations
{
public static RcHeightfield BuildSolidHeightfield(IInputGeomProvider geomProvider, RcBuilderConfig builderCfg, RcTelemetry ctx)
public static RcHeightfield BuildSolidHeightfield(RcContext ctx, IInputGeomProvider geomProvider, RcBuilderConfig builderCfg)
{
RcConfig cfg = builderCfg.cfg;
@ -34,13 +34,10 @@ namespace DotRecast.Recast
// Allocate array that can hold triangle area types.
// If you have multiple meshes you need to process, allocate
// and array which can hold the max number of triangles you need to
// process.
// and array which can hold the max number of triangles you need to process.
// Find triangles which are walkable based on their slope and rasterize
// them.
// If your input data is multiple meshes, you can transform them here,
// calculate
// Find triangles which are walkable based on their slope and rasterize them.
// If your input data is multiple meshes, you can transform them here, calculate
// the are type for each of the meshes and rasterize them.
foreach (RcTriMesh geom in geomProvider.Meshes())
{
@ -59,7 +56,7 @@ namespace DotRecast.Recast
int[] tris = node.tris;
int ntris = tris.Length / 3;
int[] m_triareas = RcCommons.MarkWalkableTriangles(ctx, cfg.WalkableSlopeAngle, verts, tris, ntris, cfg.WalkableAreaMod);
RcRasterizations.RasterizeTriangles(solid, verts, tris, m_triareas, ntris, cfg.WalkableClimb, ctx);
RcRasterizations.RasterizeTriangles(ctx, verts, tris, m_triareas, ntris, solid, cfg.WalkableClimb);
}
}
else
@ -67,7 +64,7 @@ namespace DotRecast.Recast
int[] tris = geom.GetTris();
int ntris = tris.Length / 3;
int[] m_triareas = RcCommons.MarkWalkableTriangles(ctx, cfg.WalkableSlopeAngle, verts, tris, ntris, cfg.WalkableAreaMod);
RcRasterizations.RasterizeTriangles(solid, verts, tris, m_triareas, ntris, cfg.WalkableClimb, ctx);
RcRasterizations.RasterizeTriangles(ctx, verts, tris, m_triareas, ntris, solid, cfg.WalkableClimb);
}
}

View File

@ -1,21 +1,21 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>net6.0;net7.0</TargetFrameworks>
<TargetFrameworks>net6.0;net7.0;net8.0</TargetFrameworks>
<IsPackable>false</IsPackable>
<IsTestProject>true</IsTestProject>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.7.2" />
<PackageReference Include="Moq" Version="4.20.69" />
<PackageReference Include="NUnit" Version="3.13.3"/>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.9.0" />
<PackageReference Include="Moq" Version="4.20.70" />
<PackageReference Include="NUnit" Version="4.0.1" />
<PackageReference Include="NUnit3TestAdapter" Version="4.5.0"/>
<PackageReference Include="NUnit.Analyzers" Version="3.9.0">
<PackageReference Include="NUnit.Analyzers" Version="4.0.1">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="coverlet.collector" Version="6.0.0">
<PackageReference Include="coverlet.collector" Version="6.0.1">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>

View File

@ -0,0 +1,101 @@
using System;
using System.Buffers;
using System.Collections.Generic;
using DotRecast.Core.Buffers;
using DotRecast.Core.Collections;
using NUnit.Framework;
namespace DotRecast.Core.Test;
public class RcArrayBenchmarkTests
{
private const int StepLength = 512;
private const int RandomLoop = 1000;
private readonly RcRand _rand = new RcRand();
private (string title, long ticks) Bench(string title, Action<int> source)
{
var begin = RcFrequency.Ticks;
for (int step = StepLength; step > 0; --step)
{
for (int i = 0; i < RandomLoop; ++i)
{
source.Invoke(step);
}
}
var end = RcFrequency.Ticks - begin;
return (title, end);
}
private void RoundForArray(int len)
{
var array = new int[len];
for (int ii = 0; ii < len; ++ii)
{
array[ii] = _rand.NextInt32();
}
}
private void RoundForPureRentArray(int len)
{
var array = ArrayPool<int>.Shared.Rent(len);
for (int ii = 0; ii < array.Length; ++ii)
{
array[ii] = _rand.NextInt32();
}
ArrayPool<int>.Shared.Return(array);
}
private void RoundForRcRentedArray(int len)
{
using var rentedArray = RcRentedArray.Rent<int>(len);
var array = rentedArray.AsArray();
for (int i = 0; i < rentedArray.Length; ++i)
{
array[i] = _rand.NextInt32();
}
}
private void RoundForRcStackArray(int len)
{
var array = new RcStackArray512<int>();
for (int ii = 0; ii < len; ++ii)
{
array[ii] = _rand.NextInt32();
}
}
private void RoundForStackalloc(int len)
{
Span<int> array = stackalloc int[len];
for (int ii = 0; ii < len; ++ii)
{
array[ii] = _rand.NextInt32();
}
}
[Test]
public void TestBenchmarkArrays()
{
var list = new List<(string title, long ticks)>();
list.Add(Bench("new int[len]", RoundForArray));
list.Add(Bench("ArrayPool<int>.Shared.Rent(len)", RoundForPureRentArray));
list.Add(Bench("RcRentedArray.Rent<int>(len)", RoundForRcRentedArray));
list.Add(Bench("new RcStackArray512<int>()", RoundForRcStackArray));
list.Add(Bench("stackalloc int[len]", RoundForStackalloc));
list.Sort((x, y) => x.ticks.CompareTo(y.ticks));
foreach (var t in list)
{
Console.WriteLine($"{t.title} {t.ticks / (double)TimeSpan.TicksPerMillisecond} ms");
}
}
}

View File

@ -0,0 +1,449 @@
using System;
using System.Collections.Generic;
using System.Linq;
using DotRecast.Core.Buffers;
using DotRecast.Core.Collections;
using NUnit.Framework;
namespace DotRecast.Core.Test;
// https://github.com/joaoportela/CircularBuffer-CSharp/blob/master/CircularBuffer.Tests/CircularBufferTests.cs
public class RcCyclicBufferTests
{
[Test]
public void RcCyclicBuffer_GetEnumeratorConstructorCapacity_ReturnsEmptyCollection()
{
var buffer = new RcCyclicBuffer<string>(5);
Assert.That(buffer.ToArray(), Is.Empty);
}
[Test]
public void RcCyclicBuffer_ConstructorSizeIndexAccess_CorrectContent()
{
var buffer = new RcCyclicBuffer<int>(5, new[] { 0, 1, 2, 3 });
Assert.That(buffer.Capacity, Is.EqualTo(5));
Assert.That(buffer.Size, Is.EqualTo(4));
for (int i = 0; i < 4; i++)
{
Assert.That(buffer[i], Is.EqualTo(i));
}
}
[Test]
public void RcCyclicBuffer_Constructor_ExceptionWhenSourceIsLargerThanCapacity()
{
Assert.Throws<ArgumentException>(() => new RcCyclicBuffer<int>(3, new[] { 0, 1, 2, 3 }));
}
[Test]
public void RcCyclicBuffer_GetEnumeratorConstructorDefinedArray_CorrectContent()
{
var buffer = new RcCyclicBuffer<int>(5, new[] { 0, 1, 2, 3 });
int x = 0;
foreach (var item in buffer)
{
Assert.That(item, Is.EqualTo(x));
x++;
}
}
[Test]
public void RcCyclicBuffer_PushBack_CorrectContent()
{
var buffer = new RcCyclicBuffer<int>(5);
for (int i = 0; i < 5; i++)
{
buffer.PushBack(i);
}
Assert.That(buffer.Front(), Is.EqualTo(0));
for (int i = 0; i < 5; i++)
{
Assert.That(buffer[i], Is.EqualTo(i));
}
}
[Test]
public void RcCyclicBuffer_PushBackOverflowingBuffer_CorrectContent()
{
var buffer = new RcCyclicBuffer<int>(5);
for (int i = 0; i < 10; i++)
{
buffer.PushBack(i);
}
Assert.That(buffer.ToArray(), Is.EqualTo(new[] { 5, 6, 7, 8, 9 }));
}
[Test]
public void RcCyclicBuffer_GetEnumeratorOverflowedArray_CorrectContent()
{
var buffer = new RcCyclicBuffer<int>(5);
for (int i = 0; i < 10; i++)
{
buffer.PushBack(i);
}
// buffer should have [5,6,7,8,9]
int x = 5;
buffer.ForEach(item =>
{
Assert.That(item, Is.EqualTo(x));
x++;
});
}
[Test]
public void RcCyclicBuffer_ToArrayConstructorDefinedArray_CorrectContent()
{
var buffer = new RcCyclicBuffer<int>(5, new[] { 0, 1, 2, 3 });
Assert.That(buffer.ToArray(), Is.EqualTo(new[] { 0, 1, 2, 3 }));
}
[Test]
public void RcCyclicBuffer_ToArrayOverflowedBuffer_CorrectContent()
{
var buffer = new RcCyclicBuffer<int>(5);
for (int i = 0; i < 10; i++)
{
buffer.PushBack(i);
}
Assert.That(buffer.ToArray(), Is.EqualTo(new[] { 5, 6, 7, 8, 9 }));
}
[Test]
public void RcCyclicBuffer_PushFront_CorrectContent()
{
var buffer = new RcCyclicBuffer<int>(5);
for (int i = 0; i < 5; i++)
{
buffer.PushFront(i);
}
Assert.That(buffer.ToArray(), Is.EqualTo(new[] { 4, 3, 2, 1, 0 }));
}
[Test]
public void RcCyclicBuffer_PushFrontAndOverflow_CorrectContent()
{
var buffer = new RcCyclicBuffer<int>(5);
for (int i = 0; i < 10; i++)
{
buffer.PushFront(i);
}
Assert.That(buffer.ToArray(), Is.EqualTo(new[] { 9, 8, 7, 6, 5 }));
}
[Test]
public void RcCyclicBuffer_Front_CorrectItem()
{
var buffer = new RcCyclicBuffer<int>(5, new[] { 0, 1, 2, 3, 4 });
Assert.That(buffer.Front(), Is.EqualTo(0));
}
[Test]
public void RcCyclicBuffer_Back_CorrectItem()
{
var buffer = new RcCyclicBuffer<int>(5, new[] { 0, 1, 2, 3, 4 });
Assert.That(buffer.Back(), Is.EqualTo(4));
}
[Test]
public void RcCyclicBuffer_BackOfBufferOverflowByOne_CorrectItem()
{
var buffer = new RcCyclicBuffer<int>(5, new[] { 0, 1, 2, 3, 4 });
buffer.PushBack(42);
Assert.That(buffer.ToArray(), Is.EqualTo(new[] { 1, 2, 3, 4, 42 }));
Assert.That(buffer.Back(), Is.EqualTo(42));
}
[Test]
public void RcCyclicBuffer_Front_EmptyBufferThrowsException()
{
var buffer = new RcCyclicBuffer<int>(5);
Assert.Throws<InvalidOperationException>(() => buffer.Front());
}
[Test]
public void RcCyclicBuffer_Back_EmptyBufferThrowsException()
{
var buffer = new RcCyclicBuffer<int>(5);
Assert.Throws<InvalidOperationException>(() => buffer.Back());
}
[Test]
public void RcCyclicBuffer_PopBack_RemovesBackElement()
{
var buffer = new RcCyclicBuffer<int>(5, new[] { 0, 1, 2, 3, 4 });
Assert.That(buffer.Size, Is.EqualTo(5));
buffer.PopBack();
Assert.That(buffer.Size, Is.EqualTo(4));
Assert.That(buffer.ToArray(), Is.EqualTo(new[] { 0, 1, 2, 3 }));
}
[Test]
public void RcCyclicBuffer_PopBackInOverflowBuffer_RemovesBackElement()
{
var buffer = new RcCyclicBuffer<int>(5, new[] { 0, 1, 2, 3, 4 });
buffer.PushBack(5);
Assert.That(buffer.Size, Is.EqualTo(5));
Assert.That(buffer.ToArray(), Is.EqualTo(new[] { 1, 2, 3, 4, 5 }));
buffer.PopBack();
Assert.That(buffer.Size, Is.EqualTo(4));
Assert.That(buffer.ToArray(), Is.EqualTo(new[] { 1, 2, 3, 4 }));
}
[Test]
public void RcCyclicBuffer_PopFront_RemovesBackElement()
{
var buffer = new RcCyclicBuffer<int>(5, new[] { 0, 1, 2, 3, 4 });
Assert.That(buffer.Size, Is.EqualTo(5));
buffer.PopFront();
Assert.That(buffer.Size, Is.EqualTo(4));
Assert.That(buffer.ToArray(), Is.EqualTo(new[] { 1, 2, 3, 4 }));
}
[Test]
public void RcCyclicBuffer_PopFrontInOverflowBuffer_RemovesBackElement()
{
var buffer = new RcCyclicBuffer<int>(5, new[] { 0, 1, 2, 3, 4 });
buffer.PushFront(5);
Assert.That(buffer.Size, Is.EqualTo(5));
Assert.That(buffer.ToArray(), Is.EqualTo(new[] { 5, 0, 1, 2, 3 }));
buffer.PopFront();
Assert.That(buffer.Size, Is.EqualTo(4));
Assert.That(buffer.ToArray(), Is.EqualTo(new[] { 0, 1, 2, 3 }));
}
[Test]
public void RcCyclicBuffer_SetIndex_ReplacesElement()
{
var buffer = new RcCyclicBuffer<int>(5, new[] { 0, 1, 2, 3, 4 });
buffer[1] = 10;
buffer[3] = 30;
Assert.That(buffer.ToArray(), Is.EqualTo(new[] { 0, 10, 2, 30, 4 }));
}
[Test]
public void RcCyclicBuffer_WithDifferentSizeAndCapacity_BackReturnsLastArrayPosition()
{
// test to confirm this issue does not happen anymore:
// https://github.com/joaoportela/RcCyclicBuffer-CSharp/issues/2
var buffer = new RcCyclicBuffer<int>(5, new[] { 0, 1, 2, 3, 4 });
buffer.PopFront(); // (make size and capacity different)
Assert.That(buffer.Back(), Is.EqualTo(4));
}
[Test]
public void RcCyclicBuffer_Clear_ClearsContent()
{
var buffer = new RcCyclicBuffer<int>(5, new[] { 4, 3, 2, 1, 0 });
buffer.Clear();
Assert.That(buffer.Size, Is.EqualTo(0));
Assert.That(buffer.Capacity, Is.EqualTo(5));
Assert.That(buffer.ToArray(), Is.EqualTo(new int[0]));
}
[Test]
public void RcCyclicBuffer_Clear_WorksNormallyAfterClear()
{
var buffer = new RcCyclicBuffer<int>(5, new[] { 4, 3, 2, 1, 0 });
buffer.Clear();
for (int i = 0; i < 5; i++)
{
buffer.PushBack(i);
}
Assert.That(buffer.Front(), Is.EqualTo(0));
for (int i = 0; i < 5; i++)
{
Assert.That(buffer[i], Is.EqualTo(i));
}
}
[Test]
public void RcCyclicBuffer_RegularForEachWorks()
{
var refValues = new[] { 4, 3, 2, 1, 0 };
var buffer = new RcCyclicBuffer<int>(5, refValues);
var index = 0;
foreach (var element in buffer)
{
Assert.That(element, Is.EqualTo(refValues[index++]));
}
}
[Test]
public void RcCyclicBuffer_EnumeratorWorks()
{
var refValues = new int[] { 4, 3, 2, 1, 0 };
var buffer = new RcCyclicBuffer<int>(5, refValues);
var index = 0;
using var enumerator = buffer.GetEnumerator();
enumerator.Reset();
while (enumerator.MoveNext())
{
Assert.That(enumerator.Current, Is.EqualTo(refValues[index++]));
}
// Ensure Reset works properly
index = 0;
enumerator.Reset();
while (enumerator.MoveNext())
{
Assert.That(enumerator.Current, Is.EqualTo(refValues[index++]));
}
}
[Test]
public void RcCyclicBuffers_Sum()
{
var refValues = Enumerable.Range(-100, 211).Select(x => (long)x).ToArray();
var buffer = new RcCyclicBuffer<long>(refValues.Length, refValues);
Assert.That(RcCyclicBuffers.Sum(buffer), Is.EqualTo(refValues.Sum()));
}
[Test]
public void RcCyclicBuffers_Average()
{
var refValues = Enumerable.Range(-100, 211).Select(x => (long)x).ToArray();
var buffer = new RcCyclicBuffer<long>(refValues.Length, refValues);
Assert.That(RcCyclicBuffers.Average(buffer), Is.EqualTo(refValues.Average()));
}
[Test]
public void RcCyclicBuffers_Min()
{
var refValues = Enumerable.Range(-100, 211).Select(x => (long)x).ToArray();
var buffer = new RcCyclicBuffer<long>(refValues.Length, refValues);
Assert.That(RcCyclicBuffers.Min(buffer), Is.EqualTo(refValues.Min()));
}
[Test]
public void RcCyclicBuffers_Max()
{
var refValues = Enumerable.Range(-100, 211).Select(x => (long)x).ToArray();
var buffer = new RcCyclicBuffer<long>(refValues.Length, refValues);
Assert.That(RcCyclicBuffers.Max(buffer), Is.EqualTo(refValues.Max()));
}
[Test]
public void RcCyclicBuffers_SumUnaligned()
{
var refValues = Enumerable.Range(-1, 3).Select(x => (long)x).ToArray();
var buffer = new RcCyclicBuffer<long>(refValues.Length, refValues);
Assert.That(RcCyclicBuffers.Sum(buffer), Is.EqualTo(refValues.Sum()));
}
[Test]
public void RcCyclicBuffers_AverageUnaligned()
{
var refValues = Enumerable.Range(-1, 3).Select(x => (long)x).ToArray();
var buffer = new RcCyclicBuffer<long>(refValues.Length, refValues);
Assert.That(RcCyclicBuffers.Average(buffer), Is.EqualTo(refValues.Average()));
}
[Test]
public void RcCyclicBuffers_MinUnaligned()
{
var refValues = Enumerable.Range(5, 3).Select(x => (long)x).ToArray();
var buffer = new RcCyclicBuffer<long>(refValues.Length, refValues);
Assert.That(RcCyclicBuffers.Min(buffer), Is.EqualTo(refValues.Min()));
}
[Test]
public void RcCyclicBuffers_MaxUnaligned()
{
var refValues = Enumerable.Range(-5, 3).Select(x => (long)x).ToArray();
var buffer = new RcCyclicBuffer<long>(refValues.Length, refValues);
Assert.That(RcCyclicBuffers.Max(buffer), Is.EqualTo(refValues.Max()));
}
[Test]
public void RcCyclicBuffers_SumDeleted()
{
var initialValues = Enumerable.Range(-100, 211).Select(x => (long)x).ToArray();
var refValues = initialValues.Skip(1).SkipLast(1).ToArray();
var buffer = new RcCyclicBuffer<long>(initialValues.Length, initialValues);
buffer.PopBack();
buffer.PopFront();
Assert.That(RcCyclicBuffers.Sum(buffer), Is.EqualTo(refValues.Sum()));
}
[Test]
public void RcCyclicBuffers_SumSplit()
{
var refValues = Enumerable.Range(-100, 211).Select(x => (long)x).ToArray();
var buffer = new RcCyclicBuffer<long>(refValues.Length, refValues);
buffer.PopFront();
buffer.PushBack(refValues[0]);
Assert.That(RcCyclicBuffers.Sum(buffer), Is.EqualTo(refValues.Sum()));
}
[Test]
public void RcCyclicBuffers_AverageSplit()
{
var refValues = Enumerable.Range(-100, 211).Select(x => (long)x).ToArray();
var buffer = new RcCyclicBuffer<long>(refValues.Length, refValues);
buffer.PopFront();
buffer.PushBack(refValues[0]);
Assert.That(RcCyclicBuffers.Average(buffer), Is.EqualTo(refValues.Average()));
}
[Test]
public void RcCyclicBuffers_MinSplit()
{
var refValues = Enumerable.Range(-100, 211).Select(x => (long)x).ToArray();
var buffer = new RcCyclicBuffer<long>(refValues.Length, refValues);
buffer.PopFront();
buffer.PushBack(refValues[0]);
Assert.That(RcCyclicBuffers.Min(buffer), Is.EqualTo(refValues.Min()));
}
[Test]
public void RcCyclicBuffers_MaxSplit()
{
var refValues = Enumerable.Range(-100, 211).Select(x => (long)x).ToArray();
var buffer = new RcCyclicBuffer<long>(refValues.Length, refValues);
buffer.PopFront();
buffer.PushBack(refValues[0]);
Assert.That(RcCyclicBuffers.Max(buffer), Is.EqualTo(refValues.Max()));
}
}

View File

@ -0,0 +1,40 @@
using NUnit.Framework;
namespace DotRecast.Core.Test;
public class RcHashCodesTest
{
[Test]
public void TestCombineHashCodes()
{
Assert.That(RcHashCodes.CombineHashCodes(0, 0), Is.EqualTo(0));
Assert.That(RcHashCodes.CombineHashCodes(int.MaxValue, int.MaxValue), Is.EqualTo(32));
Assert.That(RcHashCodes.CombineHashCodes(int.MaxValue, int.MinValue), Is.EqualTo(-33));
Assert.That(RcHashCodes.CombineHashCodes(int.MinValue, int.MinValue), Is.EqualTo(0));
Assert.That(RcHashCodes.CombineHashCodes(int.MinValue, int.MaxValue), Is.EqualTo(-1));
Assert.That(RcHashCodes.CombineHashCodes(int.MaxValue / 2, int.MaxValue / 2), Is.EqualTo(32));
}
[Test]
public void TestIntHash()
{
Assert.That(RcHashCodes.WangHash(0), Is.EqualTo(4158654902));
Assert.That(RcHashCodes.WangHash(1), Is.EqualTo(357654460));
Assert.That(RcHashCodes.WangHash(2), Is.EqualTo(715307540));
Assert.That(RcHashCodes.WangHash(3), Is.EqualTo(1072960876));
Assert.That(RcHashCodes.WangHash(4), Is.EqualTo(1430614333));
Assert.That(RcHashCodes.WangHash(5), Is.EqualTo(1788267159));
Assert.That(RcHashCodes.WangHash(6), Is.EqualTo(2145921005));
Assert.That(RcHashCodes.WangHash(7), Is.EqualTo(2503556531));
Assert.That(RcHashCodes.WangHash(8), Is.EqualTo(2861226262));
Assert.That(RcHashCodes.WangHash(9), Is.EqualTo(3218863982));
Assert.That(RcHashCodes.WangHash(10), Is.EqualTo(3576533554));
Assert.That(RcHashCodes.WangHash(11), Is.EqualTo(3934169234));
//
Assert.That(RcHashCodes.WangHash(int.MaxValue), Is.EqualTo(1755403298));
Assert.That(RcHashCodes.WangHash(uint.MaxValue), Is.EqualTo(3971045735));
}
}

View File

@ -0,0 +1,59 @@
using System;
using System.Collections.Generic;
using DotRecast.Core.Buffers;
using NUnit.Framework;
namespace DotRecast.Core.Test;
public class RcRentedArrayTest
{
public List<int> RandomValues(int length)
{
var rand = new RcRand();
// excepted values
var list = new List<int>();
for (int i = 0; i < length; ++i)
{
list.Add(rand.NextInt32());
}
return list;
}
[Test]
public void TestRentedArray()
{
var rand = new RcRand();
for (int loop = 0; loop < 1024; ++loop)
{
RcRentedArray<int> rentedArray;
{
int length = Math.Max(2, (int)(rand.Next() * 2048));
var values = RandomValues(length);
using var array = RcRentedArray.Rent<int>(length);
for (int i = 0; i < array.Length; ++i)
{
array[i] = values[i];
}
for (int i = 0; i < array.Length; ++i)
{
Assert.That(array[i], Is.EqualTo(values[i]));
}
Assert.That(array[^1], Is.EqualTo(values[^1]));
Assert.Throws<IndexOutOfRangeException>(() => array[-1] = 0);
Assert.Throws<IndexOutOfRangeException>(() => array[array.Length + 1] = 0);
Assert.Throws<IndexOutOfRangeException>(() => _ = array[-1]);
Assert.Throws<IndexOutOfRangeException>(() => _ = array[array.Length + 1]);
// danger
rentedArray = array;
}
Assert.Throws<NullReferenceException>(() => rentedArray[^1] = 0);
}
}
}

View File

@ -0,0 +1,114 @@
using System;
using System.Collections;
using System.Collections.Generic;
using DotRecast.Core.Collections;
using NUnit.Framework;
namespace DotRecast.Core.Test;
public class RcSortedQueueTest
{
[Test]
public void TestEnqueueAndDequeue()
{
var sortedQueue = new RcSortedQueue<int>((a, b) => a.CompareTo(b));
var r = new RcRand();
var expectedList = new List<int>();
for (int i = 0; i < 999; ++i)
{
expectedList.Add(r.NextInt32() % 300); // allow duplication
}
// ready
foreach (var expected in expectedList)
{
sortedQueue.Enqueue(expected);
}
expectedList.Sort();
// check count
Assert.That(sortedQueue.Count(), Is.EqualTo(expectedList.Count));
Assert.That(sortedQueue.IsEmpty(), Is.False);
Assert.That(sortedQueue.ToList(), Is.EqualTo(expectedList));
// check Peek and Dequeue
for (int i = 0; i < expectedList.Count; ++i)
{
Assert.That(sortedQueue.Peek(), Is.EqualTo(expectedList[i]));
Assert.That(sortedQueue.Count(), Is.EqualTo(expectedList.Count - i));
Assert.That(sortedQueue.Dequeue(), Is.EqualTo(expectedList[i]));
Assert.That(sortedQueue.Count(), Is.EqualTo(expectedList.Count - i - 1));
}
// check count
Assert.That(sortedQueue.Count(), Is.EqualTo(0));
Assert.That(sortedQueue.IsEmpty(), Is.True);
}
[Test]
public void TestRemoveForValueType()
{
var sortedQueue = new RcSortedQueue<int>((a, b) => a.CompareTo(b));
var r = new RcRand();
var expectedList = new List<int>();
for (int i = 0; i < 999; ++i)
{
expectedList.Add(r.NextInt32() % 300); // allow duplication
}
// ready
foreach (var expected in expectedList)
{
sortedQueue.Enqueue(expected);
}
expectedList.Shuffle();
// check
Assert.That(sortedQueue.Count(), Is.EqualTo(expectedList.Count));
foreach (var expected in expectedList)
{
Assert.That(sortedQueue.Remove(expected), Is.True);
}
Assert.That(sortedQueue.IsEmpty(), Is.True);
}
[Test]
public void TestRemoveForReferenceType()
{
var sortedQueue = new RcSortedQueue<RcAtomicLong>((a, b) => a.Read().CompareTo(b.Read()));
var r = new RcRand();
var expectedList = new List<RcAtomicLong>();
for (int i = 0; i < 999; ++i)
{
expectedList.Add(new RcAtomicLong(r.NextInt32() % 300)); // allow duplication
}
// ready
foreach (var expected in expectedList)
{
sortedQueue.Enqueue(expected);
}
expectedList.Shuffle();
// check
Assert.That(sortedQueue.Count(), Is.EqualTo(expectedList.Count));
foreach (var expected in expectedList)
{
Assert.That(sortedQueue.Remove(expected), Is.True);
}
Assert.That(sortedQueue.IsEmpty(), Is.True);
}
}

View File

@ -0,0 +1,272 @@
using System;
using System.Collections.Generic;
using DotRecast.Core.Collections;
using NUnit.Framework;
namespace DotRecast.Core.Test;
public class RcStackArrayTest
{
public List<int> RandomValues(int size)
{
var rand = new RcRand();
// excepted values
var list = new List<int>();
for (int i = 0; i < size; ++i)
{
list.Add(rand.NextInt32());
}
return list;
}
[Test]
public void TestStackOverflow()
{
// normal
var array_128_512_1 = RcStackArray2<RcStackArray512<float>>.Empty; // 128 * 512 = 65536
// warn
//var array_128_512_2 = RcStackArray128<RcStackArray512<float>>.Empty; // 128 * 512 = 65536
// danger
// var array_32_512_1 = RcStackArray32<RcStackArray512<float>>.Empty; // 32 * 512 = 16384
// var array_16_512_1 = RcStackArray16<RcStackArray512<float>>.Empty; // 16 * 512 = 8192
// var array_8_512_1 = RcStackArray8<RcStackArray512<float>>.Empty; // 8 * 512 = 4196
// var array_4_256_1 = RcStackArray4<RcStackArray256<float>>.Empty; // 4 * 256 = 1024
// var array_4_64_1 = RcStackArray4<RcStackArray64<float>>.Empty; // 4 * 64 = 256
// var array_2_8_1 = RcStackArray2<RcStackArray8<float>>.Empty; // 2 * 8 = 16
// var array_2_4_1 = RcStackArray2<RcStackArray2<float>>.Empty; // 2 * 2 = 4
float f1 = 0.0f; // 1
//float f2 = 0.0f; // my system stack overflow!
Assert.That(f1, Is.EqualTo(0.0f));
}
[Test]
public void TestRcStackArray2()
{
var array = RcStackArray2<int>.Empty;
Assert.That(array.Length, Is.EqualTo(2));
var values = RandomValues(array.Length);
for (int i = 0; i < array.Length; ++i)
{
array[i] = values[i];
}
for (int i = 0; i < array.Length; ++i)
{
Assert.That(array[i], Is.EqualTo(values[i]));
}
Assert.That(array[^1], Is.EqualTo(values[^1]));
Assert.Throws<IndexOutOfRangeException>(() => array[-1] = 0);
Assert.Throws<IndexOutOfRangeException>(() => array[array.Length + 1] = 0);
Assert.Throws<IndexOutOfRangeException>(() => _ = array[-1]);
Assert.Throws<IndexOutOfRangeException>(() => _ = array[array.Length + 1]);
}
[Test]
public void TestRcStackArray4()
{
var array = RcStackArray4<int>.Empty;
Assert.That(array.Length, Is.EqualTo(4));
var values = RandomValues(array.Length);
for (int i = 0; i < array.Length; ++i)
{
array[i] = values[i];
}
for (int i = 0; i < array.Length; ++i)
{
Assert.That(array[i], Is.EqualTo(values[i]));
}
Assert.That(array[^1], Is.EqualTo(values[^1]));
Assert.Throws<IndexOutOfRangeException>(() => array[-1] = 0);
Assert.Throws<IndexOutOfRangeException>(() => array[array.Length + 1] = 0);
Assert.Throws<IndexOutOfRangeException>(() => _ = array[-1]);
Assert.Throws<IndexOutOfRangeException>(() => _ = array[array.Length + 1]);
}
[Test]
public void TestRcStackArray8()
{
var array = RcStackArray8<int>.Empty;
Assert.That(array.Length, Is.EqualTo(8));
var values = RandomValues(array.Length);
for (int i = 0; i < array.Length; ++i)
{
array[i] = values[i];
}
for (int i = 0; i < array.Length; ++i)
{
Assert.That(array[i], Is.EqualTo(values[i]));
}
Assert.That(array[^1], Is.EqualTo(values[^1]));
Assert.Throws<IndexOutOfRangeException>(() => array[-1] = 0);
Assert.Throws<IndexOutOfRangeException>(() => array[array.Length + 1] = 0);
Assert.Throws<IndexOutOfRangeException>(() => _ = array[-1]);
Assert.Throws<IndexOutOfRangeException>(() => _ = array[array.Length + 1]);
}
[Test]
public void TestRcStackArray16()
{
var array = RcStackArray16<int>.Empty;
Assert.That(array.Length, Is.EqualTo(16));
var values = RandomValues(array.Length);
for (int i = 0; i < array.Length; ++i)
{
array[i] = values[i];
}
for (int i = 0; i < array.Length; ++i)
{
Assert.That(array[i], Is.EqualTo(values[i]));
}
Assert.That(array[^1], Is.EqualTo(values[^1]));
Assert.Throws<IndexOutOfRangeException>(() => array[-1] = 0);
Assert.Throws<IndexOutOfRangeException>(() => array[array.Length + 1] = 0);
Assert.Throws<IndexOutOfRangeException>(() => _ = array[-1]);
Assert.Throws<IndexOutOfRangeException>(() => _ = array[array.Length + 1]);
}
[Test]
public void TestRcStackArray32()
{
var array = RcStackArray32<int>.Empty;
Assert.That(array.Length, Is.EqualTo(32));
var values = RandomValues(array.Length);
for (int i = 0; i < array.Length; ++i)
{
array[i] = values[i];
}
for (int i = 0; i < array.Length; ++i)
{
Assert.That(array[i], Is.EqualTo(values[i]));
}
Assert.That(array[^1], Is.EqualTo(values[^1]));
Assert.Throws<IndexOutOfRangeException>(() => array[-1] = 0);
Assert.Throws<IndexOutOfRangeException>(() => array[array.Length + 1] = 0);
Assert.Throws<IndexOutOfRangeException>(() => _ = array[-1]);
Assert.Throws<IndexOutOfRangeException>(() => _ = array[array.Length + 1]);
}
[Test]
public void TestRcStackArray64()
{
var array = RcStackArray64<int>.Empty;
Assert.That(array.Length, Is.EqualTo(64));
var values = RandomValues(array.Length);
for (int i = 0; i < array.Length; ++i)
{
array[i] = values[i];
}
for (int i = 0; i < array.Length; ++i)
{
Assert.That(array[i], Is.EqualTo(values[i]));
}
Assert.That(array[^1], Is.EqualTo(values[^1]));
Assert.Throws<IndexOutOfRangeException>(() => array[-1] = 0);
Assert.Throws<IndexOutOfRangeException>(() => array[array.Length + 1] = 0);
Assert.Throws<IndexOutOfRangeException>(() => _ = array[-1]);
Assert.Throws<IndexOutOfRangeException>(() => _ = array[array.Length + 1]);
}
[Test]
public void TestRcStackArray128()
{
var array = RcStackArray128<int>.Empty;
Assert.That(array.Length, Is.EqualTo(128));
var values = RandomValues(array.Length);
for (int i = 0; i < array.Length; ++i)
{
array[i] = values[i];
}
for (int i = 0; i < array.Length; ++i)
{
Assert.That(array[i], Is.EqualTo(values[i]));
}
Assert.That(array[^1], Is.EqualTo(values[^1]));
Assert.Throws<IndexOutOfRangeException>(() => array[-1] = 0);
Assert.Throws<IndexOutOfRangeException>(() => array[array.Length + 1] = 0);
Assert.Throws<IndexOutOfRangeException>(() => _ = array[-1]);
Assert.Throws<IndexOutOfRangeException>(() => _ = array[array.Length + 1]);
}
[Test]
public void TestRcStackArray256()
{
var array = RcStackArray256<int>.Empty;
Assert.That(array.Length, Is.EqualTo(256));
var values = RandomValues(array.Length);
for (int i = 0; i < array.Length; ++i)
{
array[i] = values[i];
}
for (int i = 0; i < array.Length; ++i)
{
Assert.That(array[i], Is.EqualTo(values[i]));
}
Assert.That(array[^1], Is.EqualTo(values[^1]));
Assert.Throws<IndexOutOfRangeException>(() => array[-1] = 0);
Assert.Throws<IndexOutOfRangeException>(() => array[array.Length + 1] = 0);
Assert.Throws<IndexOutOfRangeException>(() => _ = array[-1]);
Assert.Throws<IndexOutOfRangeException>(() => _ = array[array.Length + 1]);
}
[Test]
public void TestRcStackArray512()
{
var array = RcStackArray512<int>.Empty;
Assert.That(array.Length, Is.EqualTo(512));
var values = RandomValues(array.Length);
for (int i = 0; i < array.Length; ++i)
{
array[i] = values[i];
}
for (int i = 0; i < array.Length; ++i)
{
Assert.That(array[i], Is.EqualTo(values[i]));
}
Assert.That(array[^1], Is.EqualTo(values[^1]));
Assert.Throws<IndexOutOfRangeException>(() => array[-1] = 0);
Assert.Throws<IndexOutOfRangeException>(() => array[array.Length + 1] = 0);
Assert.Throws<IndexOutOfRangeException>(() => _ = array[-1]);
Assert.Throws<IndexOutOfRangeException>(() => _ = array[array.Length + 1]);
}
}

View File

@ -5,7 +5,7 @@ using NUnit.Framework;
namespace DotRecast.Core.Test;
public class Vector3Tests
public class Vector3Test
{
[Test]
[Repeat(100000)]

View File

@ -25,7 +25,7 @@ using NUnit.Framework;
namespace DotRecast.Detour.Crowd.Test;
[Parallelizable]
public class AbstractCrowdTest
{
protected readonly long[] startRefs =

View File

@ -22,7 +22,7 @@ using NUnit.Framework;
namespace DotRecast.Detour.Crowd.Test;
[Parallelizable]
public class Crowd1Test : AbstractCrowdTest
{
static readonly float[][] EXPECTED_A1Q0TVTA =

View File

@ -24,7 +24,7 @@ using NUnit.Framework;
namespace DotRecast.Detour.Crowd.Test;
[Parallelizable]
public class Crowd4Test : AbstractCrowdTest
{
static readonly float[][] EXPECTED_A1Q2TVTA =

View File

@ -22,7 +22,7 @@ using NUnit.Framework;
namespace DotRecast.Detour.Crowd.Test;
[Parallelizable]
public class Crowd4VelocityTest : AbstractCrowdTest
{
static readonly float[][] EXPECTED_A1Q3TVTA =

View File

@ -1,21 +1,21 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>net6.0;net7.0</TargetFrameworks>
<TargetFrameworks>net6.0;net7.0;net8.0</TargetFrameworks>
<IsPackable>false</IsPackable>
<IsTestProject>true</IsTestProject>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.7.2" />
<PackageReference Include="Moq" Version="4.20.69" />
<PackageReference Include="NUnit" Version="3.13.3"/>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.9.0" />
<PackageReference Include="Moq" Version="4.20.70" />
<PackageReference Include="NUnit" Version="4.0.1" />
<PackageReference Include="NUnit3TestAdapter" Version="4.5.0"/>
<PackageReference Include="NUnit.Analyzers" Version="3.9.0">
<PackageReference Include="NUnit.Analyzers" Version="4.0.1">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="coverlet.collector" Version="6.0.0">
<PackageReference Include="coverlet.collector" Version="6.0.1">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>

View File

@ -25,7 +25,7 @@ using NUnit.Framework;
namespace DotRecast.Detour.Crowd.Test;
[Parallelizable]
public class PathCorridorTest
{
private readonly DtPathCorridor corridor = new DtPathCorridor();
@ -34,6 +34,7 @@ public class PathCorridorTest
[SetUp]
public void SetUp()
{
corridor.Init(256);
corridor.Reset(0, new RcVec3f(10, 20, 30));
}
@ -59,7 +60,7 @@ public class PathCorridorTest
{
refStraightPath = straightPath;
})
.Returns(() => DtStatus.DT_SUCCSESS);
.Returns(() => DtStatus.DT_SUCCESS);
var path = new List<DtStraightPath>();
corridor.FindCorners(ref path, int.MaxValue, mockQuery.Object, filter);
@ -90,7 +91,7 @@ public class PathCorridorTest
{
refStraightPath = straightPath;
})
.Returns(() => DtStatus.DT_SUCCSESS);
.Returns(() => DtStatus.DT_SUCCESS);
var path = new List<DtStraightPath>();
corridor.FindCorners(ref path, int.MaxValue, mockQuery.Object, filter);

View File

@ -1,21 +1,21 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>net6.0;net7.0</TargetFrameworks>
<TargetFrameworks>net6.0;net7.0;net8.0</TargetFrameworks>
<IsPackable>false</IsPackable>
<IsTestProject>true</IsTestProject>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.7.2" />
<PackageReference Include="Moq" Version="4.20.69" />
<PackageReference Include="NUnit" Version="3.13.3"/>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.9.0" />
<PackageReference Include="Moq" Version="4.20.70" />
<PackageReference Include="NUnit" Version="4.0.1" />
<PackageReference Include="NUnit3TestAdapter" Version="4.5.0"/>
<PackageReference Include="NUnit.Analyzers" Version="3.9.0">
<PackageReference Include="NUnit.Analyzers" Version="4.0.1">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="coverlet.collector" Version="6.0.0">
<PackageReference Include="coverlet.collector" Version="6.0.1">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>

View File

@ -10,7 +10,7 @@ using NUnit.Framework;
namespace DotRecast.Detour.Dynamic.Test;
[Parallelizable]
public class DynamicNavMeshTest
{
private static readonly RcVec3f START_POS = new RcVec3f(70.87453f, 0.0010070801f, 86.69021f);

View File

@ -47,7 +47,7 @@ namespace DotRecast.Detour.Dynamic.Test.Io
byte[] compressed = LZ4Pickler.Pickle(data, LZ4Level.L12_MAX);
byte[] result = new byte[4 + compressed.Length];
RcByteUtils.PutInt(compressed.Length, result, 0, RcByteOrder.BIG_ENDIAN);
Array.Copy(compressed, 0, result, 4, compressed.Length);
RcArrays.Copy(compressed, 0, result, 4, compressed.Length);
return result;
}
}

View File

@ -25,7 +25,7 @@ using NUnit.Framework;
namespace DotRecast.Detour.Dynamic.Test.Io;
[Parallelizable]
public class VoxelFileReaderTest
{
[Test]

View File

@ -25,7 +25,7 @@ using NUnit.Framework;
namespace DotRecast.Detour.Dynamic.Test.Io;
[Parallelizable]
public class VoxelFileReaderWriterTest
{
[TestCase(false)]

View File

@ -31,7 +31,7 @@ using NUnit.Framework;
namespace DotRecast.Detour.Dynamic.Test;
[Parallelizable]
public class VoxelQueryTest
{
private const int TILE_WIDTH = 100;

View File

@ -1,21 +1,21 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>net6.0;net7.0</TargetFrameworks>
<TargetFrameworks>net6.0;net7.0;net8.0</TargetFrameworks>
<IsPackable>false</IsPackable>
<IsTestProject>true</IsTestProject>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.7.2" />
<PackageReference Include="Moq" Version="4.20.69" />
<PackageReference Include="NUnit" Version="3.13.3"/>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.9.0" />
<PackageReference Include="Moq" Version="4.20.70" />
<PackageReference Include="NUnit" Version="4.0.1" />
<PackageReference Include="NUnit3TestAdapter" Version="4.5.0"/>
<PackageReference Include="NUnit.Analyzers" Version="3.9.0">
<PackageReference Include="NUnit.Analyzers" Version="4.0.1">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="coverlet.collector" Version="6.0.0">
<PackageReference Include="coverlet.collector" Version="6.0.1">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>

View File

@ -26,7 +26,7 @@ using NUnit.Framework;
namespace DotRecast.Detour.Extras.Test.Unity.Astar;
[Parallelizable]
public class UnityAStarPathfindingImporterTest
{
[Test]
@ -37,7 +37,7 @@ public class UnityAStarPathfindingImporterTest
RcVec3f endPos = new RcVec3f(11.971109f, 0.000000f, 8.663261f);
var path = new List<long>();
var status = FindPath(mesh, startPos, endPos, ref path);
Assert.That(status, Is.EqualTo(DtStatus.DT_SUCCSESS));
Assert.That(status, Is.EqualTo(DtStatus.DT_SUCCESS));
Assert.That(path.Count, Is.EqualTo(57));
SaveMesh(mesh, "v4_0_6");
}

View File

@ -21,7 +21,7 @@ using NUnit.Framework;
namespace DotRecast.Detour.Test;
[Parallelizable]
public abstract class AbstractDetourTest
{
protected static readonly long[] startRefs =

View File

@ -21,7 +21,7 @@ using NUnit.Framework;
namespace DotRecast.Detour.Test;
[Parallelizable]
public class ConvexConvexIntersectionTest
{
[Test]

View File

@ -1,21 +1,21 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>net6.0;net7.0</TargetFrameworks>
<TargetFrameworks>net6.0;net7.0;net8.0</TargetFrameworks>
<IsPackable>false</IsPackable>
<IsTestProject>true</IsTestProject>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.7.2" />
<PackageReference Include="Moq" Version="4.20.69" />
<PackageReference Include="NUnit" Version="3.13.3"/>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.9.0" />
<PackageReference Include="Moq" Version="4.20.70" />
<PackageReference Include="NUnit" Version="4.0.1" />
<PackageReference Include="NUnit3TestAdapter" Version="4.5.0"/>
<PackageReference Include="NUnit.Analyzers" Version="3.9.0">
<PackageReference Include="NUnit.Analyzers" Version="4.0.1">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="coverlet.collector" Version="6.0.0">
<PackageReference Include="coverlet.collector" Version="6.0.1">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>

View File

@ -0,0 +1,84 @@
using System.Collections.Immutable;
using System.Linq;
using NUnit.Framework;
namespace DotRecast.Detour.Test;
public class DtNodePoolTest
{
[Test]
public void TestGetNode()
{
var pool = new DtNodePool();
var node1St = pool.GetNode(0);
var node2St = pool.GetNode(0);
Assert.That(node1St, Is.SameAs(node2St));
node1St.state = 1;
var node3St = pool.GetNode(0);
Assert.That(node1St, Is.Not.SameAs(node3St));
}
[Test]
public void TestFindNode()
{
var pool = new DtNodePool();
var counts = ImmutableArray.Create(2, 3, 5);
// get and create
for (int i = 0; i < counts.Length; ++i)
{
var count = counts[i];
for (int ii = 0; ii < count; ++ii)
{
var node = pool.GetNode(i);
node.state = ii + 1;
}
}
int sum = counts.Sum();
Assert.That(sum, Is.EqualTo(10));
// check GetNodeIdx GetNodeAtIdx
for (int i = 0; i < sum; ++i)
{
var node = pool.GetNodeAtIdx(i);
var nodeIdx = pool.GetNodeIdx(node);
var nodeByIdx = pool.GetNodeAtIdx(nodeIdx);
Assert.That(node, Is.SameAs(nodeByIdx));
Assert.That(nodeIdx, Is.EqualTo(i));
}
// check count
for (int i = 0; i < counts.Length; ++i)
{
var count = counts[i];
var n = pool.FindNodes(i, out var nodes);
Assert.That(n, Is.EqualTo(count));
Assert.That(nodes, Has.Count.EqualTo(count));
var node = pool.FindNode(i);
Assert.That(nodes[0], Is.SameAs(node));
var node2 = pool.FindNode(i);
Assert.That(nodes[0], Is.SameAs(node2));
}
// check other count
{
var n = pool.FindNodes(4, out var nodes);
Assert.That(n, Is.EqualTo(0));
Assert.That(nodes, Is.Null);
}
var totalCount = pool.GetNodeCount();
Assert.That(totalCount, Is.EqualTo(sum));
pool.Clear();
totalCount = pool.GetNodeCount();
Assert.That(totalCount, Is.EqualTo(0));
}
}

View File

@ -0,0 +1,112 @@
using System.Collections.Generic;
using DotRecast.Core;
using DotRecast.Core.Collections;
using NUnit.Framework;
namespace DotRecast.Detour.Test;
public class DtNodeQueueTest
{
private static List<DtNode> ShuffledNodes(int count)
{
var nodes = new List<DtNode>();
for (int i = 0; i < count; ++i)
{
var node = new DtNode(i);
node.total = i;
nodes.Add(node);
}
nodes.Shuffle();
return nodes;
}
[Test]
public void TestPushAndPop()
{
var queue = new DtNodeQueue();
// check count
Assert.That(queue.Count(), Is.EqualTo(0));
// null push
queue.Push(null);
Assert.That(queue.Count(), Is.EqualTo(0));
// test push
const int count = 1000;
var expectedNodes = ShuffledNodes(count);
foreach (var node in expectedNodes)
{
queue.Push(node);
}
Assert.That(queue.Count(), Is.EqualTo(count));
// test pop
expectedNodes.Sort(DtNode.ComparisonNodeTotal);
foreach (var node in expectedNodes)
{
Assert.That(queue.Peek(), Is.SameAs(node));
Assert.That(queue.Pop(), Is.SameAs(node));
}
Assert.That(queue.Count(), Is.EqualTo(0));
}
[Test]
public void TestClear()
{
var queue = new DtNodeQueue();
const int count = 555;
var expectedNodes = ShuffledNodes(count);
foreach (var node in expectedNodes)
{
queue.Push(node);
}
Assert.That(queue.Count(), Is.EqualTo(count));
queue.Clear();
Assert.That(queue.Count(), Is.EqualTo(0));
Assert.That(queue.IsEmpty(), Is.True);
}
[Test]
public void TestModify()
{
var queue = new DtNodeQueue();
const int count = 5000;
var expectedNodes = ShuffledNodes(count);
foreach (var node in expectedNodes)
{
queue.Push(node);
}
// check modify
queue.Modify(null);
// change total
var r = new RcRand();
foreach (var node in expectedNodes)
{
node.total = r.NextInt32() % (count / 50); // duplication for test
}
// test modify
foreach (var node in expectedNodes)
{
queue.Modify(node);
}
// check
expectedNodes.Sort(DtNode.ComparisonNodeTotal);
foreach (var node in expectedNodes)
{
Assert.That(queue.Pop(), Is.SameAs(node));
}
}
}

View File

@ -22,7 +22,7 @@ using NUnit.Framework;
namespace DotRecast.Detour.Test;
[Parallelizable]
public class FindDistanceToWallTest : AbstractDetourTest
{
private static readonly float[] DISTANCES_TO_WALL = { 0.597511f, 3.201085f, 0.603713f, 2.791475f, 2.815544f };

View File

@ -22,7 +22,7 @@ using NUnit.Framework;
namespace DotRecast.Detour.Test;
[Parallelizable]
public class FindLocalNeighbourhoodTest : AbstractDetourTest
{
private static readonly long[][] REFS =

View File

@ -21,7 +21,7 @@ using NUnit.Framework;
namespace DotRecast.Detour.Test;
[Parallelizable]
public class FindNearestPolyTest : AbstractDetourTest
{
private static readonly long[] POLY_REFS = { 281474976710696L, 281474976710773L, 281474976710680L, 281474976710753L, 281474976710733L };

View File

@ -22,16 +22,16 @@ using NUnit.Framework;
namespace DotRecast.Detour.Test;
[Parallelizable]
public class FindPathTest : AbstractDetourTest
{
private static readonly DtStatus[] STATUSES =
{
DtStatus.DT_SUCCSESS,
DtStatus.DT_SUCCSESS | DtStatus.DT_PARTIAL_RESULT,
DtStatus.DT_SUCCSESS,
DtStatus.DT_SUCCSESS,
DtStatus.DT_SUCCSESS
DtStatus.DT_SUCCESS,
DtStatus.DT_SUCCESS | DtStatus.DT_PARTIAL_RESULT,
DtStatus.DT_SUCCESS,
DtStatus.DT_SUCCESS,
DtStatus.DT_SUCCESS
};
private static readonly long[][] RESULTS =

View File

@ -22,7 +22,7 @@ using NUnit.Framework;
namespace DotRecast.Detour.Test;
[Parallelizable]
public class FindPolysAroundCircleTest : AbstractDetourTest
{
private static readonly long[][] REFS =

View File

@ -22,7 +22,7 @@ using NUnit.Framework;
namespace DotRecast.Detour.Test;
[Parallelizable]
public class FindPolysAroundShapeTest : AbstractDetourTest
{
private static readonly long[][] REFS =

View File

@ -22,7 +22,7 @@ using NUnit.Framework;
namespace DotRecast.Detour.Test;
[Parallelizable]
public class GetPolyWallSegmentsTest : AbstractDetourTest
{
private static readonly RcSegmentVert[][] VERTICES =

View File

@ -23,7 +23,7 @@ using NUnit.Framework;
namespace DotRecast.Detour.Test.Io;
[Parallelizable]
public class MeshDataReaderWriterTest
{
private const int VERTS_PER_POLYGON = 6;
@ -131,7 +131,7 @@ public class MeshDataReaderWriterTest
Assert.That(readData.offMeshCons[i].poly, Is.EqualTo(meshData.offMeshCons[i].poly));
Assert.That(readData.offMeshCons[i].side, Is.EqualTo(meshData.offMeshCons[i].side));
Assert.That(readData.offMeshCons[i].userId, Is.EqualTo(meshData.offMeshCons[i].userId));
for (int j = 0; j < 6; j++)
for (int j = 0; j < 2; j++)
{
Assert.That(readData.offMeshCons[i].pos[j], Is.EqualTo(meshData.offMeshCons[i].pos[j]));
}

View File

@ -24,7 +24,7 @@ using NUnit.Framework;
namespace DotRecast.Detour.Test.Io;
[Parallelizable]
public class MeshSetReaderTest
{
private readonly DtMeshSetReader reader = new DtMeshSetReader();

View File

@ -28,7 +28,7 @@ using NUnit.Framework;
namespace DotRecast.Detour.Test.Io;
[Parallelizable]
public class MeshSetReaderWriterTest
{
private readonly DtMeshSetWriter writer = new DtMeshSetWriter();

View File

@ -22,7 +22,7 @@ using NUnit.Framework;
namespace DotRecast.Detour.Test;
[Parallelizable]
public class MoveAlongSurfaceTest : AbstractDetourTest
{
private static readonly long[][] VISITED =

View File

@ -16,11 +16,12 @@ freely, subject to the following restrictions:
3. This notice may not be removed or altered from any source distribution.
*/
using DotRecast.Core.Numerics;
using NUnit.Framework;
namespace DotRecast.Detour.Test;
[Parallelizable]
public class NavMeshBuilderTest
{
private DtMeshData nmd;
@ -49,9 +50,9 @@ public class NavMeshBuilderTest
Assert.That(nmd.bvTree[i], Is.Not.Null);
}
for (int i = 0; i < 6; i++)
for (int i = 0; i < 2; i++)
{
Assert.That(nmd.verts[223 * 3 + i], Is.EqualTo(nmd.offMeshCons[0].pos[i]));
Assert.That(RcVecUtils.Create(nmd.verts, 223 * 3 + (i * 3)), Is.EqualTo(nmd.offMeshCons[0].pos[i]));
}
Assert.That(nmd.offMeshCons[0].rad, Is.EqualTo(0.1f));

View File

@ -22,7 +22,7 @@ using NUnit.Framework;
namespace DotRecast.Detour.Test;
[Parallelizable]
public class PolygonByCircleConstraintTest
{
private readonly IDtPolygonByCircleConstraint _constraint = DtStrictDtPolygonByCircleConstraint.Shared;

View File

@ -25,7 +25,7 @@ using NUnit.Framework;
namespace DotRecast.Detour.Test;
[Parallelizable]
public class RandomPointTest : AbstractDetourTest
{
[Test]

View File

@ -23,10 +23,10 @@ using NUnit.Framework;
namespace DotRecast.Detour.Test;
[Parallelizable]
public class TiledFindPathTest
{
private static readonly DtStatus[] STATUSES = { DtStatus.DT_SUCCSESS };
private static readonly DtStatus[] STATUSES = { DtStatus.DT_SUCCESS };
private static readonly long[][] RESULTS =
{

View File

@ -27,7 +27,7 @@ using NUnit.Framework;
namespace DotRecast.Detour.TileCache.Test;
[Parallelizable]
public class AbstractTileCacheTest
{
private const int EXPECTED_LAYERS_PER_TILE = 4;

View File

@ -1,21 +1,21 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>net6.0;net7.0</TargetFrameworks>
<TargetFrameworks>net6.0;net7.0;net8.0</TargetFrameworks>
<IsPackable>false</IsPackable>
<IsTestProject>true</IsTestProject>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.7.2" />
<PackageReference Include="Moq" Version="4.20.69" />
<PackageReference Include="NUnit" Version="3.13.3"/>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.9.0" />
<PackageReference Include="Moq" Version="4.20.70" />
<PackageReference Include="NUnit" Version="4.0.1" />
<PackageReference Include="NUnit3TestAdapter" Version="4.5.0"/>
<PackageReference Include="NUnit.Analyzers" Version="3.9.0">
<PackageReference Include="NUnit.Analyzers" Version="4.0.1">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="coverlet.collector" Version="6.0.0">
<PackageReference Include="coverlet.collector" Version="6.0.1">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>

View File

@ -27,7 +27,7 @@ using NUnit.Framework;
namespace DotRecast.Detour.TileCache.Test.Io;
[Parallelizable]
public class TileCacheReaderTest
{
private readonly DtTileCacheReader reader = new DtTileCacheReader(DtTileCacheCompressorFactory.Shared);

View File

@ -28,7 +28,7 @@ using NUnit.Framework;
namespace DotRecast.Detour.TileCache.Test.Io;
[Parallelizable]
public class TileCacheReaderWriterTest : AbstractTileCacheTest
{
private readonly DtTileCacheReader reader = new DtTileCacheReader(DtTileCacheCompressorFactory.Shared);

View File

@ -26,7 +26,7 @@ using NUnit.Framework;
namespace DotRecast.Detour.TileCache.Test;
[Parallelizable]
public class TempObstaclesTest : AbstractTileCacheTest
{
[Test]

View File

@ -29,7 +29,7 @@ using NUnit.Framework;
namespace DotRecast.Detour.TileCache.Test;
[Parallelizable]
public class TileCacheFindPathTest : AbstractTileCacheTest
{
private readonly RcVec3f start = new RcVec3f(39.44734f, 9.998177f, -0.784811f);

View File

@ -26,14 +26,14 @@ using NUnit.Framework;
namespace DotRecast.Detour.TileCache.Test;
[Parallelizable]
public class TileCacheNavigationTest : AbstractTileCacheTest
{
protected readonly long[] startRefs = { 281475006070787L };
protected readonly long[] endRefs = { 281474986147841L };
protected readonly RcVec3f[] startPoss = { new RcVec3f(39.447338f, 9.998177f, -0.784811f) };
protected readonly RcVec3f[] endPoss = { new RcVec3f(19.292645f, 11.611748f, -57.750366f) };
private readonly DtStatus[] statuses = { DtStatus.DT_SUCCSESS };
private readonly DtStatus[] statuses = { DtStatus.DT_SUCCESS };
private readonly long[][] results =
{

View File

@ -26,7 +26,7 @@ using NUnit.Framework;
namespace DotRecast.Detour.TileCache.Test;
[Parallelizable]
public class TileCacheTest : AbstractTileCacheTest
{
[Test]

View File

@ -1,21 +1,21 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>net6.0;net7.0</TargetFrameworks>
<TargetFrameworks>net6.0;net7.0;net8.0</TargetFrameworks>
<IsPackable>false</IsPackable>
<IsTestProject>true</IsTestProject>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.7.2" />
<PackageReference Include="Moq" Version="4.20.69" />
<PackageReference Include="NUnit" Version="3.13.3"/>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.9.0" />
<PackageReference Include="Moq" Version="4.20.70" />
<PackageReference Include="NUnit" Version="4.0.1" />
<PackageReference Include="NUnit3TestAdapter" Version="4.5.0"/>
<PackageReference Include="NUnit.Analyzers" Version="3.9.0">
<PackageReference Include="NUnit.Analyzers" Version="4.0.1">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="coverlet.collector" Version="6.0.0">
<PackageReference Include="coverlet.collector" Version="6.0.1">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>

View File

@ -26,7 +26,7 @@ namespace DotRecast.Recast.Test;
using static RcConstants;
[Parallelizable]
public class RecastLayersTest
{
private const float m_cellSize = 0.3f;

View File

@ -28,7 +28,7 @@ namespace DotRecast.Recast.Test;
using static RcConstants;
using static RcAreas;
[Parallelizable]
public class RecastSoloMeshTest
{
private const float m_cellSize = 0.3f;
@ -101,7 +101,7 @@ public class RecastSoloMeshTest
long time = RcFrequency.Ticks;
RcVec3f bmin = geomProvider.GetMeshBoundsMin();
RcVec3f bmax = geomProvider.GetMeshBoundsMax();
RcTelemetry m_ctx = new RcTelemetry();
RcContext m_ctx = new RcContext();
//
// Step 1. Initialize build config.
//
@ -140,7 +140,7 @@ public class RecastSoloMeshTest
// If your input data is multiple meshes, you can transform them here, calculate
// the are type for each of the meshes and rasterize them.
int[] m_triareas = RcCommons.MarkWalkableTriangles(m_ctx, cfg.WalkableSlopeAngle, verts, tris, ntris, cfg.WalkableAreaMod);
RcRasterizations.RasterizeTriangles(m_solid, verts, tris, m_triareas, ntris, cfg.WalkableClimb, m_ctx);
RcRasterizations.RasterizeTriangles(m_ctx, verts, tris, m_triareas, ntris, m_solid, cfg.WalkableClimb);
}
//

View File

@ -23,7 +23,7 @@ namespace DotRecast.Recast.Test;
using static RcConstants;
[Parallelizable]
public class RecastTest
{
[Test]
@ -36,7 +36,7 @@ public class RecastTest
int[] unwalkable_tri = { 0, 2, 1 };
int nt = 1;
RcTelemetry ctx = new RcTelemetry();
RcContext ctx = new RcContext();
{
int[] areas = { 42 };
RcCommons.ClearUnwalkableTriangles(ctx, walkableSlopeAngle, verts, nv, unwalkable_tri, nt, areas);

View File

@ -27,7 +27,7 @@ using NUnit.Framework;
namespace DotRecast.Recast.Test;
[Parallelizable]
public class RecastTileMeshTest
{
private const float m_cellSize = 0.3f;