Compare commits

..

2 Commits

Author SHA1 Message Date
Gabriel Alexandre 23405ea5a7 Migrated some small types to structs 2023-10-29 11:38:37 +09:00
Gabriel Alexandre 70c9adeb2f Merged Build Regios of Layered and Monotone as they are very similar 2023-10-29 11:38:37 +09:00
490 changed files with 6185 additions and 14813 deletions

View File

@ -20,7 +20,6 @@ dotnet_sort_system_directives_first = true
csharp_preserve_single_line_statements = false csharp_preserve_single_line_statements = false
csharp_preserve_single_line_blocks = true csharp_preserve_single_line_blocks = true
# ReSharper properties #
resharper_csharp_wrap_lines = false
resharper_csharp_space_before_trailing_comment = true resharper_csharp_space_before_trailing_comment = true
resharper_csharp_space_after_operator_keyword = true resharper_csharp_space_after_operator_keyword = true

3
.github/FUNDING.yml vendored
View File

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

View File

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

View File

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

View File

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

2
.vscode/launch.json vendored
View File

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

View File

@ -1,97 +0,0 @@

## 🔨 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)

View File

@ -1,192 +0,0 @@
# 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 RcBinaryMinHeap ([@Sarofc](https://github.com/Sarofc))
- Added DotRecast.Benchmark ([@Sarofc](https://github.com/Sarofc))
### Fixed
- Fix raycast shortcuts ([@Sarofc](https://github.com/Sarofc)) [#72](https://github.com/ikpil/DotRecast/issues/72)
- Fix dynamic mesh bounds calculation ([@ppiastucki](https://github.com/ppiastucki)) [#77](https://github.com/ikpil/DotRecast/issues/77)
- issuer : [@OhJeongrok](https://github.com/OhJeongrok)
- Fix Support non-tiled dynamic nav meshes ([@ppiastucki](https://github.com/ppiastucki))
### Changed
- Changed data structure of 'neis' from List<byte> to byte[] for optimized memory usage and improved access speed in `DtLayerMonotoneRegion`
- Changed new RcVec3f[3] to stackalloc RcVec3f[3] in DtNavMesh.GetPolyHeight() to reduce heap allocation
- Changed memory handling to use stackalloc in DtNavMeshQuery.GetPolyWallSegments for reducing SOH
- Changed DtNavMeshQuery.GetPolyWallSegments() to use Span<T> for enhanced performance, memory efficiency.
- Changed bmin/bmax from int[] to RcVec3i for improved memory efficiency
### Removed
- Nothing
### Special Thanks
- [@Doprez](https://github.com/Doprez)
## [2024.3.1] - 2024-07-09
### Added
- Nothing
### Fixed
- Fixed bug where the dynamic voxel save file browser doesn't appear in `Recast.Demo`
### Changed
- Changed to reuse samples and edges list in `BuildPolyDetail()`
- Changed `heights`, `areas`, `cons`, and `regs` arrays to byte arrays for uniformity and efficiency in `DtTileCacheLayer`
- Changed `reg`, `area` arrays to byte arrays for uniformity and efficiency in `DtTileCacheContour`
- Changed `RcChunkyTriMesh` to separate the function and variable.
- Changed to consolidate vector-related functions into one place.
- Changed stack handling from List to a fixed-size array with manual index management for optimization in `RcLayers.BuildHeightfieldLayers()`
- Changed to use Span<byte> and stackalloc for improved performance and memory management in `RcLayers.BuildHeightfieldLayers()`
- Changed vertCount and triCount to byte in `DtPolyDetail`
- Changed `new float[]` to `stackalloc float[]` in `DtConvexConvexIntersections.Intersect()`
- Changed agents management from list to dictionary in `DtCrowd`
- Changed to efficiently stack nearby DtCrowdAgents in `DtCrowd.GetNeighbours()`
- Changed to limit neighbor search to a maximum count and use array for memory efficiency in `DtCrowd.AddNeighbour()`
### Removed
- Removed RcMeshDetails.VdistSq2(float[], float[])
- Removed RcVecUtils.Dot()
- Removed RcVecUtils.Scale()
- Removed RcVecUtils.Subtract(RcVec3f i, float[] verts, int j)
- Removed RcVecUtils.Subtract(float[] verts, int i, int j)
- Removed RcVecUtils.Min(), RcVecUtils.Max()
- Removed RcVecUtils.Create(float[] values)
- Removed RcVecUtils.Dot2D(this RcVec3f @this, Span<float> v, int vi)
### Special Thanks
- [@Doprez](https://github.com/Doprez)
## [2024.2.3] - 2024-06-03
### Added
- Added `DtCollectPolysQuery` and `FindCollectPolyTest`
### Fixed
- Nothing
### Changed
- Changed `IDtPolyQuery` interface to make `Process()` more versatile
- Changed `PolyQueryInvoker` to `DtActionPolyQuery`
- Changed `DtTileCacheBuilder` to a static class
- Changed `DtTileCacheLayerHeaderReader` to a static class
- Changed `Dictionary<int, List<DtMeshTile>>` to `DtMeshTile[]` to optimize memory usage
- Changed `MAX_STEER_POINTS` from class constant to local.
- Changed `List<DtStraightPath>` to `Span<DtStraightPath>` for enhanced memory efficiency
- Changed `DtWriter` to a static class and renamed it to `RcIO`
- Changed class `Trajectory` to interface `ITrajectory`
### Removed
- Nothing
### Special Thanks
- [@Doprez](https://github.com/Doprez)
## [2024.2.2] - 2024-05-18
### Added
- Added RcSpans UnitTest
### Fixed
- Nothing
### Changed
- Changed class name of static functions to RcRecast and DtDetour
- Changed DtLink class member variable type from int to byte
- Changed initialization in DtNavMesh constructor to Init() function.
### Removed
- Nothing
### Special Thanks
- [@Doprez](https://github.com/Doprez)
## [2024.2.1] - 2024-05-04
### 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)
- Added RcSpans [@ikpil](https://github.com/ikpil)
### Fixed
- SOH issue [#14](https://github.com/ikpil/DotRecast/issues/41)
- Optimization: reduce number of allocations on hot path. [@awgil](https://github.com/awgil)
### 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)

View File

@ -39,12 +39,6 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DotRecast.Detour.Extras.Tes
EndProject EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DotRecast.Detour.TileCache.Test", "test\DotRecast.Detour.TileCache.Test\DotRecast.Detour.TileCache.Test.csproj", "{3CAA7306-088E-4373-A406-99755CC2B605}" Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DotRecast.Detour.TileCache.Test", "test\DotRecast.Detour.TileCache.Test\DotRecast.Detour.TileCache.Test.csproj", "{3CAA7306-088E-4373-A406-99755CC2B605}"
EndProject EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DotRecast.Benchmark", "test\DotRecast.Benchmark\DotRecast.Benchmark.csproj", "{D1EFC625-D095-4208-98A2-112B73CB40B0}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tool", "tool", "{9C213609-BF13-4024-816E-A6ADD641DF24}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DotRecast.Tool.PublishToUniRecast", "tool\DotRecast.Tool.PublishToUniRecast\DotRecast.Tool.PublishToUniRecast.csproj", "{1822BBA8-FE4E-4C5F-B9C0-3E20E6353446}"
EndProject
Global Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU Debug|Any CPU = Debug|Any CPU
@ -118,33 +112,23 @@ Global
{10395C8F-DFBD-4263-8A20-EA3500A6E55A}.Debug|Any CPU.Build.0 = Debug|Any CPU {10395C8F-DFBD-4263-8A20-EA3500A6E55A}.Debug|Any CPU.Build.0 = Debug|Any CPU
{10395C8F-DFBD-4263-8A20-EA3500A6E55A}.Release|Any CPU.ActiveCfg = Release|Any CPU {10395C8F-DFBD-4263-8A20-EA3500A6E55A}.Release|Any CPU.ActiveCfg = Release|Any CPU
{10395C8F-DFBD-4263-8A20-EA3500A6E55A}.Release|Any CPU.Build.0 = Release|Any CPU {10395C8F-DFBD-4263-8A20-EA3500A6E55A}.Release|Any CPU.Build.0 = Release|Any CPU
{D1EFC625-D095-4208-98A2-112B73CB40B0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{D1EFC625-D095-4208-98A2-112B73CB40B0}.Debug|Any CPU.Build.0 = Debug|Any CPU
{D1EFC625-D095-4208-98A2-112B73CB40B0}.Release|Any CPU.ActiveCfg = Release|Any CPU
{D1EFC625-D095-4208-98A2-112B73CB40B0}.Release|Any CPU.Build.0 = Release|Any CPU
{1822BBA8-FE4E-4C5F-B9C0-3E20E6353446}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{1822BBA8-FE4E-4C5F-B9C0-3E20E6353446}.Debug|Any CPU.Build.0 = Debug|Any CPU
{1822BBA8-FE4E-4C5F-B9C0-3E20E6353446}.Release|Any CPU.ActiveCfg = Release|Any CPU
{1822BBA8-FE4E-4C5F-B9C0-3E20E6353446}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection EndGlobalSection
GlobalSection(NestedProjects) = preSolution GlobalSection(NestedProjects) = preSolution
{FFE40BBF-843B-41FA-8504-F4ABD166762E} = {8ED75CF7-A3D6-423D-8499-9316DD413DAD} {FFE40BBF-843B-41FA-8504-F4ABD166762E} = {8ED75CF7-A3D6-423D-8499-9316DD413DAD}
{38933A87-4568-40A5-A3DA-E2445E8C2B99} = {8ED75CF7-A3D6-423D-8499-9316DD413DAD} {38933A87-4568-40A5-A3DA-E2445E8C2B99} = {8ED75CF7-A3D6-423D-8499-9316DD413DAD}
{C19E4BFA-63A0-4815-9815-869A9DC52DBC} = {8ED75CF7-A3D6-423D-8499-9316DD413DAD} {C19E4BFA-63A0-4815-9815-869A9DC52DBC} = {8ED75CF7-A3D6-423D-8499-9316DD413DAD}
{FA7EF26A-BA47-43FD-86F8-0A33CFDF643F} = {8ED75CF7-A3D6-423D-8499-9316DD413DAD}
{53AF87DA-37F8-4504-B623-B2113F4438CA} = {8ED75CF7-A3D6-423D-8499-9316DD413DAD}
{17E4F2F0-FC27-416E-9CB6-9F2CAAC49C9D} = {8ED75CF7-A3D6-423D-8499-9316DD413DAD}
{DEB16B90-CCD4-497E-A2E9-4CC66FD7EF47} = {8ED75CF7-A3D6-423D-8499-9316DD413DAD}
{023E1E6A-4895-4573-89AE-3D5D8E0B39C8} = {8ED75CF7-A3D6-423D-8499-9316DD413DAD}
{DF987948-8C23-4337-AF83-D87D6407518D} = {8ED75CF7-A3D6-423D-8499-9316DD413DAD}
{88754FE2-A05A-4D4D-A81A-90418AD32362} = {A7CB8D8B-70DA-4567-8316-0659FCAE1C73} {88754FE2-A05A-4D4D-A81A-90418AD32362} = {A7CB8D8B-70DA-4567-8316-0659FCAE1C73}
{554CB5BD-D58A-4856-BFE1-666A62C9BEA3} = {A7CB8D8B-70DA-4567-8316-0659FCAE1C73} {554CB5BD-D58A-4856-BFE1-666A62C9BEA3} = {A7CB8D8B-70DA-4567-8316-0659FCAE1C73}
{FA7EF26A-BA47-43FD-86F8-0A33CFDF643F} = {8ED75CF7-A3D6-423D-8499-9316DD413DAD}
{F9C5B52E-C01D-4514-94E9-B1A6895352E2} = {A7CB8D8B-70DA-4567-8316-0659FCAE1C73} {F9C5B52E-C01D-4514-94E9-B1A6895352E2} = {A7CB8D8B-70DA-4567-8316-0659FCAE1C73}
{53AF87DA-37F8-4504-B623-B2113F4438CA} = {8ED75CF7-A3D6-423D-8499-9316DD413DAD}
{67C68B34-118A-439C-88E1-D6D1ED78DC59} = {A7CB8D8B-70DA-4567-8316-0659FCAE1C73} {67C68B34-118A-439C-88E1-D6D1ED78DC59} = {A7CB8D8B-70DA-4567-8316-0659FCAE1C73}
{17E4F2F0-FC27-416E-9CB6-9F2CAAC49C9D} = {8ED75CF7-A3D6-423D-8499-9316DD413DAD}
{7BAA69B2-EDC7-4603-B16F-BC7B24353F81} = {A7CB8D8B-70DA-4567-8316-0659FCAE1C73} {7BAA69B2-EDC7-4603-B16F-BC7B24353F81} = {A7CB8D8B-70DA-4567-8316-0659FCAE1C73}
{DEB16B90-CCD4-497E-A2E9-4CC66FD7EF47} = {8ED75CF7-A3D6-423D-8499-9316DD413DAD}
{3CAA7306-088E-4373-A406-99755CC2B605} = {A7CB8D8B-70DA-4567-8316-0659FCAE1C73} {3CAA7306-088E-4373-A406-99755CC2B605} = {A7CB8D8B-70DA-4567-8316-0659FCAE1C73}
{023E1E6A-4895-4573-89AE-3D5D8E0B39C8} = {8ED75CF7-A3D6-423D-8499-9316DD413DAD}
{DF987948-8C23-4337-AF83-D87D6407518D} = {8ED75CF7-A3D6-423D-8499-9316DD413DAD}
{10395C8F-DFBD-4263-8A20-EA3500A6E55A} = {A7CB8D8B-70DA-4567-8316-0659FCAE1C73} {10395C8F-DFBD-4263-8A20-EA3500A6E55A} = {A7CB8D8B-70DA-4567-8316-0659FCAE1C73}
{D1EFC625-D095-4208-98A2-112B73CB40B0} = {A7CB8D8B-70DA-4567-8316-0659FCAE1C73}
{1822BBA8-FE4E-4C5F-B9C0-3E20E6353446} = {9C213609-BF13-4024-816E-A6ADD641DF24}
EndGlobalSection EndGlobalSection
EndGlobal EndGlobal

114
README.md
View File

@ -1,88 +1,80 @@
# DotRecast [![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)
*DotRecast is C# Recast & Detour, a port of [recastnavigation](https://github.com/recastnavigation/recastnavigation) and [recast4j](https://github.com/ppiastucki/recast4j) to the C# language.* # Screenshot
*If you'd like to support the project, we'd appreciate starring(⭐) our repos on Github for more visibility.* ![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
![GitHub License](https://img.shields.io/github/license/ikpil/DotRecast?style=for-the-badge) https://user-images.githubusercontent.com/313821/266782992-32a72a43-8f02-4214-8f1e-86b06952c8b7.mp4
![Languages](https://img.shields.io/github/languages/top/ikpil/DotRecast?style=for-the-badge)
![GitHub repo size](https://img.shields.io/github/repo-size/ikpil/DotRecast?style=for-the-badge)
[![GitHub Repo stars](https://img.shields.io/github/stars/ikpil/DotRecast?style=for-the-badge&logo=github)](https://github.com/ikpil/DotRecast)
[![GitHub Actions Workflow Status](https://img.shields.io/github/actions/workflow/status/ikpil/DotRecast/dotnet.yml?style=for-the-badge&logo=github)](https://github.com/ikpil/DotRecast/actions/workflows/dotnet.yml)
[![GitHub Actions Workflow Status](https://img.shields.io/github/actions/workflow/status/ikpil/DotRecast/codeql.yml?style=for-the-badge&logo=github&label=CODEQL)](https://github.com/ikpil/DotRecast/actions/workflows/codeql.yml)
[![GitHub commit activity](https://img.shields.io/github/commit-activity/m/ikpil/DotRecast?style=for-the-badge&logo=github)](https://github.com/ikpil/DotRecast/commits)
[![GitHub issues](https://img.shields.io/github/issues-raw/ikpil/DotRecast?style=for-the-badge&logo=github&color=44cc11)](https://github.com/ikpil/DotRecast/issues)
[![GitHub closed issues](https://img.shields.io/github/issues-closed-raw/ikpil/DotRecast?style=for-the-badge&logo=github&color=a371f7)](https://github.com/ikpil/DotRecast/issues)
[![NuGet Version](https://img.shields.io/nuget/vpre/DotRecast.Core?style=for-the-badge&logo=nuget)](https://www.nuget.org/packages/DotRecast.Core)
[![NuGet Downloads](https://img.shields.io/nuget/dt/DotRecast.Core?style=for-the-badge&logo=nuget)](https://www.nuget.org/packages/DotRecast.Core)
[![GitHub Sponsors](https://img.shields.io/github/sponsors/ikpil?style=for-the-badge&logo=GitHub-Sponsors&link=https%3A%2F%2Fgithub.com%2Fsponsors%2Fikpil)](https://github.com/sponsors/ikpil)
--- ## 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 ## DotRecast.Detour
- 🏎️ 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
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 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.
- [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
## ⚡ 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) 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.
- 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)
## ⚙ 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. #### Windows
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.
You can use Recast to build a single navmesh, or a tiled navmesh. - need to install [microsoft visual c++ redistributable package](https://learn.microsoft.com/en-us/cpp/windows/latest-supported-vc-redist)
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.
## 📚 Documentation & Links #### Linux & macOS & Windows
- DotRecast Links - Navigate to the `DotRecast.Recast.Demo` folder and run `dotnet run`
- [DotRecast/issues](https://github.com/ikpil/DotRecast/issues)
- Official Links ### Running Unit tests
- [recastnavigation/discussions](https://github.com/recastnavigation/recastnavigation/discussions)
- [recastnav.com](https://recastnav.com)
## 🅾 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

@ -1,8 +0,0 @@
{
"name": "rnd/dotrecastnetsim",
"description": "DotRecast",
"homepage": "https://git.bit5.ru/rnd/DotRecastNetSim.git",
"require": {
"php": ">=7.4"
}
}

View File

@ -1,274 +0,0 @@
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

@ -1,119 +0,0 @@
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

@ -1,30 +0,0 @@
using System;
using System.Collections.Generic;
namespace DotRecast.Core.Buffers
{
// This implementation is thread unsafe
public class RcObjectPool<T> where T : class
{
private readonly Queue<T> _items = new Queue<T>();
private readonly Func<T> _createFunc;
public RcObjectPool(Func<T> createFunc)
{
_createFunc = createFunc;
}
public T Get()
{
if (_items.TryDequeue(out var result))
return result;
return _createFunc();
}
public void Return(T obj)
{
_items.Enqueue(obj);
}
}
}

View File

@ -1,62 +0,0 @@
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 struct 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 Span<T> AsSpan()
{
return new Span<T>(_array, 0, Length);
}
public void Dispose()
{
if (null != _owner && null != _array)
{
_owner.Return(_array, true);
_owner = null;
_array = null;
}
}
}
}

View File

@ -1,4 +1,4 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
namespace DotRecast.Core.Collections namespace DotRecast.Core.Collections
@ -45,11 +45,5 @@ namespace DotRecast.Core.Collections
(list[k], list[n]) = (list[n], list[k]); (list[k], list[n]) = (list[n], list[k]);
} }
} }
public static void AddRange<T>(this IList<T> list, Span<T> span)
{
foreach (var i in span)
list.Add(i);
}
} }
} }

View File

@ -1,153 +0,0 @@
using System;
using System.Collections.Generic;
using System.Runtime.CompilerServices;
namespace DotRecast.Core.Collections
{
public sealed class RcBinaryMinHeap<T>
{
private readonly List<T> _items;
private readonly Comparison<T> _comparision;
public int Count => _items.Count;
public int Capacity => _items.Capacity;
public RcBinaryMinHeap(Comparison<T> comparision)
{
_items = new List<T>();
_comparision = comparision;
}
public RcBinaryMinHeap(int capacity, Comparison<T> comparison) : this(comparison)
{
if (capacity <= 0)
throw new ArgumentException("capacity must greater than zero");
_items = new List<T>(capacity);
_comparision = comparison;
}
public void Push(T val)
{
_items.Add(val);
SiftUp(_items.Count - 1);
}
public T Pop()
{
var min = Peek();
RemoveMin();
return min;
}
private void RemoveMin()
{
if (_items.Count == 0)
{
Throw();
static void Throw() => throw new InvalidOperationException("no element to pop");
}
int last = _items.Count - 1;
Swap(0, last);
_items.RemoveAt(last);
MinHeapify(0, last - 1);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public T Top()
{
return _items[0];
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public T Peek()
{
if (IsEmpty())
{
throw new Exception("Heap is empty.");
}
return _items[0];
}
public bool Modify(T node)
{
for (int i = 0; i < _items.Count; i++)
{
if (_items[i].Equals(node))
{
SiftUp(i);
return true;
}
}
return false;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Clear()
{
_items.Clear();
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool IsEmpty()
{
return 0 == _items.Count;
}
private void SiftUp(int nodeIndex)
{
int parent = (nodeIndex - 1) / 2;
while (_comparision.Invoke(_items[nodeIndex], _items[parent]) < 0)
{
Swap(parent, nodeIndex);
nodeIndex = parent;
parent = (nodeIndex - 1) / 2;
}
}
private void MinHeapify(int nodeIndex, int lastIndex)
{
int left = (nodeIndex * 2) + 1;
int right = left + 1;
int smallest = nodeIndex;
if (left <= lastIndex && _comparision.Invoke(_items[left], _items[nodeIndex]) < 0)
smallest = left;
if (right <= lastIndex && _comparision.Invoke(_items[right], _items[smallest]) < 0)
smallest = right;
if (smallest == nodeIndex)
return;
Swap(nodeIndex, smallest);
MinHeapify(smallest, lastIndex);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private void Swap(int x, int y)
{
if (x == y)
return;
(_items[y], _items[x]) = (_items[x], _items[y]);
}
public T[] ToArray()
{
return _items.ToArray();
}
public List<T> ToList()
{
return new List<T>(_items);
}
}
}

View File

@ -1,4 +1,4 @@
using System; using System;
using System.Collections; using System.Collections;
using System.Collections.Generic; using System.Collections.Generic;

View File

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

View File

@ -1,4 +1,4 @@
namespace DotRecast.Core.Collections namespace DotRecast.Core.Collections
{ {
public readonly partial struct RcImmutableArray<T> public readonly partial struct RcImmutableArray<T>
{ {

View File

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

View File

@ -1,7 +1,7 @@
/* /*
Copyright (c) 2009-2010 Mikko Mononen memon@inside.org Copyright (c) 2009-2010 Mikko Mononen memon@inside.org
recast4j copyright (c) 2015-2019 Piotr Piastucki piotr@jtilia.org recast4j copyright (c) 2015-2019 Piotr Piastucki piotr@jtilia.org
DotRecast Copyright (c) 2023-2024 Choi Ikpil ikpil@naver.com DotRecast Copyright (c) 2023 Choi Ikpil ikpil@naver.com
This software is provided 'as-is', without any express or implied This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages warranty. In no event will the authors be held liable for any damages
@ -29,10 +29,10 @@ namespace DotRecast.Core.Collections
private readonly List<T> _items; private readonly List<T> _items;
private readonly Comparison<T> _comparison; private readonly Comparison<T> _comparison;
public RcSortedQueue(Comparison<T> comp) public RcSortedQueue(Comparison<T> comparison)
{ {
_items = new List<T>(); _items = new List<T>();
_comparison = (x, y) => comp(x, y) * -1; _comparison = (x, y) => comparison.Invoke(x, y) * -1; // reverse
} }
public int Count() public int Count()
@ -40,15 +40,9 @@ namespace DotRecast.Core.Collections
return _items.Count; return _items.Count;
} }
public bool IsEmpty()
{
return 0 == _items.Count;
}
public void Clear() public void Clear()
{ {
_items.Clear(); _items.Clear();
_dirty = false;
} }
private void Balance() private void Balance()
@ -63,39 +57,35 @@ namespace DotRecast.Core.Collections
public T Peek() public T Peek()
{ {
Balance(); Balance();
return _items[^1]; return _items[_items.Count - 1];
} }
public T Dequeue() public T Dequeue()
{ {
var node = Peek(); var node = Peek();
_items.RemoveAt(_items.Count - 1); _items.Remove(node);
return node; return node;
} }
public void Enqueue(T item) public void Enqueue(T item)
{ {
if (null == item)
return;
_items.Add(item); _items.Add(item);
_dirty = true; _dirty = true;
} }
public bool Remove(T item) public void Remove(T item)
{ {
if (null == item) int idx = _items.FindLastIndex(x => item.Equals(x));
return false;
//int idx = _items.BinarySearch(item, _comparer); // don't use this! Because reference types can be reused externally.
int idx = _items.LastIndexOf(item);
if (0 > idx) if (0 > idx)
return false; return;
_items.RemoveAt(idx); _items.RemoveAt(idx);
return true;
} }
public bool IsEmpty()
{
return 0 == _items.Count;
}
public List<T> ToList() public List<T> ToList()
{ {

View File

@ -1,421 +0,0 @@
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

@ -1,85 +0,0 @@
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

@ -1,43 +0,0 @@
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

@ -1,805 +0,0 @@
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

@ -1,133 +0,0 @@
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

@ -1,49 +0,0 @@
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

@ -1,229 +0,0 @@
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

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

View File

@ -1,14 +0,0 @@
{
"name": "DotRecast.Core",
"rootNamespace": "DotRecast.Core",
"references": [],
"includePlatforms": [],
"excludePlatforms": [],
"allowUnsafeCode": false,
"overrideReferences": false,
"precompiledReferences": [],
"autoReferenced": true,
"defineConstraints": [],
"versionDefines": [],
"noEngineReferences": true
}

View File

@ -1,16 +1,15 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<TargetFrameworks>netstandard2.1;net6.0;net7.0;net8.0</TargetFrameworks> <TargetFrameworks>netstandard2.1;net6.0;net7.0</TargetFrameworks>
<PackageId>DotRecast.Core</PackageId> <PackageId>DotRecast.Core</PackageId>
<PackageReadmeFile>README.md</PackageReadmeFile> <PackageReadmeFile>README.md</PackageReadmeFile>
<Authors>ikpil</Authors> <Authors>ikpil</Authors>
<Description>DotRecast - a port of Recast Detour, Industry-standard navigation mesh toolset for .NET, C#, Unity3D, games, servers</Description> <Description>DotRecast - a port of Recast Detour, navigation mesh toolset for games, Unity3D, servers, C#</Description>
<RepositoryType>git</RepositoryType> <RepositoryType>git</RepositoryType>
<PackageProjectUrl>https://github.com/ikpil/DotRecast</PackageProjectUrl> <PackageProjectUrl>https://github.com/ikpil/DotRecast</PackageProjectUrl>
<RepositoryUrl>https://github.com/ikpil/DotRecast</RepositoryUrl> <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> <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> </PropertyGroup>
<ItemGroup> <ItemGroup>

View File

@ -1,7 +1,7 @@
/* /*
Copyright (c) 2009-2010 Mikko Mononen memon@inside.org Copyright (c) 2009-2010 Mikko Mononen memon@inside.org
recast4j copyright (c) 2015-2019 Piotr Piastucki piotr@jtilia.org recast4j copyright (c) 2015-2019 Piotr Piastucki piotr@jtilia.org
DotRecast Copyright (c) 2023-2024 Choi Ikpil ikpil@naver.com DotRecast Copyright (c) 2023 Choi Ikpil ikpil@naver.com
This software is provided 'as-is', without any express or implied This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages warranty. In no event will the authors be held liable for any damages

View File

@ -1,9 +1,7 @@
namespace DotRecast.Core namespace DotRecast.Core
{ {
public interface IRcRand public interface IRcRand
{ {
float Next(); float Next();
double NextDouble();
int NextInt32();
} }
} }

View File

@ -1,4 +1,4 @@
using System; using System;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
namespace DotRecast.Core.Numerics namespace DotRecast.Core.Numerics

View File

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

View File

@ -1,6 +1,5 @@
/* /*
recast4j Copyright (c) 2015-2019 Piotr Piastucki piotr@jtilia.org recast4j copyright (c) 2015-2019 Piotr Piastucki piotr@jtilia.org
DotRecast Copyright (c) 2023-2024 Choi Ikpil ikpil@naver.com
This software is provided 'as-is', without any express or implied This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages warranty. In no event will the authors be held liable for any damages
@ -28,11 +27,11 @@ namespace DotRecast.Core.Numerics
public float Y; public float Y;
public float Z; public float Z;
public static readonly RcVec3f Zero = new RcVec3f(0.0f, 0.0f, 0.0f); public static RcVec3f Zero { get; } = new RcVec3f(0.0f, 0.0f, 0.0f);
public static readonly RcVec3f One = new RcVec3f(1.0f); public static RcVec3f One { get; } = new RcVec3f(1.0f);
public static readonly RcVec3f UnitX = new RcVec3f(1.0f, 0.0f, 0.0f); public static RcVec3f UnitX { get; } = new RcVec3f(1.0f, 0.0f, 0.0f);
public static readonly RcVec3f UnitY = new RcVec3f(0.0f, 1.0f, 0.0f); public static RcVec3f UnitY { get; } = new RcVec3f(0.0f, 1.0f, 0.0f);
public static readonly RcVec3f UnitZ = new RcVec3f(0.0f, 0.0f, 1.0f); public static RcVec3f UnitZ { get; } = new RcVec3f(0.0f, 0.0f, 1.0f);
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public RcVec3f(float x, float y, float z) public RcVec3f(float x, float y, float z)
@ -50,19 +49,6 @@ namespace DotRecast.Core.Numerics
Z = f; Z = f;
} }
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public RcVec3f(ReadOnlySpan<float> values)
{
if (values.Length < 3)
{
RcThrowHelper.ThrowArgumentOutOfRangeException(nameof(values));
}
X = values[0];
Y = values[1];
Z = values[2];
}
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public readonly float Length() public readonly float Length()

View File

@ -1,9 +0,0 @@
namespace DotRecast.Core.Numerics
{
public struct RcVec3i
{
public int X;
public int Y;
public int Z;
}
}

View File

@ -1,15 +1,20 @@
using System; using System;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
namespace DotRecast.Core.Numerics namespace DotRecast.Core.Numerics
{ {
public static class RcVec public static class RcVecUtils
{ {
public const float EPSILON = 1e-6f; public const float EPSILON = 1e-6f;
public static readonly float EQUAL_THRESHOLD = RcMath.Sqr(1.0f / 16384.0f);
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public static RcVec3f Create(Span<float> values, int n) public static RcVec3f Create(float[] values)
{
return Create(values, 0);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static RcVec3f Create(float[] values, int n)
{ {
return new RcVec3f(values[n + 0], values[n + 1], values[n + 2]); return new RcVec3f(values[n + 0], values[n + 1], values[n + 2]);
} }
@ -37,73 +42,10 @@ namespace DotRecast.Core.Numerics
} }
} }
/// Performs a 'sloppy' colocation check of the specified points.
/// @param[in] p0 A point. [(x, y, z)]
/// @param[in] p1 A point. [(x, y, z)]
/// @return True if the points are considered to be at the same location.
///
/// Basically, this function will return true if the specified points are
/// close enough to eachother to be considered colocated.
public static bool Equal(RcVec3f p0, RcVec3f p1)
{
float d = RcVec3f.DistanceSquared(p0, p1);
return d < EQUAL_THRESHOLD;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public static float Dot2(RcVec3f a, RcVec3f b) public static RcVec3f Scale(this RcVec3f v, float scale)
{ {
return a.X * b.X + a.Z * b.Z; return v * scale;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static float DistSq2(float[] verts, int p, int q)
{
float dx = verts[q + 0] - verts[p + 0];
float dy = verts[q + 2] - verts[p + 2];
return dx * dx + dy * dy;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static float Dist2(float[] verts, int p, int q)
{
return MathF.Sqrt(DistSq2(verts, p, q));
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static float DistSq2(RcVec3f p, RcVec3f q)
{
float dx = q.X - p.X;
float dy = q.Z - p.Z;
return dx * dx + dy * dy;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static float Dist2(RcVec3f p, RcVec3f q)
{
return MathF.Sqrt(DistSq2(p, q));
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static float Cross2(float[] verts, int p1, int p2, int p3)
{
float u1 = verts[p2 + 0] - verts[p1 + 0];
float v1 = verts[p2 + 2] - verts[p1 + 2];
float u2 = verts[p3 + 0] - verts[p1 + 0];
float v2 = verts[p3 + 2] - verts[p1 + 2];
return u1 * v2 - v1 * u2;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static float Cross2(RcVec3f p1, RcVec3f p2, RcVec3f p3)
{
float u1 = p2.X - p1.X;
float v1 = p2.Z - p1.Z;
float u2 = p3.X - p1.X;
float v2 = p3.Z - p1.Z;
return u1 * v2 - v1 * u2;
} }
/// Derives the dot product of two vectors on the xz-plane. (@p u . @p v) /// Derives the dot product of two vectors on the xz-plane. (@p u . @p v)
@ -120,6 +62,46 @@ namespace DotRecast.Core.Numerics
@this.Z * v.Z; @this.Z * v.Z;
} }
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static float Dot2D(this RcVec3f @this, float[] v, int vi)
{
return @this.X * v[vi] +
@this.Z * v[vi + 2];
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static RcVec3f Add(RcVec3f a, float[] verts, int i)
{
return new RcVec3f(
a.X + verts[i],
a.Y + verts[i + 1],
a.Z + verts[i + 2]
);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static RcVec3f Subtract(float[] verts, int i, int j)
{
return new RcVec3f(
verts[i] - verts[j],
verts[i + 1] - verts[j + 1],
verts[i + 2] - verts[j + 2]
);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static RcVec3f Subtract(RcVec3f i, float[] verts, int j)
{
return new RcVec3f(
i.X - verts[j],
i.Y - verts[j + 1],
i.Z - verts[j + 2]
);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void Cross(float[] dest, float[] v1, float[] v2) public static void Cross(float[] dest, float[] v1, float[] v2)
{ {
@ -137,11 +119,19 @@ namespace DotRecast.Core.Numerics
} }
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void Copy(Span<float> @out, int n, Span<float> @in, int m) public static float Dot(float[] v1, float[] v2)
{ {
@out[n + 0] = @in[m + 0]; return v1[0] * v2[0] +
@out[n + 1] = @in[m + 1]; v1[1] * v2[1] +
@out[n + 2] = @in[m + 2]; v1[2] * v2[2];
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static float Dot(float[] v1, RcVec3f vector2)
{
return v1[0] * vector2.X +
v1[1] * vector2.Y +
v1[2] * vector2.Z;
} }
/// Returns the distance between two points. /// Returns the distance between two points.
@ -177,6 +167,26 @@ namespace DotRecast.Core.Numerics
return v; return v;
} }
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static RcVec3f Min(RcVec3f v, float[] @in, int i)
{
return new RcVec3f(
(v.X < @in[i + 0]) ? v.X : @in[i + 0],
(v.Y < @in[i + 1]) ? v.Y : @in[i + 1],
(v.Z < @in[i + 2]) ? v.Z : @in[i + 2]
);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static RcVec3f Max(RcVec3f v, float[] @in, int i)
{
return new RcVec3f(
(v.X > @in[i + 0]) ? v.X : @in[i + 0],
(v.Y > @in[i + 1]) ? v.Y : @in[i + 1],
(v.Z > @in[i + 2]) ? v.Z : @in[i + 2]
);
}
/// Derives the distance between the specified points on the xz-plane. /// Derives the distance between the specified points on the xz-plane.
/// @param[in] v1 A point. [(x, y, z)] /// @param[in] v1 A point. [(x, y, z)]
/// @param[in] v2 A point. [(x, y, z)] /// @param[in] v2 A point. [(x, y, z)]
@ -192,10 +202,6 @@ namespace DotRecast.Core.Numerics
return (float)MathF.Sqrt(dx * dx + dz * dz); return (float)MathF.Sqrt(dx * dx + dz * dz);
} }
/// Derives the square of the distance between the specified points on the xz-plane.
/// @param[in] v1 A point. [(x, y, z)]
/// @param[in] v2 A point. [(x, y, z)]
/// @return The square of the distance between the point on the xz-plane.
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public static float Dist2DSqr(RcVec3f v1, RcVec3f v2) public static float Dist2DSqr(RcVec3f v1, RcVec3f v2)
{ {
@ -205,7 +211,7 @@ namespace DotRecast.Core.Numerics
} }
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public static float Dist2DSqr(RcVec3f p, Span<float> verts, int i) public static float Dist2DSqr(RcVec3f p, float[] verts, int i)
{ {
float dx = verts[i] - p.X; float dx = verts[i] - p.X;
float dz = verts[i + 2] - p.Z; float dz = verts[i + 2] - p.Z;
@ -256,7 +262,7 @@ namespace DotRecast.Core.Numerics
/// @param[in] v2 The destination vector. /// @param[in] v2 The destination vector.
/// @param[in] t The interpolation factor. [Limits: 0 <= value <= 1.0] /// @param[in] t The interpolation factor. [Limits: 0 <= value <= 1.0]
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public static RcVec3f Lerp(Span<float> verts, int v1, int v2, float t) public static RcVec3f Lerp(float[] verts, int v1, int v2, float t)
{ {
return new RcVec3f( return new RcVec3f(
verts[v1 + 0] + (verts[v2 + 0] - verts[v1 + 0]) * t, verts[v1 + 0] + (verts[v2 + 0] - verts[v1 + 0]) * t,

View File

@ -0,0 +1,20 @@
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,24 +1,9 @@
using System; using System;
using System.Runtime.CompilerServices;
namespace DotRecast.Core namespace DotRecast.Core
{ {
public static class RcArrays public static class RcArrayUtils
{ {
// 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) public static T[] CopyOf<T>(T[] source, int startIdx, int length)
{ {
var deatArr = new T[length]; var deatArr = new T[length];

View File

@ -1,4 +1,4 @@
using System.Threading; using System.Threading;
namespace DotRecast.Core namespace DotRecast.Core
{ {

View File

@ -1,4 +1,4 @@
using System.Threading; using System.Threading;
namespace DotRecast.Core namespace DotRecast.Core
{ {

View File

@ -1,4 +1,4 @@
using System.Threading; using System.Threading;
namespace DotRecast.Core namespace DotRecast.Core
{ {

View File

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

View File

@ -1,4 +1,4 @@
namespace DotRecast.Core namespace DotRecast.Core
{ {
public enum RcByteOrder public enum RcByteOrder
{ {

View File

@ -1,6 +1,6 @@
/* /*
recast4j copyright (c) 2021 Piotr Piastucki piotr@jtilia.org recast4j copyright (c) 2021 Piotr Piastucki piotr@jtilia.org
DotRecast Copyright (c) 2023-2024 Choi Ikpil ikpil@naver.com DotRecast Copyright (c) 2023 Choi Ikpil ikpil@naver.com
This software is provided 'as-is', without any express or implied This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages warranty. In no event will the authors be held liable for any damages

View File

@ -1,6 +1,6 @@
/* /*
recast4j copyright (c) 2021 Piotr Piastucki piotr@jtilia.org recast4j copyright (c) 2021 Piotr Piastucki piotr@jtilia.org
DotRecast Copyright (c) 2023-2024 Choi Ikpil ikpil@naver.com DotRecast Copyright (c) 2023 Choi Ikpil ikpil@naver.com
This software is provided 'as-is', without any express or implied This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages warranty. In no event will the authors be held liable for any damages

View File

@ -1,4 +1,4 @@
using System.IO; using System.IO;
using System.Linq; using System.Linq;
namespace DotRecast.Core namespace DotRecast.Core

View File

@ -1,4 +1,4 @@
namespace DotRecast.Core namespace DotRecast.Core
{ {
public class RcEdge public class RcEdge
{ {

View File

@ -1,4 +1,4 @@
using System; using System;
using System.Diagnostics; using System.Diagnostics;
namespace DotRecast.Core namespace DotRecast.Core

View File

@ -1,26 +1,10 @@
using System.Runtime.CompilerServices; namespace DotRecast.Core
namespace DotRecast.Core
{ {
public static class RcHashCodes public static class RcHashCodes
{ {
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static int CombineHashCodes(int h1, int h2) public static int CombineHashCodes(int h1, int h2)
{ {
return (((h1 << 5) + h1) ^ 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

@ -1,165 +0,0 @@
/*
Recast4J Copyright (c) 2015 Piotr Piastucki piotr@jtilia.org
DotRecast Copyright (c) 2023-2024 Choi Ikpil ikpil@naver.com
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
*/
using System;
using System.IO;
namespace DotRecast.Core
{
public static class RcIO
{
public static RcByteBuffer ToByteBuffer(BinaryReader br, bool direct)
{
byte[] data = ToByteArray(br);
if (direct)
{
Array.Reverse(data);
}
return new RcByteBuffer(data);
}
public static byte[] ToByteArray(BinaryReader br)
{
using var ms = new MemoryStream();
Span<byte> buffer = stackalloc byte[4096];
int l;
while ((l = br.Read(buffer)) > 0)
{
ms.Write(buffer.Slice(0, l));
}
return ms.ToArray();
}
public static RcByteBuffer ToByteBuffer(BinaryReader br)
{
var bytes = ToByteArray(br);
return new RcByteBuffer(bytes);
}
public static int SwapEndianness(int i)
{
var s = (((uint)i >> 24) & 0xFF) | (((uint)i >> 8) & 0xFF00) | (((uint)i << 8) & 0xFF0000) | ((i << 24) & 0xFF000000);
return (int)s;
}
public static byte[] ReadFileIfFound(string filename)
{
if (string.IsNullOrEmpty(filename))
return null;
string filePath = filename;
if (!File.Exists(filePath))
{
var searchFilePath = RcDirectory.SearchFile($"{filename}");
if (!File.Exists(searchFilePath))
{
searchFilePath = RcDirectory.SearchFile($"resources/{filename}");
}
if (File.Exists(searchFilePath))
{
filePath = searchFilePath;
}
}
using var fs = new FileStream(filePath, FileMode.Open, FileAccess.Read, FileShare.Read);
byte[] buffer = new byte[fs.Length];
var read = fs.Read(buffer, 0, buffer.Length);
if (read != buffer.Length)
return null;
return buffer;
}
public static void Write(BinaryWriter ws, float value, RcByteOrder order)
{
byte[] bytes = BitConverter.GetBytes(value);
int i = BitConverter.ToInt32(bytes, 0);
Write(ws, i, order);
}
public static void Write(BinaryWriter ws, short value, RcByteOrder order)
{
if (order == RcByteOrder.BIG_ENDIAN)
{
ws.Write((byte)((value >> 8) & 0xFF));
ws.Write((byte)(value & 0xFF));
}
else
{
ws.Write((byte)(value & 0xFF));
ws.Write((byte)((value >> 8) & 0xFF));
}
}
public static void Write(BinaryWriter ws, long value, RcByteOrder order)
{
if (order == RcByteOrder.BIG_ENDIAN)
{
Write(ws, (int)((ulong)value >> 32), order);
Write(ws, (int)(value & 0xFFFFFFFF), order);
}
else
{
Write(ws, (int)(value & 0xFFFFFFFF), order);
Write(ws, (int)((ulong)value >> 32), order);
}
}
public static void Write(BinaryWriter ws, int value, RcByteOrder order)
{
if (order == RcByteOrder.BIG_ENDIAN)
{
ws.Write((byte)((value >> 24) & 0xFF));
ws.Write((byte)((value >> 16) & 0xFF));
ws.Write((byte)((value >> 8) & 0xFF));
ws.Write((byte)(value & 0xFF));
}
else
{
ws.Write((byte)(value & 0xFF));
ws.Write((byte)((value >> 8) & 0xFF));
ws.Write((byte)((value >> 16) & 0xFF));
ws.Write((byte)((value >> 24) & 0xFF));
}
}
public static void Write(BinaryWriter ws, bool value)
{
Write(ws, (byte)(value ? 1 : 0));
}
public static void Write(BinaryWriter ws, byte value)
{
ws.Write(value);
}
public static void Write(BinaryWriter ws, MemoryStream ms)
{
ms.Position = 0;
byte[] buffer = new byte[ms.Length];
ms.Read(buffer, 0, buffer.Length);
ws.Write(buffer);
}
}
}

View File

@ -1,7 +1,7 @@
/* /*
Copyright (c) 2009-2010 Mikko Mononen memon@inside.org Copyright (c) 2009-2010 Mikko Mononen memon@inside.org
recast4j copyright (c) 2021 Piotr Piastucki piotr@jtilia.org recast4j copyright (c) 2021 Piotr Piastucki piotr@jtilia.org
DotRecast Copyright (c) 2023-2024 Choi Ikpil ikpil@naver.com DotRecast Copyright (c) 2023 Choi Ikpil ikpil@naver.com
This software is provided 'as-is', without any express or implied This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages warranty. In no event will the authors be held liable for any damages

View File

@ -1,7 +1,7 @@
/* /*
Copyright (c) 2009-2010 Mikko Mononen memon@inside.org Copyright (c) 2009-2010 Mikko Mononen memon@inside.org
recast4j copyright (c) 2015-2019 Piotr Piastucki piotr@jtilia.org recast4j copyright (c) 2015-2019 Piotr Piastucki piotr@jtilia.org
DotRecast Copyright (c) 2023-2024 Choi Ikpil ikpil@naver.com DotRecast Copyright (c) 2023 Choi Ikpil ikpil@naver.com
This software is provided 'as-is', without any express or implied This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages warranty. In no event will the authors be held liable for any damages
@ -29,11 +29,5 @@ namespace DotRecast.Core
{ {
return f * f; return f * f;
} }
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static float Lerp(float value1, float value2, float amount)
{
return (value1 * (1.0f - amount)) + (value2 * amount);
}
} }
} }

View File

@ -1,6 +1,6 @@
/* /*
recast4j Copyright (c) 2015-2019 Piotr Piastucki piotr@jtilia.org recast4j Copyright (c) 2015-2019 Piotr Piastucki piotr@jtilia.org
DotRecast Copyright (c) 2023-2024 Choi Ikpil ikpil@naver.com DotRecast Copyright (c) 2023 Choi Ikpil ikpil@naver.com
This software is provided 'as-is', without any express or implied This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages warranty. In no event will the authors be held liable for any damages

View File

@ -1,4 +1,4 @@
using System.Collections.Generic; using System.Collections.Generic;
namespace DotRecast.Core namespace DotRecast.Core
{ {

View File

@ -1,34 +0,0 @@
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

@ -1,4 +1,4 @@
using System; using System;
namespace DotRecast.Core namespace DotRecast.Core
{ {
@ -6,13 +6,9 @@ namespace DotRecast.Core
{ {
private readonly Random _r; private readonly Random _r;
public RcRand() : this(new Random()) public RcRand()
{ {
} _r = new Random();
public RcRand(Random r)
{
_r = r;
} }
public RcRand(long seed) public RcRand(long seed)
@ -24,15 +20,5 @@ namespace DotRecast.Core
{ {
return (float)_r.NextDouble(); return (float)_r.NextDouble();
} }
public double NextDouble()
{
return _r.NextDouble();
}
public int NextInt32()
{
return _r.Next();
}
} }
} }

View File

@ -0,0 +1,22 @@
using System.IO;
namespace DotRecast.Core
{
public static class RcResources
{
public static byte[] Load(string filename)
{
var filepath = filename;
if (!File.Exists(filepath))
{
filepath = RcDirectory.SearchFile($"resources/{filename}");
}
using var fs = new FileStream(filepath, FileMode.Open, FileAccess.Read, FileShare.Read);
byte[] buffer = new byte[fs.Length];
fs.Read(buffer, 0, buffer.Length);
return buffer;
}
}
}

View File

@ -1,23 +0,0 @@
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

@ -1,4 +1,4 @@
using DotRecast.Core.Numerics; using DotRecast.Core.Numerics;
namespace DotRecast.Core namespace DotRecast.Core
{ {

View File

@ -1,36 +0,0 @@
using System;
using System.Runtime.CompilerServices;
namespace DotRecast.Core
{
public static class RcSpans
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void Copy<T>(Span<T> src, Span<T> dst)
{
src.CopyTo(dst);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void Copy<T>(Span<T> src, int srcIdx, Span<T> dst, int dstIdx, int length)
{
var slicedSrc = src.Slice(srcIdx, length);
var slicedDst = dst.Slice(dstIdx);
slicedSrc.CopyTo(slicedDst);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void Move<T>(Span<T> src, int srcIdx, int dstIdx, int length)
{
var slicedSrc = src.Slice(srcIdx, length);
var slicedDst = src.Slice(dstIdx, length);
slicedSrc.CopyTo(slicedDst);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void Fill<T>(Span<T> span, T value, int start, int count)
{
span.Slice(start, count).Fill(value);
}
}
}

View File

@ -1,6 +1,6 @@
/* /*
recast4j copyright (c) 2021 Piotr Piastucki piotr@jtilia.org recast4j copyright (c) 2021 Piotr Piastucki piotr@jtilia.org
DotRecast Copyright (c) 2023-2024 Choi Ikpil ikpil@naver.com DotRecast Copyright (c) 2023 Choi Ikpil ikpil@naver.com
This software is provided 'as-is', without any express or implied This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages warranty. In no event will the authors be held liable for any damages
@ -25,33 +25,21 @@ using System.Threading;
namespace DotRecast.Core namespace DotRecast.Core
{ {
/// Provides an interface for optional logging and performance tracking of the Recast public class RcTelemetry
/// 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 ThreadLocal<Dictionary<string, RcAtomicLong>> _timerStart;
private readonly ConcurrentDictionary<string, RcAtomicLong> _timerAccum; private readonly ConcurrentDictionary<string, RcAtomicLong> _timerAccum;
public RcContext() public RcTelemetry()
{ {
_timerStart = new ThreadLocal<Dictionary<string, RcAtomicLong>>(() => new Dictionary<string, RcAtomicLong>()); _timerStart = new ThreadLocal<Dictionary<string, RcAtomicLong>>(() => new Dictionary<string, RcAtomicLong>());
_timerAccum = new ConcurrentDictionary<string, RcAtomicLong>(); _timerAccum = new ConcurrentDictionary<string, RcAtomicLong>();
} }
public RcScopedTimer ScopedTimer(RcTimerLabel label) public IDisposable ScopedTimer(RcTimerLabel label)
{ {
return new RcScopedTimer(this, label); StartTimer(label);
return new RcAnonymousDisposable(() => StopTimer(label));
} }
public void StartTimer(RcTimerLabel label) public void StartTimer(RcTimerLabel label)

View File

@ -1,4 +1,4 @@
using System; using System;
namespace DotRecast.Core namespace DotRecast.Core
{ {

View File

@ -1,48 +0,0 @@
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 ThrowArgumentOutOfRangeException(string argument)
{
throw new ArgumentOutOfRangeException(argument);
}
[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,4 +1,4 @@
namespace DotRecast.Core namespace DotRecast.Core
{ {
/// Recast performance timer categories. /// Recast performance timer categories.
/// @see rcContext /// @see rcContext

View File

@ -1,17 +0,0 @@
{
"name": "DotRecast.Detour.Crowd",
"rootNamespace": "DotRecast.Detour.Crowd",
"references": [
"DotRecast.Core",
"DotRecast.Detour"
],
"includePlatforms": [],
"excludePlatforms": [],
"allowUnsafeCode": false,
"overrideReferences": false,
"precompiledReferences": [],
"autoReferenced": true,
"defineConstraints": [],
"versionDefines": [],
"noEngineReferences": true
}

View File

@ -1,16 +1,15 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<TargetFrameworks>netstandard2.1;net6.0;net7.0;net8.0</TargetFrameworks> <TargetFrameworks>netstandard2.1;net6.0;net7.0</TargetFrameworks>
<PackageId>DotRecast.Detour.Crowd</PackageId> <PackageId>DotRecast.Detour.Crowd</PackageId>
<PackageReadmeFile>README.md</PackageReadmeFile> <PackageReadmeFile>README.md</PackageReadmeFile>
<Authors>ikpil</Authors> <Authors>ikpil</Authors>
<Description>DotRecast - a port of Recast Detour, Industry-standard navigation mesh toolset for .NET, C#, Unity3D, games, servers</Description> <Description>DotRecast - a port of Recast Detour, navigation mesh toolset for games, Unity3D, servers, C#</Description>
<RepositoryType>git</RepositoryType> <RepositoryType>git</RepositoryType>
<PackageProjectUrl>https://github.com/ikpil/DotRecast</PackageProjectUrl> <PackageProjectUrl>https://github.com/ikpil/DotRecast</PackageProjectUrl>
<RepositoryUrl>https://github.com/ikpil/DotRecast</RepositoryUrl> <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> <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> </PropertyGroup>
<ItemGroup> <ItemGroup>

View File

@ -1,7 +1,7 @@
/* /*
Copyright (c) 2009-2010 Mikko Mononen memon@inside.org Copyright (c) 2009-2010 Mikko Mononen memon@inside.org
recast4j copyright (c) 2015-2019 Piotr Piastucki piotr@jtilia.org recast4j copyright (c) 2015-2019 Piotr Piastucki piotr@jtilia.org
DotRecast Copyright (c) 2023-2024 Choi Ikpil ikpil@naver.com DotRecast Copyright (c) 2023 Choi Ikpil ikpil@naver.com
This software is provided 'as-is', without any express or implied This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages warranty. In no event will the authors be held liable for any damages
@ -20,162 +20,165 @@ freely, subject to the following restrictions:
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics;
using DotRecast.Core; using DotRecast.Core;
using DotRecast.Core.Buffers;
using DotRecast.Core.Collections; using DotRecast.Core.Collections;
using DotRecast.Core.Numerics; using DotRecast.Core.Numerics;
namespace DotRecast.Detour.Crowd 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.
@defgroup crowd Crowd *
* The crowd is the big beast of the navigation features. It not only handles a lot of the path management for you, but
Members in this module implement local steering and dynamic avoidance features. * also local steering and dynamic avoidance between members of the crowd. I.e. It can keep your agents from running
* into each other.
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 * Main class: Crowd
avoidance between members of the crowd. I.e. It can keep your agents from *
running into each other. * 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
Main class: #dtCrowd * 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.
The #dtNavMeshQuery and #dtPathCorridor classes provide perfectly good, easy *
to use path planning features. But in the end they only give you points that * Basically, you add an agent to the crowd, providing various configuration settings such as maximum speed and
your navigation client should be moving toward. When it comes to deciding things * acceleration. You also provide a local target to move toward. The crowd manager then provides, with every update, the
like agent velocity and steering to avoid other agents, that is up to you to * new agent position and velocity for the frame. The movement will be constrained to the navigation mesh, and steering
implement. Unless, of course, you decide to use #dtCrowd. * will be applied to ensure agents managed by the crowd do not collide with each other.
*
Basically, you add an agent to the crowd, providing various configuration * This is very powerful feature set. But it comes with limitations.
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 * The biggest limitation is that you must give control of the agent's position completely over to the crowd manager.
new agent position and velocity for the frame. The movement will be * You can update things like maximum speed and acceleration. But in order for the crowd manager to do its thing, it
constrained to the navigation mesh, and steering will be applied to ensure * can't allow you to constantly be giving it overrides to position and velocity. So you give up direct control of the
agents managed by the crowd do not collide with each other. * agent's movement. It belongs to the crowd.
*
This is very powerful feature set. But it comes with limitations. * 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
The biggest limitation is that you must give control of the agent's position * failing to reach its target. So you may still need to do long distance planning and provide the crowd manager with
completely over to the crowd manager. You can update things like maximum speed * intermediate targets.
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 * Other significant limitations:
you give up direct control of the agent's movement. It belongs to the crowd. *
* - All agents using the crowd manager will use the same #dtQueryFilter. - Crowd management is relatively expensive.
The second biggest limitation revolves around the fact that the crowd manager * The maximum agents under crowd management at any one time is between 20 and 30. A good place to start is a maximum of
deals with local planning. So the agent's target should never be more than * 25 agents for 0.5ms per frame.
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 * @note This is a summary list of members. Use the index or search feature to find minor members.
distance planning and provide the crowd manager with intermediate targets. *
* @struct dtCrowdAgentParams
Other significant limitations: * @see CrowdAgent, Crowd::AddAgent(), Crowd::UpdateAgentParameters()
*
- All agents using the crowd manager will use the same #dtQueryFilter. * @var dtCrowdAgentParams::obstacleAvoidanceType
- Crowd management is relatively expensive. The maximum agents under crowd * @par
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. * #dtCrowd permits agents to use different avoidance configurations. This value is the index of the
* #dtObstacleAvoidanceParams within the crowd.
@note This is a summary list of members. Use the index or search *
feature to find minor members. * @see dtObstacleAvoidanceParams, dtCrowd::SetObstacleAvoidanceParams(), dtCrowd::GetObstacleAvoidanceParams()
*
@struct dtCrowdAgentParams * @var dtCrowdAgentParams::collisionQueryRange
@see dtCrowdAgent, dtCrowd::addAgent(), dtCrowd::updateAgentParameters() * @par
*
@var dtCrowdAgentParams::obstacleAvoidanceType * Collision elements include other agents and navigation mesh boundaries.
@par *
* This value is often based on the agent radius and/or maximum speed. E.g. radius * 8
#dtCrowd permits agents to use different avoidance configurations. This value *
is the index of the #dtObstacleAvoidanceParams within the crowd. * @var dtCrowdAgentParams::pathOptimizationRange
* @par
@see dtObstacleAvoidanceParams, dtCrowd::setObstacleAvoidanceParams(), *
dtCrowd::getObstacleAvoidanceParams() * Only applicable if #updateFlags includes the #DT_CROWD_OPTIMIZE_VIS flag.
*
@var dtCrowdAgentParams::collisionQueryRange * This value is often based on the agent radius. E.g. radius * 30
@par *
* @see dtPathCorridor::OptimizePathVisibility()
Collision elements include other agents and navigation mesh boundaries. *
* @var dtCrowdAgentParams::separationWeight
This value is often based on the agent radius and/or maximum speed. E.g. radius * 8 * @par
*
@var dtCrowdAgentParams::pathOptimizationRange * A higher value will result in agents trying to stay farther away from each other at the cost of more difficult
@par * steering in tight spaces.
*
Only applicable if #updateFlags includes the #DT_CROWD_OPTIMIZE_VIS flag. */
/**
This value is often based on the agent radius. E.g. radius * 30 * 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
@see dtPathCorridor::optimizePathVisibility() * 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
@var dtCrowdAgentParams::separationWeight * the crowd to manage its agents. -# Retrieve agent information using #GetActiveAgents(). -# Make movement requests
@par * 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
A higher value will result in agents trying to stay farther away from each other at * active agent's position. If agent position must be fed back into the crowd, the agent must be removed and re-added.
the cost of more difficult steering in tight spaces. * 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
*/ */
/// Provides local steering behaviors for a group of agents.
/// @ingroup crowd
public class DtCrowd public class DtCrowd
{ {
private readonly RcAtomicInteger _agentIdx; /// The maximum number of corners a crowd agent will look ahead in the path.
private readonly Dictionary<int, DtCrowdAgent> _agents; /// This value is used for sizing the crowd agent corner buffers.
private readonly List<DtCrowdAgent> _activeAgents; /// 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 DtPathQueue _pathQ;
private readonly DtObstacleAvoidanceParams[] _obstacleQueryParams = new DtObstacleAvoidanceParams[DT_CROWD_MAX_OBSTAVOIDANCE_PARAMS];
private readonly DtObstacleAvoidanceParams[] _obstacleQueryParams;
private readonly DtObstacleAvoidanceQuery _obstacleQuery; private readonly DtObstacleAvoidanceQuery _obstacleQuery;
private DtProximityGrid _grid;
private readonly DtProximityGrid _grid; private readonly RcVec3f _ext = new RcVec3f();
private readonly IDtQueryFilter[] _filters = new IDtQueryFilter[DT_CROWD_MAX_QUERY_FILTER_TYPE];
private int _maxPathResult; private DtNavMeshQuery _navQuery;
private readonly RcVec3f _agentPlacementHalfExtents; private DtNavMesh _navMesh;
private readonly IDtQueryFilter[] _filters;
private readonly DtCrowdConfig _config; private readonly DtCrowdConfig _config;
private readonly DtCrowdTelemetry _telemetry = new DtCrowdTelemetry();
private int _velocitySampleCount; private int _velocitySampleCount;
private DtNavMeshQuery _navQuery; public DtCrowd(DtCrowdConfig config, DtNavMesh nav) :
this(config, nav, i => new DtQueryDefaultFilter())
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) public DtCrowd(DtCrowdConfig config, DtNavMesh nav, Func<int, IDtQueryFilter> queryFilterFactory)
{ {
_config = config; _config = config;
_agentPlacementHalfExtents = new RcVec3f(config.maxAgentRadius * 2.0f, config.maxAgentRadius * 1.5f, config.maxAgentRadius * 2.0f); _ext = new RcVec3f(config.maxAgentRadius * 2.0f, config.maxAgentRadius * 1.5f, config.maxAgentRadius * 2.0f);
_obstacleQuery = new DtObstacleAvoidanceQuery(config.maxObstacleAvoidanceCircles, config.maxObstacleAvoidanceSegments); _obstacleQuery = new DtObstacleAvoidanceQuery(config.maxObstacleAvoidanceCircles, config.maxObstacleAvoidanceSegments);
_filters = new IDtQueryFilter[DtCrowdConst.DT_CROWD_MAX_QUERY_FILTER_TYPE]; for (int i = 0; i < DT_CROWD_MAX_QUERY_FILTER_TYPE; i++)
for (int i = 0; i < DtCrowdConst.DT_CROWD_MAX_QUERY_FILTER_TYPE; i++)
{ {
_filters[i] = queryFilterFactory.Invoke(i); _filters[i] = queryFilterFactory.Invoke(i);
} }
// Init obstacle query option. // Init obstacle query option.
_obstacleQueryParams = new DtObstacleAvoidanceParams[DtCrowdConst.DT_CROWD_MAX_OBSTAVOIDANCE_PARAMS]; for (int i = 0; i < DT_CROWD_MAX_OBSTAVOIDANCE_PARAMS; ++i)
for (int i = 0; i < DtCrowdConst.DT_CROWD_MAX_OBSTAVOIDANCE_PARAMS; ++i)
{ {
_obstacleQueryParams[i] = new DtObstacleAvoidanceParams(); _obstacleQueryParams[i] = new DtObstacleAvoidanceParams();
} }
// Allocate temp buffer for merging paths. // Allocate temp buffer for merging paths.
_maxPathResult = DtCrowdConst.MAX_PATH_RESULT;
_pathQ = new DtPathQueue(config); _pathQ = new DtPathQueue(config);
_agentIdx = new RcAtomicInteger(0); _agents = new List<DtCrowdAgent>();
_agents = new Dictionary<int, DtCrowdAgent>();
_activeAgents = new List<DtCrowdAgent>();
_grid = new DtProximityGrid(_config.maxAgentRadius * 3);
// The navQuery is mostly used for local searches, no need for large node pool. // The navQuery is mostly used for local searches, no need for large node pool.
SetNavMesh(nav); SetNavMesh(nav);
@ -203,7 +206,7 @@ namespace DotRecast.Detour.Crowd
/// @param[in] option The new configuration. /// @param[in] option The new configuration.
public void SetObstacleAvoidanceParams(int idx, DtObstacleAvoidanceParams option) public void SetObstacleAvoidanceParams(int idx, DtObstacleAvoidanceParams option)
{ {
if (idx >= 0 && idx < DtCrowdConst.DT_CROWD_MAX_OBSTAVOIDANCE_PARAMS) if (idx >= 0 && idx < DT_CROWD_MAX_OBSTAVOIDANCE_PARAMS)
{ {
_obstacleQueryParams[idx] = new DtObstacleAvoidanceParams(option); _obstacleQueryParams[idx] = new DtObstacleAvoidanceParams(option);
} }
@ -215,7 +218,7 @@ namespace DotRecast.Detour.Crowd
/// @return The requested configuration. /// @return The requested configuration.
public DtObstacleAvoidanceParams GetObstacleAvoidanceParams(int idx) public DtObstacleAvoidanceParams GetObstacleAvoidanceParams(int idx)
{ {
if (idx >= 0 && idx < DtCrowdConst.DT_CROWD_MAX_OBSTAVOIDANCE_PARAMS) if (idx >= 0 && idx < DT_CROWD_MAX_OBSTAVOIDANCE_PARAMS)
{ {
return _obstacleQueryParams[idx]; return _obstacleQueryParams[idx];
} }
@ -231,23 +234,23 @@ namespace DotRecast.Detour.Crowd
agent.option = option; agent.option = option;
} }
/// @par /**
/// * Adds a new agent to the crowd.
/// The agent's position will be constrained to the surface of the navigation mesh. *
/// Adds a new agent to the crowd. * @param pos
/// @param[in] pos The requested position of the agent. [(x, y, z)] * The requested position of the agent. [(x, y, z)]
/// @param[in] params The configuration of the agent. * @param params
/// @return The index of the agent in the agent pool. Or -1 if the agent could not be added. * The configuration of the agent.
* @return The newly created agent object
*/
public DtCrowdAgent AddAgent(RcVec3f pos, DtCrowdAgentParams option) public DtCrowdAgent AddAgent(RcVec3f pos, DtCrowdAgentParams option)
{ {
int idx = _agentIdx.GetAndIncrement(); DtCrowdAgent ag = new DtCrowdAgent(_agentId.GetAndIncrement());
DtCrowdAgent ag = new DtCrowdAgent(idx); _agents.Add(ag);
ag.corridor.Init(_maxPathResult);
AddAgent(ag);
UpdateAgentParameters(ag, option); UpdateAgentParameters(ag, option);
// Find nearest position on navmesh and place the agent there. // Find nearest position on navmesh and place the agent there.
var status = _navQuery.FindNearestPoly(pos, _agentPlacementHalfExtents, _filters[ag.option.queryFilterType], out var refs, out var nearestPt, out var _); var status = _navQuery.FindNearestPoly(pos, _ext, _filters[ag.option.queryFilterType], out var refs, out var nearestPt, out var _);
if (status.Failed()) if (status.Failed())
{ {
nearestPt = pos; nearestPt = pos;
@ -260,7 +263,6 @@ namespace DotRecast.Detour.Crowd
ag.topologyOptTime = 0; ag.topologyOptTime = 0;
ag.targetReplanTime = 0; ag.targetReplanTime = 0;
ag.nneis = 0;
ag.dvel = RcVec3f.Zero; ag.dvel = RcVec3f.Zero;
ag.nvel = RcVec3f.Zero; ag.nvel = RcVec3f.Zero;
@ -283,27 +285,15 @@ namespace DotRecast.Detour.Crowd
return ag; return ag;
} }
public DtCrowdAgent GetAgent(int idx) /**
{ * Removes the agent from the crowd.
return _agents.GetValueOrDefault(idx); *
} * @param agent
* Agent to be removed
// Add the agent from the crowd. */
public void AddAgent(DtCrowdAgent agent)
{
if (_agents.TryAdd(agent.idx, agent))
{
_activeAgents.Add(agent);
}
}
// Removes the agent from the crowd.
public void RemoveAgent(DtCrowdAgent agent) public void RemoveAgent(DtCrowdAgent agent)
{ {
if (_agents.Remove(agent.idx)) _agents.Remove(agent);
{
_activeAgents.Remove(agent);
}
} }
private bool RequestMoveTargetReplan(DtCrowdAgent ag, long refs, RcVec3f pos) private bool RequestMoveTargetReplan(DtCrowdAgent ag, long refs, RcVec3f pos)
@ -375,17 +365,17 @@ namespace DotRecast.Detour.Crowd
*/ */
public IList<DtCrowdAgent> GetActiveAgents() public IList<DtCrowdAgent> GetActiveAgents()
{ {
return _activeAgents; return _agents;
} }
public RcVec3f GetQueryExtents() public RcVec3f GetQueryExtents()
{ {
return _agentPlacementHalfExtents; return _ext;
} }
public IDtQueryFilter GetFilter(int i) public IDtQueryFilter GetFilter(int i)
{ {
return i >= 0 && i < DtCrowdConst.DT_CROWD_MAX_QUERY_FILTER_TYPE ? _filters[i] : null; return i >= 0 && i < DT_CROWD_MAX_QUERY_FILTER_TYPE ? _filters[i] : null;
} }
public DtProximityGrid GetGrid() public DtProximityGrid GetGrid()
@ -461,9 +451,8 @@ namespace DotRecast.Detour.Crowd
{ {
using var timer = _telemetry.ScopedTimer(DtCrowdTimerLabel.CheckPathValidity); using var timer = _telemetry.ScopedTimer(DtCrowdTimerLabel.CheckPathValidity);
for (var i = 0; i < agents.Count; i++) foreach (DtCrowdAgent ag in agents)
{ {
var ag = agents[i];
if (ag.state != DtCrowdAgentState.DT_CROWDAGENT_STATE_WALKING) if (ag.state != DtCrowdAgentState.DT_CROWDAGENT_STATE_WALKING)
{ {
continue; continue;
@ -481,7 +470,7 @@ namespace DotRecast.Detour.Crowd
{ {
// Current location is not valid, try to reposition. // Current location is not valid, try to reposition.
// TODO: this can snap agents, how to handle that? // TODO: this can snap agents, how to handle that?
_navQuery.FindNearestPoly(ag.npos, _agentPlacementHalfExtents, _filters[ag.option.queryFilterType], out agentRef, out var nearestPt, out var _); _navQuery.FindNearestPoly(ag.npos, _ext, _filters[ag.option.queryFilterType], out agentRef, out var nearestPt, out var _);
agentPos = nearestPt; agentPos = nearestPt;
if (agentRef == 0) if (agentRef == 0)
@ -521,7 +510,7 @@ namespace DotRecast.Detour.Crowd
if (!_navQuery.IsValidPolyRef(ag.targetRef, _filters[ag.option.queryFilterType])) if (!_navQuery.IsValidPolyRef(ag.targetRef, _filters[ag.option.queryFilterType]))
{ {
// Current target is not valid, try to reposition. // Current target is not valid, try to reposition.
_navQuery.FindNearestPoly(ag.targetPos, _agentPlacementHalfExtents, _filters[ag.option.queryFilterType], out ag.targetRef, out var nearestPt, out var _); _navQuery.FindNearestPoly(ag.targetPos, _ext, _filters[ag.option.queryFilterType], out ag.targetRef, out var nearestPt, out var _);
ag.targetPos = nearestPt; ag.targetPos = nearestPt;
replan = true; replan = true;
} }
@ -545,7 +534,8 @@ namespace DotRecast.Detour.Crowd
replan = true; 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.targetState == DtMoveRequestState.DT_CROWDAGENT_TARGET_VALID)
{ {
if (ag.targetReplanTime > _config.targetReplanDelay && ag.corridor.GetPathCount() < _config.checkLookAhead if (ag.targetReplanTime > _config.targetReplanDelay && ag.corridor.GetPathCount() < _config.checkLookAhead
@ -566,21 +556,16 @@ namespace DotRecast.Detour.Crowd
} }
} }
private readonly RcSortedQueue<DtCrowdAgent> UpdateMoveRequest_queue = new RcSortedQueue<DtCrowdAgent>((a1, a2) => a2.targetReplanTime.CompareTo(a1.targetReplanTime));
private readonly List<long> UpdateMoveRequest_reqPath = new List<long>();
private void UpdateMoveRequest(IList<DtCrowdAgent> agents, float dt) private void UpdateMoveRequest(IList<DtCrowdAgent> agents, float dt)
{ {
using var timer = _telemetry.ScopedTimer(DtCrowdTimerLabel.UpdateMoveRequest); using var timer = _telemetry.ScopedTimer(DtCrowdTimerLabel.UpdateMoveRequest);
RcSortedQueue<DtCrowdAgent> queue = UpdateMoveRequest_queue; RcSortedQueue<DtCrowdAgent> queue = new RcSortedQueue<DtCrowdAgent>((a1, a2) => a2.targetReplanTime.CompareTo(a1.targetReplanTime));
queue.Clear();
// Fire off new requests. // Fire off new requests.
List<long> reqPath = UpdateMoveRequest_reqPath; List<long> reqPath = new List<long>();
reqPath.Clear(); 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) if (ag.state == DtCrowdAgentState.DT_CROWDAGENT_STATE_INVALID)
{ {
continue; continue;
@ -610,7 +595,7 @@ namespace DotRecast.Detour.Crowd
if (ag.targetReplan) // && npath > 10) if (ag.targetReplan) // && npath > 10)
{ {
// Try to use existing steady path during replan if possible. // Try to use existing steady path during replan if possible.
status = _navQuery.FinalizeSlicedFindPathPartial(path, path.Count, ref reqPath); status = _navQuery.FinalizeSlicedFindPathPartial(path, ref reqPath);
} }
else else
{ {
@ -692,9 +677,8 @@ namespace DotRecast.Detour.Crowd
} }
// Process path results. // Process path results.
for (var i = 0; i < agents.Count; i++) foreach (DtCrowdAgent ag in agents)
{ {
var ag = agents[i];
if (ag.targetState == DtMoveRequestState.DT_CROWDAGENT_TARGET_NONE if (ag.targetState == DtMoveRequestState.DT_CROWDAGENT_TARGET_NONE
|| ag.targetState == DtMoveRequestState.DT_CROWDAGENT_TARGET_VELOCITY) || ag.targetState == DtMoveRequestState.DT_CROWDAGENT_TARGET_VELOCITY)
{ {
@ -832,9 +816,8 @@ namespace DotRecast.Detour.Crowd
RcSortedQueue<DtCrowdAgent> queue = new RcSortedQueue<DtCrowdAgent>((a1, a2) => a2.topologyOptTime.CompareTo(a1.topologyOptTime)); RcSortedQueue<DtCrowdAgent> queue = new RcSortedQueue<DtCrowdAgent>((a1, a2) => a2.topologyOptTime.CompareTo(a1.topologyOptTime));
for (var i = 0; i < agents.Count; i++) foreach (DtCrowdAgent ag in agents)
{ {
var ag = agents[i];
if (ag.state != DtCrowdAgentState.DT_CROWDAGENT_STATE_WALKING) if (ag.state != DtCrowdAgentState.DT_CROWDAGENT_STATE_WALKING)
{ {
continue; continue;
@ -870,11 +853,10 @@ namespace DotRecast.Detour.Crowd
{ {
using var timer = _telemetry.ScopedTimer(DtCrowdTimerLabel.BuildProximityGrid); using var timer = _telemetry.ScopedTimer(DtCrowdTimerLabel.BuildProximityGrid);
_grid.Clear(); _grid = new DtProximityGrid(_config.maxAgentRadius * 3);
for (var i = 0; i < agents.Count; i++) foreach (DtCrowdAgent ag in agents)
{ {
var ag = agents[i];
RcVec3f p = ag.npos; RcVec3f p = ag.npos;
float r = ag.option.radius; float r = ag.option.radius;
_grid.AddItem(ag, p.X - r, p.Z - r, p.X + r, p.Z + r); _grid.AddItem(ag, p.X - r, p.Z - r, p.X + r, p.Z + r);
@ -885,9 +867,8 @@ namespace DotRecast.Detour.Crowd
{ {
using var timer = _telemetry.ScopedTimer(DtCrowdTimerLabel.BuildNeighbours); using var timer = _telemetry.ScopedTimer(DtCrowdTimerLabel.BuildNeighbours);
for (var i = 0; i < agents.Count; i++) foreach (DtCrowdAgent ag in agents)
{ {
var ag = agents[i];
if (ag.state != DtCrowdAgentState.DT_CROWDAGENT_STATE_WALKING) if (ag.state != DtCrowdAgentState.DT_CROWDAGENT_STATE_WALKING)
{ {
continue; continue;
@ -896,7 +877,7 @@ namespace DotRecast.Detour.Crowd
// Update the collision boundary after certain distance has been passed or // Update the collision boundary after certain distance has been passed or
// if it has become invalid. // if it has become invalid.
float updateThr = ag.option.collisionQueryRange * 0.25f; float updateThr = ag.option.collisionQueryRange * 0.25f;
if (RcVec.Dist2DSqr(ag.npos, ag.boundary.GetCenter()) > RcMath.Sqr(updateThr) if (RcVecUtils.Dist2DSqr(ag.npos, ag.boundary.GetCenter()) > RcMath.Sqr(updateThr)
|| !ag.boundary.IsValid(_navQuery, _filters[ag.option.queryFilterType])) || !ag.boundary.IsValid(_navQuery, _filters[ag.option.queryFilterType]))
{ {
ag.boundary.Update(ag.corridor.GetFirstPoly(), ag.npos, ag.option.collisionQueryRange, _navQuery, ag.boundary.Update(ag.corridor.GetFirstPoly(), ag.npos, ag.option.collisionQueryRange, _navQuery,
@ -904,66 +885,19 @@ namespace DotRecast.Detour.Crowd
} }
// Query neighbour agents // Query neighbour agents
ag.nneis = GetNeighbours(ag.npos, ag.option.height, ag.option.collisionQueryRange, ag, ag.neis, DtCrowdConst.DT_CROWDAGENT_MAX_NEIGHBOURS, _grid); GetNeighbours(ag.npos, ag.option.height, ag.option.collisionQueryRange, ag, ref ag.neis, _grid);
} }
} }
public static int AddNeighbour(DtCrowdAgent idx, float dist, Span<DtCrowdNeighbour> neis, int nneis, int maxNeis)
{
// Insert neighbour based on the distance.
int nei = 0;
if (0 == nneis)
{
nei = nneis;
}
else if (dist >= neis[nneis - 1].dist)
{
if (nneis >= maxNeis)
return nneis;
nei = nneis;
}
else
{
int i;
for (i = 0; i < nneis; ++i)
{
if (dist <= neis[i].dist)
{
break;
}
}
int tgt = i + 1; private int GetNeighbours(RcVec3f pos, float height, float range, DtCrowdAgent skip, ref List<DtCrowdNeighbour> result, DtProximityGrid grid)
int n = Math.Min(nneis - i, maxNeis - tgt);
Debug.Assert(tgt + n <= maxNeis);
if (n > 0)
{ {
RcSpans.Move(neis, i, tgt, n); result.Clear();
}
nei = i; 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)
neis[nei] = new DtCrowdNeighbour(idx, dist);
return Math.Min(nneis + 1, maxNeis);
}
private int GetNeighbours(RcVec3f pos, float height, float range, DtCrowdAgent skip, DtCrowdNeighbour[] result, int maxResult, DtProximityGrid grid)
{ {
int n = 0;
const int MAX_NEIS = 32;
Span<int> ids = stackalloc int[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 = GetAgent(ids[i]);
if (ag == skip) if (ag == skip)
{ {
continue; continue;
@ -983,10 +917,11 @@ namespace DotRecast.Detour.Crowd
continue; continue;
} }
n = AddNeighbour(ag, distSqr, result, n, maxResult); result.Add(new DtCrowdNeighbour(ag, distSqr));
} }
return n; result.Sort((o1, o2) => o1.dist.CompareTo(o2.dist));
return result.Count;
} }
private void FindCorners(IList<DtCrowdAgent> agents, DtCrowdAgentDebugInfo debug) private void FindCorners(IList<DtCrowdAgent> agents, DtCrowdAgentDebugInfo debug)
@ -994,9 +929,8 @@ namespace DotRecast.Detour.Crowd
using var timer = _telemetry.ScopedTimer(DtCrowdTimerLabel.FindCorners); using var timer = _telemetry.ScopedTimer(DtCrowdTimerLabel.FindCorners);
DtCrowdAgent debugAgent = debug != null ? debug.agent : null; DtCrowdAgent debugAgent = debug != null ? debug.agent : null;
for (var i = 0; i < agents.Count; i++) foreach (DtCrowdAgent ag in agents)
{ {
var ag = agents[i];
if (ag.state != DtCrowdAgentState.DT_CROWDAGENT_STATE_WALKING) if (ag.state != DtCrowdAgentState.DT_CROWDAGENT_STATE_WALKING)
{ {
continue; continue;
@ -1009,13 +943,13 @@ namespace DotRecast.Detour.Crowd
} }
// Find corners for steering // Find corners for steering
ag.ncorners = ag.corridor.FindCorners(ag.corners, DtCrowdConst.DT_CROWDAGENT_MAX_CORNERS, _navQuery, _filters[ag.option.queryFilterType]); ag.corridor.FindCorners(ref ag.corners, DT_CROWDAGENT_MAX_CORNERS, _navQuery, _filters[ag.option.queryFilterType]);
// Check to see if the corner after the next corner is directly visible, // Check to see if the corner after the next corner is directly visible,
// and short cut to there. // and short cut to there.
if ((ag.option.updateFlags & DtCrowdAgentUpdateFlags.DT_CROWD_OPTIMIZE_VIS) != 0 && ag.ncorners > 0) if ((ag.option.updateFlags & DtCrowdAgentUpdateFlags.DT_CROWD_OPTIMIZE_VIS) != 0 && ag.corners.Count > 0)
{ {
RcVec3f target = ag.corners[Math.Min(1, ag.ncorners - 1)].pos; RcVec3f target = ag.corners[Math.Min(1, ag.corners.Count - 1)].pos;
ag.corridor.OptimizePathVisibility(target, ag.option.pathOptimizationRange, _navQuery, ag.corridor.OptimizePathVisibility(target, ag.option.pathOptimizationRange, _navQuery,
_filters[ag.option.queryFilterType]); _filters[ag.option.queryFilterType]);
@ -1042,9 +976,8 @@ namespace DotRecast.Detour.Crowd
{ {
using var timer = _telemetry.ScopedTimer(DtCrowdTimerLabel.TriggerOffMeshConnections); using var timer = _telemetry.ScopedTimer(DtCrowdTimerLabel.TriggerOffMeshConnections);
for (var i = 0; i < agents.Count; i++) foreach (DtCrowdAgent ag in agents)
{ {
var ag = agents[i];
if (ag.state != DtCrowdAgentState.DT_CROWDAGENT_STATE_WALKING) if (ag.state != DtCrowdAgentState.DT_CROWDAGENT_STATE_WALKING)
{ {
continue; continue;
@ -1057,7 +990,7 @@ namespace DotRecast.Detour.Crowd
} }
// Check // Check
float triggerRadius = ag.option.radius * 0.25f;//todo make parameterizable float triggerRadius = ag.option.radius * 2.25f;
if (ag.OverOffmeshConnection(triggerRadius)) if (ag.OverOffmeshConnection(triggerRadius))
{ {
// Prepare to off-mesh connection. // Prepare to off-mesh connection.
@ -1065,18 +998,18 @@ namespace DotRecast.Detour.Crowd
// Adjust the path over the off-mesh connection. // Adjust the path over the off-mesh connection.
long[] refs = new long[2]; long[] refs = new long[2];
if (ag.corridor.MoveOverOffmeshConnection(ag.corners[ag.ncorners - 1].refs, refs, ref anim.startPos, if (ag.corridor.MoveOverOffmeshConnection(ag.corners[ag.corners.Count - 1].refs, refs, ref anim.startPos,
ref anim.endPos, _navQuery)) ref anim.endPos, _navQuery))
{ {
anim.initPos = ag.npos; anim.initPos = ag.npos;
anim.polyRef = refs[1]; anim.polyRef = refs[1];
anim.active = true; anim.active = true;
anim.t = 0.0f; anim.t = 0.0f;
anim.tmax = (RcVec.Dist2D(anim.startPos, anim.endPos) / ag.option.maxSpeed) * 0.5f; anim.tmax = (RcVecUtils.Dist2D(anim.startPos, anim.endPos) / ag.option.maxSpeed) * 0.5f;
ag.state = DtCrowdAgentState.DT_CROWDAGENT_STATE_OFFMESH; ag.state = DtCrowdAgentState.DT_CROWDAGENT_STATE_OFFMESH;
ag.ncorners = 0; ag.corners.Clear();
ag.nneis = 0; ag.neis.Clear();
continue; continue;
} }
else else
@ -1091,9 +1024,8 @@ namespace DotRecast.Detour.Crowd
{ {
using var timer = _telemetry.ScopedTimer(DtCrowdTimerLabel.CalculateSteering); using var timer = _telemetry.ScopedTimer(DtCrowdTimerLabel.CalculateSteering);
for (var i = 0; i < agents.Count; i++) foreach (DtCrowdAgent ag in agents)
{ {
var ag = agents[i];
if (ag.state != DtCrowdAgentState.DT_CROWDAGENT_STATE_WALKING) if (ag.state != DtCrowdAgentState.DT_CROWDAGENT_STATE_WALKING)
{ {
continue; continue;
@ -1128,7 +1060,7 @@ namespace DotRecast.Detour.Crowd
float speedScale = ag.GetDistanceToGoal(slowDownRadius) / slowDownRadius; float speedScale = ag.GetDistanceToGoal(slowDownRadius) / slowDownRadius;
ag.desiredSpeed = ag.option.maxSpeed; ag.desiredSpeed = ag.option.maxSpeed;
dvel = dvel * (ag.desiredSpeed * speedScale); dvel = dvel.Scale(ag.desiredSpeed * speedScale);
} }
// Separation // Separation
@ -1141,7 +1073,7 @@ namespace DotRecast.Detour.Crowd
float w = 0; float w = 0;
RcVec3f disp = new RcVec3f(); RcVec3f disp = new RcVec3f();
for (int j = 0; j < ag.nneis; ++j) for (int j = 0; j < ag.neis.Count; ++j)
{ {
DtCrowdAgent nei = ag.neis[j].agent; DtCrowdAgent nei = ag.neis[j].agent;
@ -1162,20 +1094,20 @@ namespace DotRecast.Detour.Crowd
float dist = MathF.Sqrt(distSqr); float dist = MathF.Sqrt(distSqr);
float weight = separationWeight * (1.0f - RcMath.Sqr(dist * invSeparationDist)); float weight = separationWeight * (1.0f - RcMath.Sqr(dist * invSeparationDist));
disp = RcVec.Mad(disp, diff, weight / dist); disp = RcVecUtils.Mad(disp, diff, weight / dist);
w += 1.0f; w += 1.0f;
} }
if (w > 0.0001f) if (w > 0.0001f)
{ {
// Adjust desired velocity. // Adjust desired velocity.
dvel = RcVec.Mad(dvel, disp, 1.0f / w); dvel = RcVecUtils.Mad(dvel, disp, 1.0f / w);
// Clamp desired velocity to desired speed. // Clamp desired velocity to desired speed.
float speedSqr = dvel.LengthSquared(); float speedSqr = dvel.LengthSquared();
float desiredSqr = RcMath.Sqr(ag.desiredSpeed); float desiredSqr = RcMath.Sqr(ag.desiredSpeed);
if (speedSqr > desiredSqr) if (speedSqr > desiredSqr)
{ {
dvel = dvel * (desiredSqr / speedSqr); dvel = dvel.Scale(desiredSqr / speedSqr);
} }
} }
} }
@ -1190,9 +1122,8 @@ namespace DotRecast.Detour.Crowd
using var timer = _telemetry.ScopedTimer(DtCrowdTimerLabel.PlanVelocity); using var timer = _telemetry.ScopedTimer(DtCrowdTimerLabel.PlanVelocity);
DtCrowdAgent debugAgent = debug != null ? debug.agent : null; DtCrowdAgent debugAgent = debug != null ? debug.agent : null;
for (var i = 0; i < agents.Count; i++) foreach (DtCrowdAgent ag in agents)
{ {
var ag = agents[i];
if (ag.state != DtCrowdAgentState.DT_CROWDAGENT_STATE_WALKING) if (ag.state != DtCrowdAgentState.DT_CROWDAGENT_STATE_WALKING)
{ {
continue; continue;
@ -1203,12 +1134,9 @@ namespace DotRecast.Detour.Crowd
_obstacleQuery.Reset(); _obstacleQuery.Reset();
// Add neighbours as obstacles. // Add neighbours as obstacles.
for (int j = 0; j < ag.nneis; ++j) for (int j = 0; j < ag.neis.Count; ++j)
{ {
DtCrowdAgent nei = ag.neis[j].agent; DtCrowdAgent nei = ag.neis[j].agent;
if(!nei.option.contributeObstacleAvoidance || nei.option.obstacleAvoidanceWeight < ag.option.obstacleAvoidanceWeight)
continue;
_obstacleQuery.AddCircle(nei.npos, nei.option.radius, nei.vel, nei.dvel); _obstacleQuery.AddCircle(nei.npos, nei.option.radius, nei.vel, nei.dvel);
} }
@ -1217,7 +1145,7 @@ namespace DotRecast.Detour.Crowd
{ {
RcVec3f[] s = ag.boundary.GetSegment(j); RcVec3f[] s = ag.boundary.GetSegment(j);
RcVec3f s3 = s[1]; RcVec3f s3 = s[1];
//RcArrays.Copy(s, 3, s3, 0, 3); //Array.Copy(s, 3, s3, 0, 3);
if (DtUtils.TriArea2D(ag.npos, s[0], s3) < 0.0f) if (DtUtils.TriArea2D(ag.npos, s[0], s3) < 0.0f)
{ {
continue; continue;
@ -1263,9 +1191,8 @@ namespace DotRecast.Detour.Crowd
{ {
using var timer = _telemetry.ScopedTimer(DtCrowdTimerLabel.Integrate); using var timer = _telemetry.ScopedTimer(DtCrowdTimerLabel.Integrate);
for (var i = 0; i < agents.Count; i++) foreach (DtCrowdAgent ag in agents)
{ {
var ag = agents[i];
if (ag.state != DtCrowdAgentState.DT_CROWDAGENT_STATE_WALKING) if (ag.state != DtCrowdAgentState.DT_CROWDAGENT_STATE_WALKING)
{ {
continue; continue;
@ -1281,9 +1208,8 @@ namespace DotRecast.Detour.Crowd
for (int iter = 0; iter < 4; ++iter) for (int iter = 0; iter < 4; ++iter)
{ {
for (var i = 0; i < agents.Count; i++) foreach (DtCrowdAgent ag in agents)
{ {
var ag = agents[i];
long idx0 = ag.idx; long idx0 = ag.idx;
if (ag.state != DtCrowdAgentState.DT_CROWDAGENT_STATE_WALKING) if (ag.state != DtCrowdAgentState.DT_CROWDAGENT_STATE_WALKING)
{ {
@ -1294,7 +1220,7 @@ namespace DotRecast.Detour.Crowd
float w = 0; float w = 0;
for (int j = 0; j < ag.nneis; ++j) for (int j = 0; j < ag.neis.Count; ++j)
{ {
DtCrowdAgent nei = ag.neis[j].agent; DtCrowdAgent nei = ag.neis[j].agent;
long idx1 = nei.idx; long idx1 = nei.idx;
@ -1328,7 +1254,7 @@ namespace DotRecast.Detour.Crowd
pen = (1.0f / dist) * (pen * 0.5f) * _config.collisionResolveFactor; pen = (1.0f / dist) * (pen * 0.5f) * _config.collisionResolveFactor;
} }
ag.disp = RcVec.Mad(ag.disp, diff, pen); ag.disp = RcVecUtils.Mad(ag.disp, diff, pen);
w += 1.0f; w += 1.0f;
} }
@ -1336,13 +1262,12 @@ namespace DotRecast.Detour.Crowd
if (w > 0.0001f) if (w > 0.0001f)
{ {
float iw = 1.0f / w; float iw = 1.0f / w;
ag.disp = ag.disp * iw; ag.disp = ag.disp.Scale(iw);
} }
} }
for (var i = 0; i < agents.Count; i++) foreach (DtCrowdAgent ag in agents)
{ {
var ag = agents[i];
if (ag.state != DtCrowdAgentState.DT_CROWDAGENT_STATE_WALKING) if (ag.state != DtCrowdAgentState.DT_CROWDAGENT_STATE_WALKING)
{ {
continue; continue;
@ -1357,9 +1282,8 @@ namespace DotRecast.Detour.Crowd
{ {
using var timer = _telemetry.ScopedTimer(DtCrowdTimerLabel.MoveAgents); using var timer = _telemetry.ScopedTimer(DtCrowdTimerLabel.MoveAgents);
for (var i = 0; i < agents.Count; i++) foreach (DtCrowdAgent ag in agents)
{ {
var ag = agents[i];
if (ag.state != DtCrowdAgentState.DT_CROWDAGENT_STATE_WALKING) if (ag.state != DtCrowdAgentState.DT_CROWDAGENT_STATE_WALKING)
{ {
continue; continue;
@ -1384,9 +1308,8 @@ namespace DotRecast.Detour.Crowd
{ {
using var timer = _telemetry.ScopedTimer(DtCrowdTimerLabel.UpdateOffMeshConnections); using var timer = _telemetry.ScopedTimer(DtCrowdTimerLabel.UpdateOffMeshConnections);
for (var i = 0; i < agents.Count; i++) foreach (DtCrowdAgent ag in agents)
{ {
var ag = agents[i];
DtCrowdAgentAnimation anim = ag.animation; DtCrowdAgentAnimation anim = ag.animation;
if (!anim.active) if (!anim.active)
{ {

View File

@ -1,7 +1,7 @@
/* /*
Copyright (c) 2009-2010 Mikko Mononen memon@inside.org Copyright (c) 2009-2010 Mikko Mononen memon@inside.org
recast4j copyright (c) 2015-2019 Piotr Piastucki piotr@jtilia.org recast4j copyright (c) 2015-2019 Piotr Piastucki piotr@jtilia.org
DotRecast Copyright (c) 2023-2024 Choi Ikpil ikpil@naver.com DotRecast Copyright (c) 2023 Choi Ikpil ikpil@naver.com
This software is provided 'as-is', without any express or implied This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages warranty. In no event will the authors be held liable for any damages
@ -28,53 +28,75 @@ namespace DotRecast.Detour.Crowd
/// @ingroup crowd /// @ingroup crowd
public class DtCrowdAgent public class DtCrowdAgent
{ {
public readonly int idx; public readonly long idx;
/// The type of mesh polygon the agent is traversing. (See: #CrowdAgentState) /// The type of mesh polygon the agent is traversing. (See: #CrowdAgentState)
public DtCrowdAgentState state; 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; public bool partial;
/// The path corridor the agent is using. /// The path corridor the agent is using.
public readonly DtPathCorridor corridor; public DtPathCorridor corridor;
/// The local boundary data for the agent. /// The local boundary data for the agent.
public readonly DtLocalBoundary boundary; public DtLocalBoundary boundary;
/// Time since the agent's path corridor was optimized. /// Time since the agent's path corridor was optimized.
public float topologyOptTime; public float topologyOptTime;
/// The known neighbors of the agent. /// The known neighbors of the agent.
public readonly DtCrowdNeighbour[] neis = new DtCrowdNeighbour[DtCrowdConst.DT_CROWDAGENT_MAX_NEIGHBOURS]; public List<DtCrowdNeighbour> neis = new List<DtCrowdNeighbour>();
/// The number of neighbors.
public int nneis;
/// The desired speed. /// The desired speed.
public float desiredSpeed; public float desiredSpeed;
public RcVec3f npos = new RcVec3f(); // < The current agent position. [(x, y, z)] public RcVec3f npos = new RcVec3f();
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. /// The agent's configuration parameters.
public DtCrowdAgentParams option; public DtCrowdAgentParams option;
/// The local path corridor corners for the agent. /// The local path corridor corners for the agent.
public DtStraightPath[] corners = new DtStraightPath[DtCrowdConst.DT_CROWDAGENT_MAX_CORNERS]; public List<DtStraightPath> corners = new List<DtStraightPath>();
/// The number of corners. public DtMoveRequestState targetState;
public int ncorners;
public DtMoveRequestState targetState; // < State of the movement request. /// < State of the movement request.
public long targetRef; // < Target polyref of the movement request. public long targetRef;
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 /// < Target polyref of the movement request.
public bool targetReplan; // < Flag indicating that the current path is being replanned. public RcVec3f targetPos = new RcVec3f();
public float targetReplanTime; // <Time since the agent's target was replanned.
/// < 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 float targetReplanWaitTime;
public DtCrowdAgentAnimation animation; public DtCrowdAgentAnimation animation;
@ -94,28 +116,28 @@ namespace DotRecast.Detour.Crowd
RcVec3f dv = RcVec3f.Subtract(nvel, vel); RcVec3f dv = RcVec3f.Subtract(nvel, vel);
float ds = dv.Length(); float ds = dv.Length();
if (ds > maxDelta) if (ds > maxDelta)
dv = dv * (maxDelta / ds); dv = dv.Scale(maxDelta / ds);
vel = RcVec3f.Add(vel, dv); vel = RcVec3f.Add(vel, dv);
// Integrate // Integrate
if (vel.Length() > 0.0001f) if (vel.Length() > 0.0001f)
npos = RcVec.Mad(npos, vel, dt); npos = RcVecUtils.Mad(npos, vel, dt);
else else
vel = RcVec3f.Zero; vel = RcVec3f.Zero;
} }
public bool OverOffmeshConnection(float radius) public bool OverOffmeshConnection(float radius)
{ {
if (0 == ncorners) if (0 == corners.Count)
return false; return false;
bool offMeshConnection = ((corners[ncorners - 1].flags bool offMeshConnection = ((corners[corners.Count - 1].flags
& DtStraightPathFlags.DT_STRAIGHTPATH_OFFMESH_CONNECTION) != 0) & DtStraightPathFlags.DT_STRAIGHTPATH_OFFMESH_CONNECTION) != 0)
? true ? true
: false; : false;
if (offMeshConnection) if (offMeshConnection)
{ {
float distSq = RcVec.Dist2DSqr(npos, corners[ncorners - 1].pos); float distSq = RcVecUtils.Dist2DSqr(npos, corners[corners.Count - 1].pos);
if (distSq < radius * radius) if (distSq < radius * radius)
return true; return true;
} }
@ -125,12 +147,12 @@ namespace DotRecast.Detour.Crowd
public float GetDistanceToGoal(float range) public float GetDistanceToGoal(float range)
{ {
if (0 == ncorners) if (0 == corners.Count)
return range; return range;
bool endOfPath = ((corners[ncorners - 1].flags & DtStraightPathFlags.DT_STRAIGHTPATH_END) != 0) ? true : false; bool endOfPath = ((corners[corners.Count - 1].flags & DtStraightPathFlags.DT_STRAIGHTPATH_END) != 0) ? true : false;
if (endOfPath) if (endOfPath)
return Math.Min(RcVec.Dist2D(npos, corners[ncorners - 1].pos), range); return Math.Min(RcVecUtils.Dist2D(npos, corners[corners.Count - 1].pos), range);
return range; return range;
} }
@ -138,10 +160,10 @@ namespace DotRecast.Detour.Crowd
public RcVec3f CalcSmoothSteerDirection() public RcVec3f CalcSmoothSteerDirection()
{ {
RcVec3f dir = new RcVec3f(); RcVec3f dir = new RcVec3f();
if (0 < ncorners) if (0 < corners.Count)
{ {
int ip0 = 0; int ip0 = 0;
int ip1 = Math.Min(1, ncorners - 1); int ip1 = Math.Min(1, corners.Count - 1);
var p0 = corners[ip0].pos; var p0 = corners[ip0].pos;
var p1 = corners[ip1].pos; var p1 = corners[ip1].pos;
@ -153,7 +175,7 @@ namespace DotRecast.Detour.Crowd
float len0 = dir0.Length(); float len0 = dir0.Length();
float len1 = dir1.Length(); float len1 = dir1.Length();
if (len1 > 0.001f) if (len1 > 0.001f)
dir1 = dir1 * (1.0f / len1); dir1 = dir1.Scale(1.0f / len1);
dir.X = dir0.X - dir1.X * len0 * 0.5f; dir.X = dir0.X - dir1.X * len0 * 0.5f;
dir.Y = 0; dir.Y = 0;
@ -167,7 +189,7 @@ namespace DotRecast.Detour.Crowd
public RcVec3f CalcStraightSteerDirection() public RcVec3f CalcStraightSteerDirection()
{ {
RcVec3f dir = new RcVec3f(); RcVec3f dir = new RcVec3f();
if (0 < ncorners) if (0 < corners.Count)
{ {
dir = RcVec3f.Subtract(corners[0].pos, npos); dir = RcVec3f.Subtract(corners[0].pos, npos);
dir.Y = 0; dir.Y = 0;

View File

@ -1,7 +1,7 @@
/* /*
Copyright (c) 2009-2010 Mikko Mononen memon@inside.org Copyright (c) 2009-2010 Mikko Mononen memon@inside.org
recast4j copyright (c) 2015-2019 Piotr Piastucki piotr@jtilia.org recast4j copyright (c) 2015-2019 Piotr Piastucki piotr@jtilia.org
DotRecast Copyright (c) 2023-2024 Choi Ikpil ikpil@naver.com DotRecast Copyright (c) 2023 Choi Ikpil ikpil@naver.com
This software is provided 'as-is', without any express or implied This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages warranty. In no event will the authors be held liable for any damages

View File

@ -1,4 +1,4 @@
namespace DotRecast.Detour.Crowd namespace DotRecast.Detour.Crowd
{ {
public class DtCrowdAgentConfig public class DtCrowdAgentConfig
{ {

View File

@ -1,7 +1,7 @@
/* /*
Copyright (c) 2009-2010 Mikko Mononen memon@inside.org Copyright (c) 2009-2010 Mikko Mononen memon@inside.org
recast4j copyright (c) 2015-2019 Piotr Piastucki piotr@jtilia.org recast4j copyright (c) 2015-2019 Piotr Piastucki piotr@jtilia.org
DotRecast Copyright (c) 2023-2024 Choi Ikpil ikpil@naver.com DotRecast Copyright (c) 2023 Choi Ikpil ikpil@naver.com
This software is provided 'as-is', without any express or implied This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages warranty. In no event will the authors be held liable for any damages

View File

@ -1,7 +1,7 @@
/* /*
Copyright (c) 2009-2010 Mikko Mononen memon@inside.org Copyright (c) 2009-2010 Mikko Mononen memon@inside.org
recast4j copyright (c) 2015-2019 Piotr Piastucki piotr@jtilia.org recast4j copyright (c) 2015-2019 Piotr Piastucki piotr@jtilia.org
DotRecast Copyright (c) 2023-2024 Choi Ikpil ikpil@naver.com DotRecast Copyright (c) 2023 Choi Ikpil ikpil@naver.com
This software is provided 'as-is', without any express or implied This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages warranty. In no event will the authors be held liable for any damages
@ -24,15 +24,23 @@ namespace DotRecast.Detour.Crowd
/// @ingroup crowd /// @ingroup crowd
public class DtCrowdAgentParams public class DtCrowdAgentParams
{ {
public float radius; // < Agent radius. [Limit: >= 0] /// < Agent radius. [Limit: >= 0]
public float height; // < Agent height. [Limit: > 0] public float radius;
public float maxAcceleration; // < Maximum allowed acceleration. [Limit: >= 0]
public float maxSpeed; // < Maximum allowed speed. [Limit: >= 0] /// < Agent height. [Limit: > 0]
public float height;
/// < Maximum allowed acceleration. [Limit: >= 0]
public float maxAcceleration;
/// < Maximum allowed speed. [Limit: >= 0]
public float maxSpeed;
/// Defines how close a collision element must be before it is considered for steering behaviors. [Limits: > 0] /// Defines how close a collision element must be before it is considered for steering behaviors. [Limits: > 0]
public float collisionQueryRange; public float collisionQueryRange;
public float pathOptimizationRange; // < The path visibility optimization range. [Limit: > 0] /// < The path visibility optimization range. [Limit: > 0]
public float pathOptimizationRange;
/// How aggresive the agent manager should be at avoiding collisions with this agent. [Limit: >= 0] /// How aggresive the agent manager should be at avoiding collisions with this agent. [Limit: >= 0]
public float separationWeight; public float separationWeight;
@ -45,9 +53,6 @@ namespace DotRecast.Detour.Crowd
/// [Limits: 0 <= value < #DT_CROWD_MAX_OBSTAVOIDANCE_PARAMS] /// [Limits: 0 <= value < #DT_CROWD_MAX_OBSTAVOIDANCE_PARAMS]
public int obstacleAvoidanceType; public int obstacleAvoidanceType;
public bool contributeObstacleAvoidance;
public float obstacleAvoidanceWeight;
/// The index of the query filter used by this agent. /// The index of the query filter used by this agent.
public int queryFilterType; public int queryFilterType;

View File

@ -1,4 +1,4 @@
namespace DotRecast.Detour.Crowd namespace DotRecast.Detour.Crowd
{ {
/// The type of navigation mesh polygon the agent is currently traversing. /// The type of navigation mesh polygon the agent is currently traversing.
/// @ingroup crowd /// @ingroup crowd

View File

@ -1,4 +1,4 @@
namespace DotRecast.Detour.Crowd namespace DotRecast.Detour.Crowd
{ {
/// Crowd agent update flags. /// Crowd agent update flags.
/// @ingroup crowd /// @ingroup crowd

View File

@ -1,6 +1,6 @@
/* /*
recast4j copyright (c) 2021 Piotr Piastucki piotr@jtilia.org recast4j copyright (c) 2021 Piotr Piastucki piotr@jtilia.org
DotRecast Copyright (c) 2023-2024 Choi Ikpil ikpil@naver.com DotRecast Copyright (c) 2023 Choi Ikpil ikpil@naver.com
This software is provided 'as-is', without any express or implied This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages warranty. In no event will the authors be held liable for any damages
@ -23,16 +23,52 @@ namespace DotRecast.Detour.Crowd
{ {
public readonly float maxAgentRadius; public readonly float maxAgentRadius;
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) * Max number of path requests in the queue
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 pathQueueSize = 32;
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 * 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 float collisionResolveFactor = 0.7f; public float collisionResolveFactor = 0.7f;
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 /**
* 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 DtCrowdConfig(float maxAgentRadius) public DtCrowdConfig(float maxAgentRadius)
{ {

View File

@ -1,35 +0,0 @@
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

@ -1,13 +1,16 @@
namespace DotRecast.Detour.Crowd namespace DotRecast.Detour.Crowd
{ {
/// Provides neighbor data for agents managed by the crowd. /// Provides neighbor data for agents managed by the crowd.
/// @ingroup crowd /// @ingroup crowd
/// @see dtCrowdAgent::neis, dtCrowd /// @see dtCrowdAgent::neis, dtCrowd
public readonly struct DtCrowdNeighbour public readonly struct DtCrowdNeighbour
{ {
public readonly DtCrowdAgent agent; // < The index of the neighbor in the crowd. public readonly DtCrowdAgent agent;
public readonly float dist; // < The distance between the current agent and the neighbor.
/// < The index of the neighbor in the crowd.
public readonly float dist;
/// < The distance between the current agent and the neighbor.
public DtCrowdNeighbour(DtCrowdAgent agent, float dist) public DtCrowdNeighbour(DtCrowdAgent agent, float dist)
{ {
this.agent = agent; this.agent = agent;

View File

@ -1,23 +0,0 @@
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

@ -1,6 +1,6 @@
/* /*
recast4j copyright (c) 2021 Piotr Piastucki piotr@jtilia.org recast4j copyright (c) 2021 Piotr Piastucki piotr@jtilia.org
DotRecast Copyright (c) 2023-2024 Choi Ikpil ikpil@naver.com DotRecast Copyright (c) 2023 Choi Ikpil ikpil@naver.com
This software is provided 'as-is', without any express or implied This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages warranty. In no event will the authors be held liable for any damages
@ -19,9 +19,11 @@ freely, subject to the following restrictions:
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics;
using System.Linq; using System.Linq;
using System.Reflection.Emit;
using DotRecast.Core; using DotRecast.Core;
using DotRecast.Core.Buffers; using DotRecast.Core.Numerics;
namespace DotRecast.Detour.Crowd namespace DotRecast.Detour.Crowd
{ {
@ -32,7 +34,7 @@ namespace DotRecast.Detour.Crowd
private float _maxTimeToFindPath; private float _maxTimeToFindPath;
private readonly Dictionary<DtCrowdTimerLabel, long> _executionTimings = new Dictionary<DtCrowdTimerLabel, long>(); private readonly Dictionary<DtCrowdTimerLabel, long> _executionTimings = new Dictionary<DtCrowdTimerLabel, long>();
private readonly Dictionary<DtCrowdTimerLabel, RcCyclicBuffer<long>> _executionTimingSamples = new Dictionary<DtCrowdTimerLabel, RcCyclicBuffer<long>>(); private readonly Dictionary<DtCrowdTimerLabel, List<long>> _executionTimingSamples = new Dictionary<DtCrowdTimerLabel, List<long>>();
public float MaxTimeToEnqueueRequest() public float MaxTimeToEnqueueRequest()
{ {
@ -69,27 +71,33 @@ namespace DotRecast.Detour.Crowd
_maxTimeToFindPath = Math.Max(_maxTimeToFindPath, time); _maxTimeToFindPath = Math.Max(_maxTimeToFindPath, time);
} }
internal DtCrowdScopedTimer ScopedTimer(DtCrowdTimerLabel label) public IDisposable ScopedTimer(DtCrowdTimerLabel label)
{ {
return new DtCrowdScopedTimer(this, label); Start(label);
return new RcAnonymousDisposable(() => Stop(label));
} }
internal void Start(DtCrowdTimerLabel name) private void Start(DtCrowdTimerLabel name)
{ {
_executionTimings.Add(name, RcFrequency.Ticks); _executionTimings.Add(name, RcFrequency.Ticks);
} }
internal void Stop(DtCrowdTimerLabel name) private void Stop(DtCrowdTimerLabel name)
{ {
long duration = RcFrequency.Ticks - _executionTimings[name]; long duration = RcFrequency.Ticks - _executionTimings[name];
if (!_executionTimingSamples.TryGetValue(name, out var cb)) if (!_executionTimingSamples.TryGetValue(name, out var s))
{ {
cb = new RcCyclicBuffer<long>(TIMING_SAMPLES); s = new List<long>();
_executionTimingSamples.Add(name, cb); _executionTimingSamples.Add(name, s);
} }
cb.PushBack(duration); if (s.Count == TIMING_SAMPLES)
_executionTimings[name] = (long)cb.Average(); {
s.RemoveAt(0);
}
s.Add(duration);
_executionTimings[name] = (long)s.Average();
} }
} }
} }

View File

@ -1,4 +1,4 @@
namespace DotRecast.Detour.Crowd namespace DotRecast.Detour.Crowd
{ {
public class DtCrowdTimerLabel public class DtCrowdTimerLabel
{ {

View File

@ -1,7 +1,7 @@
/* /*
Copyright (c) 2009-2010 Mikko Mononen memon@inside.org Copyright (c) 2009-2010 Mikko Mononen memon@inside.org
recast4j copyright (c) 2015-2019 Piotr Piastucki piotr@jtilia.org recast4j copyright (c) 2015-2019 Piotr Piastucki piotr@jtilia.org
DotRecast Copyright (c) 2023-2024 Choi Ikpil ikpil@naver.com DotRecast Copyright (c) 2023 Choi Ikpil ikpil@naver.com
This software is provided 'as-is', without any express or implied This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages warranty. In no event will the authors be held liable for any damages
@ -18,7 +18,6 @@ freely, subject to the following restrictions:
3. This notice may not be removed or altered from any source distribution. 3. This notice may not be removed or altered from any source distribution.
*/ */
using System;
using System.Collections.Generic; using System.Collections.Generic;
using DotRecast.Core; using DotRecast.Core;
using DotRecast.Core.Numerics; using DotRecast.Core.Numerics;
@ -26,6 +25,8 @@ using DotRecast.Core.Numerics;
namespace DotRecast.Detour.Crowd namespace DotRecast.Detour.Crowd
{ {
public class DtLocalBoundary public class DtLocalBoundary
{ {
public const int MAX_LOCAL_SEGS = 8; public const int MAX_LOCAL_SEGS = 8;
@ -53,7 +54,7 @@ namespace DotRecast.Detour.Crowd
DtSegment seg = new DtSegment(); DtSegment seg = new DtSegment();
seg.s[0] = s.vmin; seg.s[0] = s.vmin;
seg.s[1] = s.vmax; seg.s[1] = s.vmax;
//RcArrays.Copy(s, seg.s, 6); //Array.Copy(s, seg.s, 6);
seg.d = dist; seg.d = dist;
if (0 == m_segs.Count) if (0 == m_segs.Count)
{ {
@ -91,8 +92,6 @@ namespace DotRecast.Detour.Crowd
public void Update(long startRef, RcVec3f pos, float collisionQueryRange, DtNavMeshQuery navquery, IDtQueryFilter filter) public void Update(long startRef, RcVec3f pos, float collisionQueryRange, DtNavMeshQuery navquery, IDtQueryFilter filter)
{ {
const int MAX_SEGS_PER_POLY = DtDetour.DT_VERTS_PER_POLYGON * 3;
if (startRef == 0) if (startRef == 0)
{ {
Reset(); Reset();
@ -107,17 +106,18 @@ namespace DotRecast.Detour.Crowd
{ {
// Secondly, store all polygon edges. // Secondly, store all polygon edges.
m_segs.Clear(); m_segs.Clear();
Span<RcSegmentVert> segs = stackalloc RcSegmentVert[MAX_SEGS_PER_POLY];
int nsegs = 0; var segmentVerts = new List<RcSegmentVert>();
var segmentRefs = new List<long>();
for (int j = 0; j < m_polys.Count; ++j) for (int j = 0; j < m_polys.Count; ++j)
{ {
var result = navquery.GetPolyWallSegments(m_polys[j], filter, segs, null, ref nsegs, MAX_SEGS_PER_POLY); var result = navquery.GetPolyWallSegments(m_polys[j], false, filter, ref segmentVerts, ref segmentRefs);
if (result.Succeeded()) if (result.Succeeded())
{ {
for (int k = 0; k < nsegs; ++k) for (int k = 0; k < segmentRefs.Count; ++k)
{ {
ref RcSegmentVert s = ref segs[k]; RcSegmentVert s = segmentVerts[k];
var s0 = s.vmin; var s0 = s.vmin;
var s3 = s.vmax; var s3 = s.vmax;

View File

@ -1,4 +1,4 @@
namespace DotRecast.Detour.Crowd namespace DotRecast.Detour.Crowd
{ {
public enum DtMoveRequestState public enum DtMoveRequestState
{ {

View File

@ -1,7 +1,7 @@
/* /*
Copyright (c) 2009-2010 Mikko Mononen memon@inside.org Copyright (c) 2009-2010 Mikko Mononen memon@inside.org
recast4j copyright (c) 2015-2019 Piotr Piastucki piotr@jtilia.org recast4j copyright (c) 2015-2019 Piotr Piastucki piotr@jtilia.org
DotRecast Copyright (c) 2023-2024 Choi Ikpil ikpil@naver.com DotRecast Copyright (c) 2023 Choi Ikpil ikpil@naver.com
This software is provided 'as-is', without any express or implied This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages warranty. In no event will the authors be held liable for any damages

View File

@ -1,4 +1,4 @@
namespace DotRecast.Detour.Crowd namespace DotRecast.Detour.Crowd
{ {
public class DtObstacleAvoidanceParams public class DtObstacleAvoidanceParams
{ {
@ -8,11 +8,18 @@ namespace DotRecast.Detour.Crowd
public float weightSide; public float weightSide;
public float weightToi; public float weightToi;
public float horizTime; public float horizTime;
public int gridSize; // < grid public int gridSize;
public int adaptiveDivs; // < adaptive
public int adaptiveRings; // < adaptive
public int adaptiveDepth; // < adaptive
/// < grid
public int adaptiveDivs;
/// < adaptive
public int adaptiveRings;
/// < adaptive
public int adaptiveDepth;
/// < adaptive
public DtObstacleAvoidanceParams() public DtObstacleAvoidanceParams()
{ {
velBias = 0.4f; velBias = 0.4f;

View File

@ -1,7 +1,7 @@
/* /*
Copyright (c) 2009-2010 Mikko Mononen memon@inside.org Copyright (c) 2009-2010 Mikko Mononen memon@inside.org
recast4j copyright (c) 2015-2019 Piotr Piastucki piotr@jtilia.org recast4j copyright (c) 2015-2019 Piotr Piastucki piotr@jtilia.org
DotRecast Copyright (c) 2023-2024 Choi Ikpil ikpil@naver.com DotRecast Copyright (c) 2023 Choi Ikpil ikpil@naver.com
This software is provided 'as-is', without any express or implied This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages warranty. In no event will the authors be held liable for any damages
@ -19,7 +19,6 @@ freely, subject to the following restrictions:
*/ */
using System; using System;
using System.Runtime.CompilerServices;
using DotRecast.Core; using DotRecast.Core;
using DotRecast.Core.Numerics; using DotRecast.Core.Numerics;
@ -28,9 +27,11 @@ namespace DotRecast.Detour.Crowd
{ {
public class DtObstacleAvoidanceQuery public class DtObstacleAvoidanceQuery
{ {
public const int DT_MAX_PATTERN_DIVS = 32; // < Max numver of adaptive divs. public const int DT_MAX_PATTERN_DIVS = 32;
/// < Max numver of adaptive divs.
public const int DT_MAX_PATTERN_RINGS = 4; public const int DT_MAX_PATTERN_RINGS = 4;
public const float DT_PI = 3.14159265f;
private DtObstacleAvoidanceParams m_params; private DtObstacleAvoidanceParams m_params;
private float m_invHorizTime; private float m_invHorizTime;
@ -186,16 +187,16 @@ namespace DotRecast.Detour.Crowd
{ {
RcVec3f v = RcVec3f.Subtract(bq, bp); RcVec3f v = RcVec3f.Subtract(bq, bp);
RcVec3f w = RcVec3f.Subtract(ap, bp); RcVec3f w = RcVec3f.Subtract(ap, bp);
float d = RcVec.Perp2D(u, v); float d = RcVecUtils.Perp2D(u, v);
if (MathF.Abs(d) < 1e-6f) if (MathF.Abs(d) < 1e-6f)
return false; return false;
d = 1.0f / d; d = 1.0f / d;
t = RcVec.Perp2D(v, w) * d; t = RcVecUtils.Perp2D(v, w) * d;
if (t < 0 || t > 1) if (t < 0 || t > 1)
return false; return false;
float s = RcVec.Perp2D(u, w) * d; float s = RcVecUtils.Perp2D(u, w) * d;
if (s < 0 || s > 1) if (s < 0 || s > 1)
return false; return false;
@ -216,8 +217,8 @@ namespace DotRecast.Detour.Crowd
float minPenalty, DtObstacleAvoidanceDebugData debug) float minPenalty, DtObstacleAvoidanceDebugData debug)
{ {
// penalty for straying away from the desired and current velocities // penalty for straying away from the desired and current velocities
float vpen = m_params.weightDesVel * (RcVec.Dist2D(vcand, dvel) * m_invVmax); float vpen = m_params.weightDesVel * (RcVecUtils.Dist2D(vcand, dvel) * m_invVmax);
float vcpen = m_params.weightCurVel * (RcVec.Dist2D(vcand, vel) * m_invVmax); float vcpen = m_params.weightCurVel * (RcVecUtils.Dist2D(vcand, vel) * m_invVmax);
// find the threshold hit time to bail out based on the early out penalty // find the threshold hit time to bail out based on the early out penalty
// (see how the penalty is calculated below to understand) // (see how the penalty is calculated below to understand)
@ -236,7 +237,7 @@ namespace DotRecast.Detour.Crowd
DtObstacleCircle cir = m_circles[i]; DtObstacleCircle cir = m_circles[i];
// RVO // RVO
RcVec3f vab = vcand * 2; RcVec3f vab = vcand.Scale(2);
vab = RcVec3f.Subtract(vab, vel); vab = RcVec3f.Subtract(vab, vel);
vab = RcVec3f.Subtract(vab, cir.vel); vab = RcVec3f.Subtract(vab, cir.vel);
@ -361,8 +362,7 @@ namespace DotRecast.Detour.Crowd
} }
// vector normalization that ignores the y-component. // vector normalization that ignores the y-component.
[MethodImpl(MethodImplOptions.AggressiveInlining)] void DtNormalize2D(float[] v)
void DtNormalize2D(Span<float> v)
{ {
float d = MathF.Sqrt(v[0] * v[0] + v[2] * v[2]); float d = MathF.Sqrt(v[0] * v[0] + v[2] * v[2]);
if (d == 0) if (d == 0)
@ -373,8 +373,7 @@ namespace DotRecast.Detour.Crowd
} }
// vector normalization that ignores the y-component. // vector normalization that ignores the y-component.
[MethodImpl(MethodImplOptions.AggressiveInlining)] RcVec3f DtRotate2D(float[] v, float ang)
RcVec3f DtRotate2D(Span<float> v, float ang)
{ {
RcVec3f dest = new RcVec3f(); RcVec3f dest = new RcVec3f();
float c = MathF.Cos(ang); float c = MathF.Cos(ang);
@ -385,6 +384,7 @@ namespace DotRecast.Detour.Crowd
return dest; return dest;
} }
static readonly float DT_PI = 3.14159265f;
public int SampleVelocityAdaptive(RcVec3f pos, float rad, float vmax, RcVec3f vel, RcVec3f dvel, out RcVec3f nvel, public int SampleVelocityAdaptive(RcVec3f pos, float rad, float vmax, RcVec3f vel, RcVec3f dvel, out RcVec3f nvel,
DtObstacleAvoidanceParams option, DtObstacleAvoidanceParams option,
@ -402,7 +402,7 @@ namespace DotRecast.Detour.Crowd
debug.Reset(); debug.Reset();
// Build sampling pattern aligned to desired velocity. // Build sampling pattern aligned to desired velocity.
Span<float> pat = stackalloc float[(DT_MAX_PATTERN_DIVS * DT_MAX_PATTERN_RINGS + 1) * 2]; float[] pat = new float[(DT_MAX_PATTERN_DIVS * DT_MAX_PATTERN_RINGS + 1) * 2];
int npat = 0; int npat = 0;
int ndivs = m_params.adaptiveDivs; int ndivs = m_params.adaptiveDivs;
@ -416,7 +416,7 @@ namespace DotRecast.Detour.Crowd
float sa = MathF.Sin(da); float sa = MathF.Sin(da);
// desired direction // desired direction
Span<float> ddir = stackalloc float[6]; float[] ddir = new float[6];
ddir[0] = dvel.X; ddir[0] = dvel.X;
ddir[1] = dvel.Y; ddir[1] = dvel.Y;
ddir[2] = dvel.Z; ddir[2] = dvel.Z;

View File

@ -1,4 +1,4 @@
using DotRecast.Core.Numerics; using DotRecast.Core.Numerics;
namespace DotRecast.Detour.Crowd namespace DotRecast.Detour.Crowd
{ {

View File

@ -1,4 +1,4 @@
using DotRecast.Core.Numerics; using DotRecast.Core.Numerics;
namespace DotRecast.Detour.Crowd namespace DotRecast.Detour.Crowd
{ {

View File

@ -1,7 +1,7 @@
/* /*
Copyright (c) 2009-2010 Mikko Mononen memon@inside.org Copyright (c) 2009-2010 Mikko Mononen memon@inside.org
recast4j copyright (c) 2015-2019 Piotr Piastucki piotr@jtilia.org recast4j copyright (c) 2015-2019 Piotr Piastucki piotr@jtilia.org
DotRecast Copyright (c) 2023-2024 Choi Ikpil ikpil@naver.com DotRecast Copyright (c) 2023 Choi Ikpil ikpil@naver.com
This software is provided 'as-is', without any express or implied This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages warranty. In no event will the authors be held liable for any damages
@ -26,179 +26,160 @@ using DotRecast.Core.Numerics;
namespace DotRecast.Detour.Crowd namespace DotRecast.Detour.Crowd
{ {
/// Represents a dynamic polygon corridor used to plan agent movement. /**
/// @ingroup crowd, detour * 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.
*
*/
public class DtPathCorridor public class DtPathCorridor
{ {
private RcVec3f m_pos; private RcVec3f m_pos = new RcVec3f();
private RcVec3f m_target; private RcVec3f m_target = new RcVec3f();
private List<long> m_path;
private List<long> m_path = new List<long>();
private int m_npath;
private int m_maxPath;
/** /**
@class dtPathCorridor * Allocates the corridor's path buffer.
@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() public DtPathCorridor()
{ {
m_path = new List<long>();
} }
/// @par /**
/// * Resets the path corridor to the specified position.
/// @warning Cannot be called more than once. *
/// Allocates the corridor's path buffer. * @param ref
/// @param[in] maxPath The maximum path size the corridor can handle. * The polygon reference containing the position.
/// @return True if the initialization succeeded. * @param pos
public bool Init(int maxPath) * The new position in the corridor. [(x, y, z)]
{ */
if (m_path.Capacity < maxPath)
m_path.Capacity = maxPath;
m_npath = 0;
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) public void Reset(long refs, RcVec3f pos)
{ {
m_pos = pos;
m_target = pos;
m_path.Clear(); m_path.Clear();
m_path.Add(refs); m_path.Add(refs);
m_npath = 1; m_pos = pos;
m_target = pos;
} }
private static readonly float MIN_TARGET_DIST = RcMath.Sqr(0.01f);
/** /**
@par * 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 * This is the function used to plan local movement within the corridor. One or more corners can be detected in
detected in order to plan movement. It performs essentially the same function as #dtNavMeshQuery::findStraightPath. * 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) * Due to internal optimizations, the maximum number of corners returned will be (@p maxCorners - 1) For example: If
For example: If the buffers are sized to hold 10 corners, the function will never return more than 9 corners. * the buffers are sized to hold 10 corners, the function will never return more than 9 corners. So if 10 corners
So if 10 corners are needed, the buffers should be sized for 11 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. * 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
*/ */
/// Finds the corners in the corridor from the position toward the target. (The straightened path.) public int FindCorners(ref List<DtStraightPath> corners, int maxCorners, DtNavMeshQuery navquery, IDtQueryFilter filter)
/// @param[out] cornerVerts The corner vertices. [(x, y, z) * cornerCount] [Size: <= maxCorners] {
/// @param[out] cornerFlags The flag for each corner. [(flag) * cornerCount] [Size: <= maxCorners] var result = navquery.FindStraightPath(m_pos, m_target, m_path, ref corners, maxCorners, 0);
/// @param[out] cornerPolys The polygon reference for each corner. [(polyRef) * cornerCount] if (result.Succeeded())
/// [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(Span<DtStraightPath> corners, int maxCorners, DtNavMeshQuery navquery, IDtQueryFilter filter)
{ {
const float MIN_TARGET_DIST = 0.01f;
int ncorners = 0;
navquery.FindStraightPath(m_pos, m_target, m_path, m_npath, corners, out ncorners, maxCorners, 0);
// Prune points in the beginning of the path which are too close. // Prune points in the beginning of the path which are too close.
while (0 < ncorners) int start = 0;
foreach (DtStraightPath spi in corners)
{ {
if ((corners[0].flags & DtStraightPathFlags.DT_STRAIGHTPATH_OFFMESH_CONNECTION) != 0 || if ((spi.flags & DtStraightPathFlags.DT_STRAIGHTPATH_OFFMESH_CONNECTION) != 0
RcVec.Dist2DSqr(corners[0].pos, m_pos) > RcMath.Sqr(MIN_TARGET_DIST)) || RcVecUtils.Dist2DSqr(spi.pos, m_pos) > MIN_TARGET_DIST)
{ {
break; break;
} }
ncorners--; start++;
if (0 < ncorners)
{
RcSpans.Move(corners, 1, 0, 3);
}
} }
int end = corners.Count;
// Prune points after an off-mesh connection. // Prune points after an off-mesh connection.
for (int i = 0; i < ncorners; ++i) for (int i = start; i < corners.Count; i++)
{ {
if ((corners[i].flags & DtStraightPathFlags.DT_STRAIGHTPATH_OFFMESH_CONNECTION) != 0) DtStraightPath spi = corners[i];
if ((spi.flags & DtStraightPathFlags.DT_STRAIGHTPATH_OFFMESH_CONNECTION) != 0)
{ {
ncorners = i + 1; end = i + 1;
break; break;
} }
} }
corners = corners.GetRange(start, end - start);
}
return ncorners; return corners.Count;
} }
/** /**
@par * Attempts to optimize the path if the specified point is visible from the current position.
*
Inaccurate locomotion or dynamic obstacle avoidance can force the argent position significantly outside the * 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 * 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. * also form near the corners of tiles.
*
This function uses an efficient local visibility search to try to optimize the corridor * This function uses an efficient local visibility search to try to optimize the corridor between the current
between the current position and @p next. * position and @p next.
*
The corridor will change only if @p next is visible from the current position and moving directly toward the point * The corridor will change only if @p next is visible from the current position and moving directly toward the
is better than following the existing path. * point is better than following the existing path.
*
The more inaccurate the agent movement, the more beneficial this function becomes. Simply adjust the frequency * The more inaccurate the agent movement, the more beneficial this function becomes. Simply adjust the frequency of
of the call to match the needs to the agent. * the call to match the needs to the agent.
*
This function is not suitable for long distance searches. * 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.
*/ */
/// 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) public void OptimizePathVisibility(RcVec3f next, float pathOptimizationRange, DtNavMeshQuery navquery, IDtQueryFilter filter)
{ {
// Clamp the ray to max distance. // Clamp the ray to max distance.
float dist = RcVec.Dist2D(m_pos, next); float dist = RcVecUtils.Dist2D(m_pos, next);
// If too close to the goal, do not try to optimize. // If too close to the goal, do not try to optimize.
if (dist < 0.01f) if (dist < 0.01f)
@ -206,52 +187,55 @@ namespace DotRecast.Detour.Crowd
return; 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); dist = Math.Min(dist + 0.01f, pathOptimizationRange);
// Adjust ray length. // Adjust ray length.
var delta = RcVec3f.Subtract(next, m_pos); var delta = RcVec3f.Subtract(next, m_pos);
RcVec3f goal = RcVec.Mad(m_pos, delta, pathOptimizationRange / dist); RcVec3f goal = RcVecUtils.Mad(m_pos, delta, pathOptimizationRange / dist);
var res = new List<long>(); var status = navquery.Raycast(m_path[0], m_pos, goal, filter, 0, 0, out var rayHit);
var status = navquery.Raycast(m_path[0], m_pos, goal, filter, out var t, out var norm, ref res);
if (status.Succeeded()) if (status.Succeeded())
{ {
if (res.Count > 1 && t > 0.99f) if (rayHit.path.Count > 1 && rayHit.t > 0.99f)
{ {
m_npath = DtPathUtils.MergeCorridorStartShortcut(m_path, m_npath, m_maxPath, res, res.Count); m_path = DtPathUtils.MergeCorridorStartShortcut(m_path, rayHit.path);
} }
} }
} }
/** /**
@par * 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 * 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 * 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. * 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 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. * 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.
*
*/ */
/// 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) public bool OptimizePathTopology(DtNavMeshQuery navquery, IDtQueryFilter filter, int maxIterations)
{ {
if (m_npath < 3) if (m_path.Count < 3)
{ {
return false; return false;
} }
var res = new List<long>(); var res = new List<long>();
navquery.InitSlicedFindPath(m_path[0], m_path[^1], m_pos, m_target, filter, 0); navquery.InitSlicedFindPath(m_path[0], m_path[m_path.Count - 1], m_pos, m_target, filter, 0);
navquery.UpdateSlicedFindPath(maxIterations, out var _); navquery.UpdateSlicedFindPath(maxIterations, out var _);
var status = navquery.FinalizeSlicedFindPathPartial(m_path, m_npath, ref res); var status = navquery.FinalizeSlicedFindPathPartial(m_path, ref res);
if (status.Succeeded() && res.Count > 0) if (status.Succeeded() && res.Count > 0)
{ {
m_npath = DtPathUtils.MergeCorridorStartShortcut(m_path, m_npath, m_maxPath, res, res.Count); m_path = DtPathUtils.MergeCorridorStartShortcut(m_path, res);
return true; return true;
} }
@ -263,23 +247,21 @@ namespace DotRecast.Detour.Crowd
// Advance the path up to and over the off-mesh connection. // Advance the path up to and over the off-mesh connection.
long prevRef = 0, polyRef = m_path[0]; long prevRef = 0, polyRef = m_path[0];
int npos = 0; int npos = 0;
while (npos < m_npath && polyRef != offMeshConRef) while (npos < m_path.Count && polyRef != offMeshConRef)
{ {
prevRef = polyRef; prevRef = polyRef;
polyRef = m_path[npos]; polyRef = m_path[npos];
npos++; npos++;
} }
if (npos == m_npath) if (npos == m_path.Count)
{ {
// Could not find offMeshConRef // Could not find offMeshConRef
return false; return false;
} }
// Prune path // Prune path
m_path.RemoveRange(0, npos); m_path = m_path.GetRange(npos, m_path.Count - npos);
m_npath -= npos;
refs[0] = prevRef; refs[0] = prevRef;
refs[1] = polyRef; refs[1] = polyRef;
@ -295,35 +277,36 @@ namespace DotRecast.Detour.Crowd
} }
/** /**
@par * Moves the position from the current location to the desired location, adjusting the corridor as needed to reflect
* the change.
Behavior: *
* 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 movement is constrained to the surface of the navigation mesh. - The corridor is automatically adjusted
- The new position will be located in the adjusted corridor's first polygon. * (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 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. * 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.
*/ */
/// 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) public bool MovePosition(RcVec3f npos, DtNavMeshQuery navquery, IDtQueryFilter filter)
{ {
// Move along navmesh and update new position. // Move along navmesh and update new position.
const int MAX_VISITED = 16; var visited = new List<long>();
Span<long> visited = stackalloc long[MAX_VISITED]; var status = navquery.MoveAlongSurface(m_path[0], m_pos, npos, filter, out var result, ref visited);
var status = navquery.MoveAlongSurface(m_path[0], m_pos, npos, filter, out var result, visited, out var nvisited, MAX_VISITED);
if (status.Succeeded()) if (status.Succeeded())
{ {
m_npath = DtPathUtils.MergeCorridorStartMoved(m_path, m_npath, m_maxPath, visited, nvisited); m_path = DtPathUtils.MergeCorridorStartMoved(m_path, visited);
// Adjust the position to stay on top of the navmesh. // Adjust the position to stay on top of the navmesh.
m_pos = result; m_pos = result;
@ -340,35 +323,30 @@ namespace DotRecast.Detour.Crowd
} }
/** /**
@par * 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
Behavior: * automatically adjusted (shorted or lengthened) in order to remain valid. - The new target will be located in the
* adjusted corridor's last polygon.
- 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 expected use case is that the desired target will be 'near' the current corridor. What is considered 'near'
- The new target will be located in the adjusted corridor's last polygon. * 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.
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. *
* @param npos
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. * 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.
*/ */
/// 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) public bool MoveTargetPosition(RcVec3f npos, DtNavMeshQuery navquery, IDtQueryFilter filter)
{ {
// Move along navmesh and update new position. // Move along navmesh and update new position.
const int MAX_VISITED = 16; var visited = new List<long>();
Span<long> visited = stackalloc long[MAX_VISITED]; var status = navquery.MoveAlongSurface(m_path[m_path.Count - 1], m_target, npos, filter, out var result, ref visited);
int nvisited = 0;
var status = navquery.MoveAlongSurface(m_path[^1], m_target, npos, filter, out var result, visited, out nvisited, MAX_VISITED);
if (status.Succeeded()) if (status.Succeeded())
{ {
m_npath = DtPathUtils.MergeCorridorEndMoved(m_path, m_npath, m_maxPath, visited, nvisited); m_path = DtPathUtils.MergeCorridorEndMoved(m_path, visited);
// TODO: should we do that? // TODO: should we do that?
// Adjust the position to stay on top of the navmesh. // Adjust the position to stay on top of the navmesh.
/* /*
@ -382,45 +360,38 @@ namespace DotRecast.Detour.Crowd
return false; return false;
} }
/// @par /**
/// * Loads a new path and target into the corridor. The current corridor position is expected to be within the first
/// The current corridor position is expected to be within the first polygon in the path. The target * polygon in the path. The target is expected to be in the last polygon.
/// 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().
/// @warning The size of the path must not exceed the size of corridor's path buffer set during #init(). * @param target
/// Loads a new path and target into the corridor. * The target location within the last polygon of the path. [(x, y, z)]
/// @param[in] target The target location within the last polygon of the path. [(x, y, z)] * @param path
/// @param[in] path The path corridor. [(polyRef) * @p npolys] * The path corridor.
/// @param[in] npath The number of polygons in the path. */
public void SetCorridor(RcVec3f target, List<long> path) public void SetCorridor(RcVec3f target, List<long> path)
{ {
m_target = target; m_target = target;
if(path != m_path) m_path = new List<long>(path);
{
m_path.Clear();
m_path.AddRange(path);
}
m_npath = path.Count;
} }
public void FixPathStart(long safeRef, RcVec3f safePos) public void FixPathStart(long safeRef, RcVec3f safePos)
{ {
m_pos = safePos; m_pos = safePos;
if (m_npath < 3 && m_npath > 0) if (m_path.Count < 3 && m_path.Count > 0)
{ {
long p = m_path[m_npath - 1]; long p = m_path[m_path.Count - 1];
m_path.Clear(); m_path.Clear();
m_path.Add(safeRef); m_path.Add(safeRef);
m_path.Add(0L); m_path.Add(0L);
m_path.Add(p); m_path.Add(p);
m_npath = 3;
} }
else else
{ {
m_path.Clear(); m_path.Clear();
m_path.Add(safeRef); m_path.Add(safeRef);
m_path.Add(0L); m_path.Add(0L);
m_npath = 2;
} }
} }
@ -428,12 +399,12 @@ namespace DotRecast.Detour.Crowd
{ {
// Keep valid path as far as possible. // Keep valid path as far as possible.
int n = 0; int n = 0;
while (n < m_npath && navquery.IsValidPolyRef(m_path[n], filter)) while (n < m_path.Count && navquery.IsValidPolyRef(m_path[n], filter))
{ {
n++; n++;
} }
if (m_npath == n) if (m_path.Count == n)
{ {
// All valid, no need to fix. // All valid, no need to fix.
return true; return true;
@ -441,36 +412,38 @@ namespace DotRecast.Detour.Crowd
else if (n == 0) else if (n == 0)
{ {
// The first polyref is bad, use current safe values. // The first polyref is bad, use current safe values.
m_pos = new RcVec3f(safePos); m_pos = RcVecUtils.Create(safePos);
m_path.Clear(); m_path.Clear();
m_path.Add(safeRef); m_path.Add(safeRef);
m_npath = 1;
} }
else if (n < m_npath) else if (n < m_path.Count)
{ {
// The path is partially usable. // The path is partially usable.
m_path.RemoveRange(n, m_path.Count - n); m_path = m_path.GetRange(0, n);
m_npath = n;
} }
// Clamp target pos to last poly // Clamp target pos to last poly
navquery.ClosestPointOnPolyBoundary(m_path[m_npath - 1], m_target, out m_target); navquery.ClosestPointOnPolyBoundary(m_path[m_path.Count - 1], m_target, out m_target);
return true; return true;
} }
/// @par /**
/// * Checks the current corridor path to see if its polygon references remain valid. The path can be invalidated if
/// The path can be invalidated if there are structural changes to the underlying navigation mesh, or the state of * there are structural changes to the underlying navigation mesh, or the state of a polygon within the path changes
/// a polygon within the path changes resulting in it being filtered out. (E.g. An exclusion or inclusion flag 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 maxLookAhead
/// @param[in] maxLookAhead The number of polygons from the beginning of the corridor to search. * The number of polygons from the beginning of the corridor to search.
/// @param[in] navquery The query object used to build the corridor. * @param navquery
/// @param[in] filter The filter to apply to the operation. * The query object used to build the corridor.
* @param filter
* The filter to apply to the operation.
* @return
*/
public bool IsValid(int maxLookAhead, DtNavMeshQuery navquery, IDtQueryFilter filter) public bool IsValid(int maxLookAhead, DtNavMeshQuery navquery, IDtQueryFilter filter)
{ {
// Check that all polygons still pass query filter. // Check that all polygons still pass query filter.
int n = Math.Min(m_npath, maxLookAhead); int n = Math.Min(m_path.Count, maxLookAhead);
for (int i = 0; i < n; ++i) for (int i = 0; i < n; ++i)
{ {
if (!navquery.IsValidPolyRef(m_path[i], filter)) if (!navquery.IsValidPolyRef(m_path[i], filter))
@ -482,46 +455,62 @@ namespace DotRecast.Detour.Crowd
return true; 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() public RcVec3f GetPos()
{ {
return m_pos; 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() public RcVec3f GetTarget()
{ {
return m_target; 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() public long GetFirstPoly()
{ {
return 0 == m_npath ? 0 : m_path[0]; 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() public long GetLastPoly()
{ {
return 0 == m_npath ? 0 : m_path[m_npath - 1]; return 0 == m_path.Count ? 0 : m_path[m_path.Count - 1];
} }
/// The corridor's path. /**
/// @return The corridor's path. [(polyRef) * #getPathCount()] * The corridor's path.
*/
public List<long> GetPath() public List<long> GetPath()
{ {
return m_path; 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() public int GetPathCount()
{ {
return m_npath; return m_path.Count;
} }
} }
} }

View File

@ -1,7 +1,7 @@
/* /*
Copyright (c) 2009-2010 Mikko Mononen memon@inside.org Copyright (c) 2009-2010 Mikko Mononen memon@inside.org
recast4j copyright (c) 2015-2019 Piotr Piastucki piotr@jtilia.org recast4j copyright (c) 2015-2019 Piotr Piastucki piotr@jtilia.org
DotRecast Copyright (c) 2023-2024 Choi Ikpil ikpil@naver.com DotRecast Copyright (c) 2023 Choi Ikpil ikpil@naver.com
This software is provided 'as-is', without any express or implied This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages warranty. In no event will the authors be held liable for any damages

View File

@ -1,6 +1,6 @@
/* /*
recast4j copyright (c) 2021 Piotr Piastucki piotr@jtilia.org recast4j copyright (c) 2021 Piotr Piastucki piotr@jtilia.org
DotRecast Copyright (c) 2023-2024 Choi Ikpil ikpil@naver.com DotRecast Copyright (c) 2023 Choi Ikpil ikpil@naver.com
This software is provided 'as-is', without any express or implied This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages warranty. In no event will the authors be held liable for any damages

View File

@ -1,7 +1,7 @@
/* /*
Copyright (c) 2009-2010 Mikko Mononen memon@inside.org Copyright (c) 2009-2010 Mikko Mononen memon@inside.org
recast4j copyright (c) 2015-2019 Piotr Piastucki piotr@jtilia.org recast4j copyright (c) 2015-2019 Piotr Piastucki piotr@jtilia.org
DotRecast Copyright (c) 2023-2024 Choi Ikpil ikpil@naver.com DotRecast Copyright (c) 2023 Choi Ikpil ikpil@naver.com
This software is provided 'as-is', without any express or implied This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages warranty. In no event will the authors be held liable for any damages
@ -26,29 +26,28 @@ namespace DotRecast.Detour.Crowd
{ {
public class DtPathQueue public class DtPathQueue
{ {
private readonly DtCrowdConfig m_config; private readonly DtCrowdConfig config;
private readonly LinkedList<DtPathQuery> m_queue; private readonly LinkedList<DtPathQuery> queue = new LinkedList<DtPathQuery>();
public DtPathQueue(DtCrowdConfig config) public DtPathQueue(DtCrowdConfig config)
{ {
m_config = config; this.config = config;
m_queue = new LinkedList<DtPathQuery>();
} }
public void Update(DtNavMesh navMesh) public void Update(DtNavMesh navMesh)
{ {
// Update path request until there is nothing to update // Update path request until there is nothing to update or up to maxIters pathfinder iterations has been
// or upto maxIters pathfinder iterations has been consumed. // consumed.
int iterCount = m_config.maxFindPathIterations; int iterCount = config.maxFindPathIterations;
while (iterCount > 0) while (iterCount > 0)
{ {
DtPathQuery q = m_queue.First?.Value; DtPathQuery q = queue.First?.Value;
if (q == null) if (q == null)
{ {
break; break;
} }
m_queue.RemoveFirst(); queue.RemoveFirst();
// Handle query start. // Handle query start.
if (q.result.status.IsEmpty()) if (q.result.status.IsEmpty())
@ -71,14 +70,14 @@ namespace DotRecast.Detour.Crowd
if (!(q.result.status.Failed() || q.result.status.Succeeded())) if (!(q.result.status.Failed() || q.result.status.Succeeded()))
{ {
m_queue.AddFirst(q); queue.AddFirst(q);
} }
} }
} }
public DtPathQueryResult Request(long startRef, long endRef, RcVec3f startPos, RcVec3f endPos, IDtQueryFilter filter) public DtPathQueryResult Request(long startRef, long endRef, RcVec3f startPos, RcVec3f endPos, IDtQueryFilter filter)
{ {
if (m_queue.Count >= m_config.pathQueueSize) if (queue.Count >= config.pathQueueSize)
{ {
return null; return null;
} }
@ -89,7 +88,7 @@ namespace DotRecast.Detour.Crowd
q.endPos = endPos; q.endPos = endPos;
q.endRef = endRef; q.endRef = endRef;
q.filter = filter; q.filter = filter;
m_queue.AddLast(q); queue.AddLast(q);
return q.result; return q.result;
} }
} }

View File

@ -1,7 +1,7 @@
/* /*
Copyright (c) 2009-2010 Mikko Mononen memon@inside.org Copyright (c) 2009-2010 Mikko Mononen memon@inside.org
recast4j copyright (c) 2015-2019 Piotr Piastucki piotr@jtilia.org recast4j copyright (c) 2015-2019 Piotr Piastucki piotr@jtilia.org
DotRecast Copyright (c) 2023-2024 Choi Ikpil ikpil@naver.com DotRecast Copyright (c) 2023 Choi Ikpil ikpil@naver.com
This software is provided 'as-is', without any express or implied This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages warranty. In no event will the authors be held liable for any damages
@ -22,7 +22,6 @@ using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using DotRecast.Core.Buffers;
namespace DotRecast.Detour.Crowd namespace DotRecast.Detour.Crowd
{ {
@ -31,14 +30,12 @@ namespace DotRecast.Detour.Crowd
private readonly float _cellSize; private readonly float _cellSize;
private readonly float _invCellSize; private readonly float _invCellSize;
private readonly Dictionary<long, List<DtCrowdAgent>> _items; private readonly Dictionary<long, List<DtCrowdAgent>> _items;
private readonly RcObjectPool<List<DtCrowdAgent>> _listPool;
public DtProximityGrid(float cellSize) public DtProximityGrid(float cellSize)
{ {
_cellSize = cellSize; _cellSize = cellSize;
_invCellSize = 1.0f / cellSize; _invCellSize = 1.0f / cellSize;
_items = new Dictionary<long, List<DtCrowdAgent>>(); _items = new Dictionary<long, List<DtCrowdAgent>>();
_listPool = new RcObjectPool<List<DtCrowdAgent>>(() => new List<DtCrowdAgent>());
} }
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
@ -60,8 +57,6 @@ namespace DotRecast.Detour.Crowd
public void Clear() public void Clear()
{ {
foreach (var pair in _items)
_listPool.Return(pair.Value);
_items.Clear(); _items.Clear();
} }
@ -79,8 +74,7 @@ namespace DotRecast.Detour.Crowd
long key = CombineKey(x, y); long key = CombineKey(x, y);
if (!_items.TryGetValue(key, out var ids)) if (!_items.TryGetValue(key, out var ids))
{ {
ids = _listPool.Get(); ids = new List<DtCrowdAgent>();
ids.Clear();
_items.Add(key, ids); _items.Add(key, ids);
} }
@ -89,51 +83,30 @@ namespace DotRecast.Detour.Crowd
} }
} }
public int QueryItems(float minx, float miny, float maxx, float maxy, Span<int> ids, int maxIds) // 해당 셀 사이즈의 크기로 x ~ y 영역을 찾아, 군집 에이전트를 가져오는 코드
public int QueryItems(float minx, float miny, float maxx, float maxy, ref HashSet<DtCrowdAgent> result)
{ {
int iminx = (int)MathF.Floor(minx * _invCellSize); int iminx = (int)MathF.Floor(minx * _invCellSize);
int iminy = (int)MathF.Floor(miny * _invCellSize); int iminy = (int)MathF.Floor(miny * _invCellSize);
int imaxx = (int)MathF.Floor(maxx * _invCellSize); int imaxx = (int)MathF.Floor(maxx * _invCellSize);
int imaxy = (int)MathF.Floor(maxy * _invCellSize); int imaxy = (int)MathF.Floor(maxy * _invCellSize);
int n = 0;
for (int y = iminy; y <= imaxy; ++y) for (int y = iminy; y <= imaxy; ++y)
{ {
for (int x = iminx; x <= imaxx; ++x) for (int x = iminx; x <= imaxx; ++x)
{ {
long key = CombineKey(x, y); long key = CombineKey(x, y);
bool hasPool = _items.TryGetValue(key, out var pool); if (_items.TryGetValue(key, out var ids))
if (!hasPool)
{ {
continue; for (int i = 0; i < ids.Count; ++i)
}
for (int idx = 0; idx < pool.Count; ++idx)
{ {
var item = pool[idx]; result.Add(ids[i]);
// Check if the id exists already.
int end = n;
int i = 0;
while (i != end && ids[i] != item.idx)
{
++i;
}
// Item not found, add it.
if (i == n)
{
ids[n++] = item.idx;
if (n >= maxIds)
return n;
} }
} }
} }
} }
return n; return result.Count;
} }
public IEnumerable<(long, int)> GetItemCounts() public IEnumerable<(long, int)> GetItemCounts()

View File

@ -1,4 +1,4 @@
using DotRecast.Core.Numerics; using DotRecast.Core.Numerics;
namespace DotRecast.Detour.Crowd namespace DotRecast.Detour.Crowd
{ {

View File

@ -1,6 +1,6 @@
/* /*
recast4j copyright (c) 2021 Piotr Piastucki piotr@jtilia.org recast4j copyright (c) 2021 Piotr Piastucki piotr@jtilia.org
DotRecast Copyright (c) 2023-2024 Choi Ikpil ikpil@naver.com DotRecast Copyright (c) 2023 Choi Ikpil ikpil@naver.com
This software is provided 'as-is', without any express or implied This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages warranty. In no event will the authors be held liable for any damages
@ -62,10 +62,10 @@ namespace DotRecast.Detour.Dynamic.Colliders
return bounds; return bounds;
} }
public override void Rasterize(RcHeightfield hf, RcContext context) public override void Rasterize(RcHeightfield hf, RcTelemetry telemetry)
{ {
RcFilledVolumeRasterization.RasterizeBox( RcFilledVolumeRasterization.RasterizeBox(
hf, center, halfEdges, area, (int)MathF.Floor(flagMergeThreshold / hf.ch), context); hf, center, halfEdges, area, (int)MathF.Floor(flagMergeThreshold / hf.ch), telemetry);
} }
public static RcVec3f[] GetHalfEdges(RcVec3f up, RcVec3f forward, RcVec3f extent) public static RcVec3f[] GetHalfEdges(RcVec3f up, RcVec3f forward, RcVec3f extent)

View File

@ -1,6 +1,6 @@
/* /*
recast4j copyright (c) 2021 Piotr Piastucki piotr@jtilia.org recast4j copyright (c) 2021 Piotr Piastucki piotr@jtilia.org
DotRecast Copyright (c) 2023-2024 Choi Ikpil ikpil@naver.com DotRecast Copyright (c) 2023 Choi Ikpil ikpil@naver.com
This software is provided 'as-is', without any express or implied This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages warranty. In no event will the authors be held liable for any damages
@ -38,9 +38,9 @@ namespace DotRecast.Detour.Dynamic.Colliders
this.radius = radius; this.radius = radius;
} }
public override void Rasterize(RcHeightfield hf, RcContext context) public override void Rasterize(RcHeightfield hf, RcTelemetry telemetry)
{ {
RcFilledVolumeRasterization.RasterizeCapsule(hf, start, end, radius, area, (int)MathF.Floor(flagMergeThreshold / hf.ch), context); RcFilledVolumeRasterization.RasterizeCapsule(hf, start, end, radius, area, (int)MathF.Floor(flagMergeThreshold / hf.ch), telemetry);
} }
private static float[] Bounds(RcVec3f start, RcVec3f end, float radius) private static float[] Bounds(RcVec3f start, RcVec3f end, float radius)

View File

@ -1,6 +1,6 @@
/* /*
recast4j copyright (c) 2021 Piotr Piastucki piotr@jtilia.org recast4j copyright (c) 2021 Piotr Piastucki piotr@jtilia.org
DotRecast Copyright (c) 2023-2024 Choi Ikpil ikpil@naver.com DotRecast Copyright (c) 2023 Choi Ikpil ikpil@naver.com
This software is provided 'as-is', without any express or implied This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages warranty. In no event will the authors be held liable for any damages
@ -40,6 +40,6 @@ namespace DotRecast.Detour.Dynamic.Colliders
return _bounds; return _bounds;
} }
public abstract void Rasterize(RcHeightfield hf, RcContext context); public abstract void Rasterize(RcHeightfield hf, RcTelemetry telemetry);
} }
} }

View File

@ -1,6 +1,6 @@
/* /*
recast4j copyright (c) 2021 Piotr Piastucki piotr@jtilia.org recast4j copyright (c) 2021 Piotr Piastucki piotr@jtilia.org
DotRecast Copyright (c) 2023-2024 Choi Ikpil ikpil@naver.com DotRecast Copyright (c) 2023 Choi Ikpil ikpil@naver.com
This software is provided 'as-is', without any express or implied This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages warranty. In no event will the authors be held liable for any damages
@ -68,10 +68,10 @@ namespace DotRecast.Detour.Dynamic.Colliders
return bounds; return bounds;
} }
public void Rasterize(RcHeightfield hf, RcContext context) public void Rasterize(RcHeightfield hf, RcTelemetry telemetry)
{ {
foreach (var c in colliders) foreach (var c in colliders)
c.Rasterize(hf, context); c.Rasterize(hf, telemetry);
} }
} }
} }

View File

@ -1,6 +1,6 @@
/* /*
recast4j copyright (c) 2021 Piotr Piastucki piotr@jtilia.org recast4j copyright (c) 2021 Piotr Piastucki piotr@jtilia.org
DotRecast Copyright (c) 2023-2024 Choi Ikpil ikpil@naver.com DotRecast Copyright (c) 2023 Choi Ikpil ikpil@naver.com
This software is provided 'as-is', without any express or implied This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages warranty. In no event will the authors be held liable for any damages
@ -42,10 +42,10 @@ namespace DotRecast.Detour.Dynamic.Colliders
this.triangles = triangles; this.triangles = triangles;
} }
public override void Rasterize(RcHeightfield hf, RcContext context) public override void Rasterize(RcHeightfield hf, RcTelemetry telemetry)
{ {
RcFilledVolumeRasterization.RasterizeConvex(hf, vertices, triangles, area, RcFilledVolumeRasterization.RasterizeConvex(hf, vertices, triangles, area,
(int)MathF.Floor(flagMergeThreshold / hf.ch), context); (int)MathF.Floor(flagMergeThreshold / hf.ch), telemetry);
} }
} }
} }

View File

@ -1,6 +1,6 @@
/* /*
recast4j copyright (c) 2021 Piotr Piastucki piotr@jtilia.org recast4j copyright (c) 2021 Piotr Piastucki piotr@jtilia.org
DotRecast Copyright (c) 2023-2024 Choi Ikpil ikpil@naver.com DotRecast Copyright (c) 2023 Choi Ikpil ikpil@naver.com
This software is provided 'as-is', without any express or implied This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages warranty. In no event will the authors be held liable for any damages
@ -38,10 +38,10 @@ namespace DotRecast.Detour.Dynamic.Colliders
this.radius = radius; this.radius = radius;
} }
public override void Rasterize(RcHeightfield hf, RcContext context) public override void Rasterize(RcHeightfield hf, RcTelemetry telemetry)
{ {
RcFilledVolumeRasterization.RasterizeCylinder(hf, start, end, radius, area, (int)MathF.Floor(flagMergeThreshold / hf.ch), RcFilledVolumeRasterization.RasterizeCylinder(hf, start, end, radius, area, (int)MathF.Floor(flagMergeThreshold / hf.ch),
context); telemetry);
} }
private static float[] Bounds(RcVec3f start, RcVec3f end, float radius) private static float[] Bounds(RcVec3f start, RcVec3f end, float radius)

View File

@ -1,6 +1,6 @@
/* /*
recast4j copyright (c) 2021 Piotr Piastucki piotr@jtilia.org recast4j copyright (c) 2021 Piotr Piastucki piotr@jtilia.org
DotRecast Copyright (c) 2023-2024 Choi Ikpil ikpil@naver.com DotRecast Copyright (c) 2023 Choi Ikpil ikpil@naver.com
This software is provided 'as-is', without any express or implied This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages warranty. In no event will the authors be held liable for any damages
@ -36,10 +36,10 @@ namespace DotRecast.Detour.Dynamic.Colliders
this.radius = radius; this.radius = radius;
} }
public override void Rasterize(RcHeightfield hf, RcContext context) public override void Rasterize(RcHeightfield hf, RcTelemetry telemetry)
{ {
RcFilledVolumeRasterization.RasterizeSphere(hf, center, radius, area, (int)MathF.Floor(flagMergeThreshold / hf.ch), RcFilledVolumeRasterization.RasterizeSphere(hf, center, radius, area, (int)MathF.Floor(flagMergeThreshold / hf.ch),
context); telemetry);
} }
private static float[] Bounds(RcVec3f center, float radius) private static float[] Bounds(RcVec3f center, float radius)

View File

@ -1,6 +1,6 @@
/* /*
recast4j copyright (c) 2021 Piotr Piastucki piotr@jtilia.org recast4j copyright (c) 2021 Piotr Piastucki piotr@jtilia.org
DotRecast Copyright (c) 2023-2024 Choi Ikpil ikpil@naver.com DotRecast Copyright (c) 2023 Choi Ikpil ikpil@naver.com
This software is provided 'as-is', without any express or implied This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages warranty. In no event will the authors be held liable for any damages
@ -58,12 +58,12 @@ namespace DotRecast.Detour.Dynamic.Colliders
return bounds; return bounds;
} }
public override void Rasterize(RcHeightfield hf, RcContext context) public override void Rasterize(RcHeightfield hf, RcTelemetry telemetry)
{ {
for (int i = 0; i < triangles.Length; i += 3) for (int i = 0; i < triangles.Length; i += 3)
{ {
RcRasterizations.RasterizeTriangle(context, vertices, triangles[i], triangles[i + 1], triangles[i + 2], area, RcRasterizations.RasterizeTriangle(hf, vertices, triangles[i], triangles[i + 1], triangles[i + 2], area,
hf, (int)MathF.Floor(flagMergeThreshold / hf.ch)); (int)MathF.Floor(flagMergeThreshold / hf.ch), telemetry);
} }
} }
} }

View File

@ -1,6 +1,6 @@
/* /*
recast4j copyright (c) 2021 Piotr Piastucki piotr@jtilia.org recast4j copyright (c) 2021 Piotr Piastucki piotr@jtilia.org
DotRecast Copyright (c) 2023-2024 Choi Ikpil ikpil@naver.com DotRecast Copyright (c) 2023 Choi Ikpil ikpil@naver.com
This software is provided 'as-is', without any express or implied This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages warranty. In no event will the authors be held liable for any damages
@ -25,6 +25,6 @@ namespace DotRecast.Detour.Dynamic.Colliders
public interface IDtCollider public interface IDtCollider
{ {
float[] Bounds(); float[] Bounds();
void Rasterize(RcHeightfield hf, RcContext context); void Rasterize(RcHeightfield hf, RcTelemetry telemetry);
} }
} }

View File

@ -1,18 +0,0 @@
{
"name": "DotRecast.Detour.Dynamic",
"rootNamespace": "DotRecast.Detour.Dynamic",
"references": [
"DotRecast.Detour",
"DotRecast.Recast",
"DotRecast.Core"
],
"includePlatforms": [],
"excludePlatforms": [],
"allowUnsafeCode": false,
"overrideReferences": false,
"precompiledReferences": [],
"autoReferenced": true,
"defineConstraints": [],
"versionDefines": [],
"noEngineReferences": true
}

View File

@ -1,16 +1,15 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<TargetFrameworks>netstandard2.1;net6.0;net7.0;net8.0</TargetFrameworks> <TargetFrameworks>netstandard2.1;net6.0;net7.0</TargetFrameworks>
<PackageId>DotRecast.Detour.Dynamic</PackageId> <PackageId>DotRecast.Detour.Dynamic</PackageId>
<PackageReadmeFile>README.md</PackageReadmeFile> <PackageReadmeFile>README.md</PackageReadmeFile>
<Authors>ikpil</Authors> <Authors>ikpil</Authors>
<Description>DotRecast - a port of Recast Detour, Industry-standard navigation mesh toolset for .NET, C#, Unity3D, games, servers</Description> <Description>DotRecast - a port of Recast Detour, navigation mesh toolset for games, Unity3D, servers, C#</Description>
<RepositoryType>git</RepositoryType> <RepositoryType>git</RepositoryType>
<PackageProjectUrl>https://github.com/ikpil/DotRecast</PackageProjectUrl> <PackageProjectUrl>https://github.com/ikpil/DotRecast</PackageProjectUrl>
<RepositoryUrl>https://github.com/ikpil/DotRecast</RepositoryUrl> <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> <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> </PropertyGroup>
<ItemGroup> <ItemGroup>

View File

@ -1,6 +1,6 @@
/* /*
recast4j copyright (c) 2021 Piotr Piastucki piotr@jtilia.org recast4j copyright (c) 2021 Piotr Piastucki piotr@jtilia.org
DotRecast Copyright (c) 2023-2024 Choi Ikpil ikpil@naver.com DotRecast Copyright (c) 2023 Choi Ikpil ikpil@naver.com
This software is provided 'as-is', without any express or implied This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages warranty. In no event will the authors be held liable for any damages
@ -23,7 +23,6 @@ using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using DotRecast.Core; using DotRecast.Core;
using DotRecast.Core.Collections;
using DotRecast.Detour.Dynamic.Colliders; using DotRecast.Detour.Dynamic.Colliders;
using DotRecast.Detour.Dynamic.Io; using DotRecast.Detour.Dynamic.Io;
using DotRecast.Recast; using DotRecast.Recast;
@ -36,12 +35,12 @@ namespace DotRecast.Detour.Dynamic
public readonly DtDynamicNavMeshConfig config; public readonly DtDynamicNavMeshConfig config;
private readonly RcBuilder builder; private readonly RcBuilder builder;
private readonly Dictionary<long, DtDynamicTile> _tiles = new Dictionary<long, DtDynamicTile>(); private readonly Dictionary<long, DtDynamicTile> _tiles = new Dictionary<long, DtDynamicTile>();
private readonly RcContext _context; private readonly RcTelemetry telemetry;
private readonly DtNavMeshParams navMeshParams; private readonly DtNavMeshParams navMeshParams;
private readonly BlockingCollection<IDtDaynmicTileJob> updateQueue = new BlockingCollection<IDtDaynmicTileJob>(); private readonly BlockingCollection<IDtDaynmicTileJob> updateQueue = new BlockingCollection<IDtDaynmicTileJob>();
private readonly RcAtomicLong currentColliderId = new RcAtomicLong(0); private readonly RcAtomicLong currentColliderId = new RcAtomicLong(0);
private DtNavMesh _navMesh; private DtNavMesh _navMesh;
private bool _dirty = true; private bool dirty = true;
public DtDynamicNavMesh(DtVoxelFile voxelFile) public DtDynamicNavMesh(DtVoxelFile voxelFile)
{ {
@ -63,8 +62,8 @@ namespace DotRecast.Detour.Dynamic
navMeshParams.orig.X = voxelFile.bounds[0]; navMeshParams.orig.X = voxelFile.bounds[0];
navMeshParams.orig.Y = voxelFile.bounds[1]; navMeshParams.orig.Y = voxelFile.bounds[1];
navMeshParams.orig.Z = voxelFile.bounds[2]; navMeshParams.orig.Z = voxelFile.bounds[2];
navMeshParams.tileWidth = voxelFile.useTiles ? voxelFile.cellSize * voxelFile.tileSizeX : voxelFile.bounds[3] - voxelFile.bounds[0]; navMeshParams.tileWidth = voxelFile.cellSize * voxelFile.tileSizeX;
navMeshParams.tileHeight = voxelFile.useTiles ? voxelFile.cellSize * voxelFile.tileSizeZ : voxelFile.bounds[5] - voxelFile.bounds[2]; navMeshParams.tileHeight = voxelFile.cellSize * voxelFile.tileSizeZ;
navMeshParams.maxTiles = voxelFile.tiles.Count; navMeshParams.maxTiles = voxelFile.tiles.Count;
navMeshParams.maxPolys = 0x8000; navMeshParams.maxPolys = 0x8000;
foreach (var t in voxelFile.tiles) foreach (var t in voxelFile.tiles)
@ -73,7 +72,7 @@ namespace DotRecast.Detour.Dynamic
} }
; ;
_context = new RcContext(); telemetry = new RcTelemetry();
} }
public DtNavMesh NavMesh() public DtNavMesh NavMesh()
@ -106,6 +105,29 @@ namespace DotRecast.Detour.Dynamic
updateQueue.Add(new DtDynamicTileColliderRemovalJob(colliderId, GetTilesByCollider(colliderId))); updateQueue.Add(new DtDynamicTileColliderRemovalJob(colliderId, GetTilesByCollider(colliderId)));
} }
/**
* Perform full build of the nav mesh
*/
public void Build()
{
ProcessQueue();
Rebuild(_tiles.Values);
}
/**
* Perform incremental update of the nav mesh
*/
public bool Update()
{
return Rebuild(ProcessQueue());
}
private bool Rebuild(ICollection<DtDynamicTile> stream)
{
foreach (var dynamicTile in stream)
Rebuild(dynamicTile);
return UpdateNavMesh();
}
private HashSet<DtDynamicTile> ProcessQueue() private HashSet<DtDynamicTile> ProcessQueue()
{ {
@ -137,49 +159,27 @@ namespace DotRecast.Detour.Dynamic
} }
} }
// Perform full build of the navmesh /**
public void Build() * Perform full build concurrently using the given {@link ExecutorService}
{ */
ProcessQueue(); public Task<bool> Build(TaskFactory executor)
Rebuild(_tiles.Values);
}
// Perform full build concurrently using the given {@link ExecutorService}
public bool Build(TaskFactory executor)
{ {
ProcessQueue(); ProcessQueue();
return Rebuild(_tiles.Values, executor); return Rebuild(_tiles.Values, executor);
} }
/**
// Perform incremental update of the navmesh * Perform incremental update concurrently using the given {@link ExecutorService}
public bool Update() */
{ public Task<bool> Update(TaskFactory executor)
return Rebuild(ProcessQueue());
}
// Perform incremental update concurrently using the given {@link ExecutorService}
public bool Update(TaskFactory executor)
{ {
return Rebuild(ProcessQueue(), executor); return Rebuild(ProcessQueue(), executor);
} }
private bool Rebuild(ICollection<DtDynamicTile> tiles) private Task<bool> Rebuild(ICollection<DtDynamicTile> tiles, TaskFactory executor)
{ {
foreach (var tile in tiles) var tasks = tiles.Select(tile => executor.StartNew(() => Rebuild(tile))).ToArray();
Rebuild(tile); return Task.WhenAll(tasks).ContinueWith(k => UpdateNavMesh());
return UpdateNavMesh();
}
private bool Rebuild(ICollection<DtDynamicTile> tiles, TaskFactory executor)
{
var tasks = tiles
.Select(tile => executor.StartNew(() => Rebuild(tile)))
.ToArray();
Task.WaitAll(tasks);
return UpdateNavMesh();
} }
private ICollection<DtDynamicTile> GetTiles(float[] bounds) private ICollection<DtDynamicTile> GetTiles(float[] bounds)
@ -189,17 +189,17 @@ namespace DotRecast.Detour.Dynamic
return _tiles.Values; return _tiles.Values;
} }
int minx = (int)MathF.Floor((bounds[0] - navMeshParams.orig.X) / navMeshParams.tileWidth) - 1; int minx = (int)MathF.Floor((bounds[0] - navMeshParams.orig.X) / navMeshParams.tileWidth);
int minz = (int)MathF.Floor((bounds[2] - navMeshParams.orig.Z) / navMeshParams.tileHeight) - 1; int minz = (int)MathF.Floor((bounds[2] - navMeshParams.orig.Z) / navMeshParams.tileHeight);
int maxx = (int)MathF.Floor((bounds[3] - navMeshParams.orig.X) / navMeshParams.tileWidth) + 1; int maxx = (int)MathF.Floor((bounds[3] - navMeshParams.orig.X) / navMeshParams.tileWidth);
int maxz = (int)MathF.Floor((bounds[5] - navMeshParams.orig.Z) / navMeshParams.tileHeight) + 1; int maxz = (int)MathF.Floor((bounds[5] - navMeshParams.orig.Z) / navMeshParams.tileHeight);
List<DtDynamicTile> tiles = new List<DtDynamicTile>(); List<DtDynamicTile> tiles = new List<DtDynamicTile>();
for (int z = minz; z <= maxz; ++z) for (int z = minz; z <= maxz; ++z)
{ {
for (int x = minx; x <= maxx; ++x) for (int x = minx; x <= maxx; ++x)
{ {
DtDynamicTile tile = GetTileAt(x, z); DtDynamicTile tile = GetTileAt(x, z);
if (tile != null && IntersectsXZ(tile, bounds)) if (tile != null)
{ {
tiles.Add(tile); tiles.Add(tile);
} }
@ -209,12 +209,6 @@ namespace DotRecast.Detour.Dynamic
return tiles; return tiles;
} }
private bool IntersectsXZ(DtDynamicTile tile, float[] bounds)
{
return tile.voxelTile.boundsMin.X <= bounds[3] && tile.voxelTile.boundsMax.X >= bounds[0] &&
tile.voxelTile.boundsMin.Z <= bounds[5] && tile.voxelTile.boundsMax.Z >= bounds[2];
}
private List<DtDynamicTile> GetTilesByCollider(long cid) private List<DtDynamicTile> GetTilesByCollider(long cid)
{ {
return _tiles.Values.Where(t => t.ContainsCollider(cid)).ToList(); return _tiles.Values.Where(t => t.ContainsCollider(cid)).ToList();
@ -224,24 +218,19 @@ namespace DotRecast.Detour.Dynamic
{ {
DtNavMeshCreateParams option = new DtNavMeshCreateParams(); DtNavMeshCreateParams option = new DtNavMeshCreateParams();
option.walkableHeight = config.walkableHeight; option.walkableHeight = config.walkableHeight;
_dirty = _dirty | tile.Build(builder, config, _context); dirty = dirty | tile.Build(builder, config, telemetry);
} }
private bool UpdateNavMesh() private bool UpdateNavMesh()
{ {
if (_dirty) if (dirty)
{ {
_dirty = false; DtNavMesh navMesh = new DtNavMesh(navMeshParams, MAX_VERTS_PER_POLY);
DtNavMesh navMesh = new DtNavMesh();
navMesh.Init(navMeshParams, MAX_VERTS_PER_POLY);
foreach (var t in _tiles.Values) foreach (var t in _tiles.Values)
{
t.AddTo(navMesh); t.AddTo(navMesh);
}
_navMesh = navMesh; this._navMesh = navMesh;
dirty = false;
return true; return true;
} }
@ -269,21 +258,5 @@ namespace DotRecast.Detour.Dynamic
{ {
return _tiles.Values.Select(t => t.recastResult).ToList(); return _tiles.Values.Select(t => t.recastResult).ToList();
} }
public void NavMesh(DtNavMesh mesh)
{
_tiles.Values.ForEach(t =>
{
const int MAX_NEIS = 32;
DtMeshTile[] tiles = new DtMeshTile[MAX_NEIS];
int nneis = mesh.GetTilesAt(t.voxelTile.tileX, t.voxelTile.tileZ, tiles, MAX_NEIS);
if (0 < nneis)
{
t.SetMeshData(tiles[0].data);
}
});
_navMesh = mesh;
_dirty = false;
}
} }
} }

View File

@ -1,6 +1,6 @@
/* /*
recast4j copyright (c) 2021 Piotr Piastucki piotr@jtilia.org recast4j copyright (c) 2021 Piotr Piastucki piotr@jtilia.org
DotRecast Copyright (c) 2023-2024 Choi Ikpil ikpil@naver.com DotRecast Copyright (c) 2023 Choi Ikpil ikpil@naver.com
This software is provided 'as-is', without any express or implied This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages warranty. In no event will the authors be held liable for any damages

View File

@ -1,6 +1,6 @@
/* /*
recast4j copyright (c) 2021 Piotr Piastucki piotr@jtilia.org recast4j copyright (c) 2021 Piotr Piastucki piotr@jtilia.org
DotRecast Copyright (c) 2023-2024 Choi Ikpil ikpil@naver.com DotRecast Copyright (c) 2023 Choi Ikpil ikpil@naver.com
This software is provided 'as-is', without any express or implied This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages warranty. In no event will the authors be held liable for any damages
@ -44,12 +44,12 @@ namespace DotRecast.Detour.Dynamic
this.voxelTile = voxelTile; this.voxelTile = voxelTile;
} }
public bool Build(RcBuilder builder, DtDynamicNavMeshConfig config, RcContext context) public bool Build(RcBuilder builder, DtDynamicNavMeshConfig config, RcTelemetry telemetry)
{ {
if (dirty) if (dirty)
{ {
RcHeightfield heightfield = BuildHeightfield(config, context); RcHeightfield heightfield = BuildHeightfield(config, telemetry);
RcBuilderResult r = BuildRecast(builder, config, voxelTile, heightfield, context); RcBuilderResult r = BuildRecast(builder, config, voxelTile, heightfield, telemetry);
DtNavMeshCreateParams option = NavMeshCreateParams(voxelTile.tileX, voxelTile.tileZ, voxelTile.cellSize, DtNavMeshCreateParams option = NavMeshCreateParams(voxelTile.tileX, voxelTile.tileZ, voxelTile.cellSize,
voxelTile.cellHeight, config, r); voxelTile.cellHeight, config, r);
meshData = DtNavMeshBuilder.CreateNavMeshData(option); meshData = DtNavMeshBuilder.CreateNavMeshData(option);
@ -59,7 +59,7 @@ namespace DotRecast.Detour.Dynamic
return false; return false;
} }
private RcHeightfield BuildHeightfield(DtDynamicNavMeshConfig config, RcContext context) private RcHeightfield BuildHeightfield(DtDynamicNavMeshConfig config, RcTelemetry telemetry)
{ {
ICollection<long> rasterizedColliders = checkpoint != null ICollection<long> rasterizedColliders = checkpoint != null
? checkpoint.colliders as ICollection<long> ? checkpoint.colliders as ICollection<long>
@ -74,7 +74,7 @@ namespace DotRecast.Detour.Dynamic
if (!rasterizedColliders.Contains(cid)) if (!rasterizedColliders.Contains(cid))
{ {
heightfield.bmax.Y = Math.Max(heightfield.bmax.Y, c.Bounds()[4] + heightfield.ch * 2); heightfield.bmax.Y = Math.Max(heightfield.bmax.Y, c.Bounds()[4] + heightfield.ch * 2);
c.Rasterize(heightfield, context); c.Rasterize(heightfield, telemetry);
} }
} }
@ -87,7 +87,7 @@ namespace DotRecast.Detour.Dynamic
} }
private RcBuilderResult BuildRecast(RcBuilder builder, DtDynamicNavMeshConfig config, DtVoxelTile vt, private RcBuilderResult BuildRecast(RcBuilder builder, DtDynamicNavMeshConfig config, DtVoxelTile vt,
RcHeightfield heightfield, RcContext context) RcHeightfield heightfield, RcTelemetry telemetry)
{ {
RcConfig rcConfig = new RcConfig( RcConfig rcConfig = new RcConfig(
config.useTiles, config.tileSizeX, config.tileSizeZ, config.useTiles, config.tileSizeX, config.tileSizeZ,
@ -100,7 +100,7 @@ namespace DotRecast.Detour.Dynamic
Math.Min(DtDynamicNavMesh.MAX_VERTS_PER_POLY, config.vertsPerPoly), Math.Min(DtDynamicNavMesh.MAX_VERTS_PER_POLY, config.vertsPerPoly),
config.detailSampleDistance, config.detailSampleMaxError, config.detailSampleDistance, config.detailSampleMaxError,
true, true, true, default, true); true, true, true, default, true);
RcBuilderResult r = builder.Build(context, vt.tileX, vt.tileZ, null, rcConfig, heightfield, false); RcBuilderResult r = builder.Build(vt.tileX, vt.tileZ, null, rcConfig, heightfield, telemetry);
if (config.keepIntermediateResults) if (config.keepIntermediateResults)
{ {
recastResult = r; recastResult = r;
@ -132,8 +132,8 @@ namespace DotRecast.Detour.Dynamic
private DtNavMeshCreateParams NavMeshCreateParams(int tilex, int tileZ, float cellSize, float cellHeight, private DtNavMeshCreateParams NavMeshCreateParams(int tilex, int tileZ, float cellSize, float cellHeight,
DtDynamicNavMeshConfig config, RcBuilderResult rcResult) DtDynamicNavMeshConfig config, RcBuilderResult rcResult)
{ {
RcPolyMesh m_pmesh = rcResult.Mesh; RcPolyMesh m_pmesh = rcResult.GetMesh();
RcPolyMeshDetail m_dmesh = rcResult.MeshDetail; RcPolyMeshDetail m_dmesh = rcResult.GetMeshDetail();
DtNavMeshCreateParams option = new DtNavMeshCreateParams(); DtNavMeshCreateParams option = new DtNavMeshCreateParams();
for (int i = 0; i < m_pmesh.npolys; ++i) for (int i = 0; i < m_pmesh.npolys; ++i)
{ {
@ -168,12 +168,12 @@ namespace DotRecast.Detour.Dynamic
option.buildBvTree = true; option.buildBvTree = true;
option.offMeshConCount = 0; option.offMeshConCount = 0;
option.offMeshConVerts = Array.Empty<float>(); option.offMeshConVerts = new float[0];
option.offMeshConRad = Array.Empty<float>(); option.offMeshConRad = new float[0];
option.offMeshConDir = Array.Empty<int>(); option.offMeshConDir = new int[0];
option.offMeshConAreas = Array.Empty<int>(); option.offMeshConAreas = new int[0];
option.offMeshConFlags = Array.Empty<int>(); option.offMeshConFlags = new int[0];
option.offMeshConUserID = Array.Empty<int>(); option.offMeshConUserID = new int[0];
return option; return option;
} }
@ -181,7 +181,7 @@ namespace DotRecast.Detour.Dynamic
{ {
if (meshData != null) if (meshData != null)
{ {
navMesh.AddTile(meshData, 0, 0, out var id); id = navMesh.AddTile(meshData, 0, 0);
} }
else else
{ {
@ -189,10 +189,5 @@ namespace DotRecast.Detour.Dynamic
id = 0; id = 0;
} }
} }
public void SetMeshData(DtMeshData data)
{
this.meshData = data;
}
} }
} }

View File

@ -1,6 +1,6 @@
/* /*
recast4j copyright (c) 2021 Piotr Piastucki piotr@jtilia.org recast4j copyright (c) 2021 Piotr Piastucki piotr@jtilia.org
DotRecast Copyright (c) 2023-2024 Choi Ikpil ikpil@naver.com DotRecast Copyright (c) 2023 Choi Ikpil ikpil@naver.com
This software is provided 'as-is', without any express or implied This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages warranty. In no event will the authors be held liable for any damages

View File

@ -1,6 +1,6 @@
/* /*
recast4j copyright (c) 2021 Piotr Piastucki piotr@jtilia.org recast4j copyright (c) 2021 Piotr Piastucki piotr@jtilia.org
DotRecast Copyright (c) 2023-2024 Choi Ikpil ikpil@naver.com DotRecast Copyright (c) 2023 Choi Ikpil ikpil@naver.com
This software is provided 'as-is', without any express or implied This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages warranty. In no event will the authors be held liable for any damages

View File

@ -1,6 +1,6 @@
/* /*
recast4j copyright (c) 2021 Piotr Piastucki piotr@jtilia.org recast4j copyright (c) 2021 Piotr Piastucki piotr@jtilia.org
DotRecast Copyright (c) 2023-2024 Choi Ikpil ikpil@naver.com DotRecast Copyright (c) 2023 Choi Ikpil ikpil@naver.com
This software is provided 'as-is', without any express or implied This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages warranty. In no event will the authors be held liable for any damages

View File

@ -1,6 +1,6 @@
/* /*
recast4j copyright (c) 2021 Piotr Piastucki piotr@jtilia.org recast4j copyright (c) 2021 Piotr Piastucki piotr@jtilia.org
DotRecast Copyright (c) 2023-2024 Choi Ikpil ikpil@naver.com DotRecast Copyright (c) 2023 Choi Ikpil ikpil@naver.com
This software is provided 'as-is', without any express or implied This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages warranty. In no event will the authors be held liable for any damages

View File

@ -1,6 +1,6 @@
/* /*
recast4j copyright (c) 2021 Piotr Piastucki piotr@jtilia.org recast4j copyright (c) 2021 Piotr Piastucki piotr@jtilia.org
DotRecast Copyright (c) 2023-2024 Choi Ikpil ikpil@naver.com DotRecast Copyright (c) 2023 Choi Ikpil ikpil@naver.com
This software is provided 'as-is', without any express or implied This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages warranty. In no event will the authors be held liable for any damages

View File

@ -1,6 +1,6 @@
/* /*
recast4j copyright (c) 2021 Piotr Piastucki piotr@jtilia.org recast4j copyright (c) 2021 Piotr Piastucki piotr@jtilia.org
DotRecast Copyright (c) 2023-2024 Choi Ikpil ikpil@naver.com DotRecast Copyright (c) 2023 Choi Ikpil ikpil@naver.com
This software is provided 'as-is', without any express or implied This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages warranty. In no event will the authors be held liable for any damages
@ -78,7 +78,7 @@ namespace DotRecast.Detour.Dynamic.Io
walkbableAreaMod, buildMeshDetail); walkbableAreaMod, buildMeshDetail);
} }
public static DtVoxelFile From(RcConfig config, IList<RcBuilderResult> results) public static DtVoxelFile From(RcConfig config, List<RcBuilderResult> results)
{ {
DtVoxelFile f = new DtVoxelFile(); DtVoxelFile f = new DtVoxelFile();
f.version = 1; f.version = 1;
@ -109,14 +109,13 @@ namespace DotRecast.Detour.Dynamic.Io
}; };
foreach (RcBuilderResult r in results) foreach (RcBuilderResult r in results)
{ {
float pad = r.SolidHeightfiled.borderSize * r.SolidHeightfiled.cs; f.tiles.Add(new DtVoxelTile(r.tileX, r.tileZ, r.GetSolidHeightfield()));
f.tiles.Add(new DtVoxelTile(r.TileX, r.TileZ, r.SolidHeightfiled)); f.bounds[0] = Math.Min(f.bounds[0], r.GetSolidHeightfield().bmin.X);
f.bounds[0] = Math.Min(f.bounds[0], r.SolidHeightfiled.bmin.X + pad); f.bounds[1] = Math.Min(f.bounds[1], r.GetSolidHeightfield().bmin.Y);
f.bounds[1] = Math.Min(f.bounds[1], r.SolidHeightfiled.bmin.Y); f.bounds[2] = Math.Min(f.bounds[2], r.GetSolidHeightfield().bmin.Z);
f.bounds[2] = Math.Min(f.bounds[2], r.SolidHeightfiled.bmin.Z + pad); f.bounds[3] = Math.Max(f.bounds[3], r.GetSolidHeightfield().bmax.X);
f.bounds[3] = Math.Max(f.bounds[3], r.SolidHeightfiled.bmax.X - pad); f.bounds[4] = Math.Max(f.bounds[4], r.GetSolidHeightfield().bmax.Y);
f.bounds[4] = Math.Max(f.bounds[4], r.SolidHeightfiled.bmax.Y); f.bounds[5] = Math.Max(f.bounds[5], r.GetSolidHeightfield().bmax.Z);
f.bounds[5] = Math.Max(f.bounds[5], r.SolidHeightfiled.bmax.Z - pad);
} }
return f; return f;
@ -156,13 +155,12 @@ namespace DotRecast.Detour.Dynamic.Io
{ {
RcHeightfield heightfield = vt.Heightfield(); RcHeightfield heightfield = vt.Heightfield();
f.tiles.Add(new DtVoxelTile(vt.tileX, vt.tileZ, heightfield)); f.tiles.Add(new DtVoxelTile(vt.tileX, vt.tileZ, heightfield));
float pad = vt.borderSize * vt.cellSize; f.bounds[0] = Math.Min(f.bounds[0], vt.boundsMin.X);
f.bounds[0] = Math.Min(f.bounds[0], vt.boundsMin.X + pad);
f.bounds[1] = Math.Min(f.bounds[1], vt.boundsMin.Y); f.bounds[1] = Math.Min(f.bounds[1], vt.boundsMin.Y);
f.bounds[2] = Math.Min(f.bounds[2], vt.boundsMin.Z + pad); f.bounds[2] = Math.Min(f.bounds[2], vt.boundsMin.Z);
f.bounds[3] = Math.Max(f.bounds[3], vt.boundsMax.X - pad); f.bounds[3] = Math.Max(f.bounds[3], vt.boundsMax.X);
f.bounds[4] = Math.Max(f.bounds[4], vt.boundsMax.Y); f.bounds[4] = Math.Max(f.bounds[4], vt.boundsMax.Y);
f.bounds[5] = Math.Max(f.bounds[5], vt.boundsMax.Z - pad); f.bounds[5] = Math.Max(f.bounds[5], vt.boundsMax.Z);
} }
return f; return f;

View File

@ -1,6 +1,6 @@
/* /*
recast4j copyright (c) 2021 Piotr Piastucki piotr@jtilia.org recast4j copyright (c) 2021 Piotr Piastucki piotr@jtilia.org
DotRecast Copyright (c) 2023-2024 Choi Ikpil ikpil@naver.com DotRecast Copyright (c) 2023 Choi Ikpil ikpil@naver.com
This software is provided 'as-is', without any express or implied This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages warranty. In no event will the authors be held liable for any damages
@ -35,12 +35,12 @@ namespace DotRecast.Detour.Dynamic.Io
public DtVoxelFile Read(BinaryReader stream) public DtVoxelFile Read(BinaryReader stream)
{ {
RcByteBuffer buf = RcIO.ToByteBuffer(stream); RcByteBuffer buf = IOUtils.ToByteBuffer(stream);
DtVoxelFile file = new DtVoxelFile(); DtVoxelFile file = new DtVoxelFile();
int magic = buf.GetInt(); int magic = buf.GetInt();
if (magic != DtVoxelFile.MAGIC) if (magic != DtVoxelFile.MAGIC)
{ {
magic = RcIO.SwapEndianness(magic); magic = IOUtils.SwapEndianness(magic);
if (magic != DtVoxelFile.MAGIC) if (magic != DtVoxelFile.MAGIC)
{ {
throw new IOException("Invalid magic"); throw new IOException("Invalid magic");

View File

@ -1,6 +1,6 @@
/* /*
recast4j copyright (c) 2021 Piotr Piastucki piotr@jtilia.org recast4j copyright (c) 2021 Piotr Piastucki piotr@jtilia.org
DotRecast Copyright (c) 2023-2024 Choi Ikpil ikpil@naver.com DotRecast Copyright (c) 2023 Choi Ikpil ikpil@naver.com
This software is provided 'as-is', without any express or implied This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages warranty. In no event will the authors be held liable for any damages
@ -19,11 +19,12 @@ freely, subject to the following restrictions:
using System.IO; using System.IO;
using DotRecast.Core; using DotRecast.Core;
using DotRecast.Core.Numerics;
using DotRecast.Detour.Io; using DotRecast.Detour.Io;
namespace DotRecast.Detour.Dynamic.Io namespace DotRecast.Detour.Dynamic.Io
{ {
public class DtVoxelFileWriter public class DtVoxelFileWriter : DtWriter
{ {
private readonly IRcCompressor _compressor; private readonly IRcCompressor _compressor;
@ -39,34 +40,34 @@ namespace DotRecast.Detour.Dynamic.Io
public void Write(BinaryWriter stream, DtVoxelFile f, RcByteOrder byteOrder, bool compression) public void Write(BinaryWriter stream, DtVoxelFile f, RcByteOrder byteOrder, bool compression)
{ {
RcIO.Write(stream, DtVoxelFile.MAGIC, byteOrder); Write(stream, DtVoxelFile.MAGIC, byteOrder);
RcIO.Write(stream, DtVoxelFile.VERSION_EXPORTER_RECAST4J | (compression ? DtVoxelFile.VERSION_COMPRESSION_LZ4 : 0), byteOrder); Write(stream, DtVoxelFile.VERSION_EXPORTER_RECAST4J | (compression ? DtVoxelFile.VERSION_COMPRESSION_LZ4 : 0), byteOrder);
RcIO.Write(stream, f.walkableRadius, byteOrder); Write(stream, f.walkableRadius, byteOrder);
RcIO.Write(stream, f.walkableHeight, byteOrder); Write(stream, f.walkableHeight, byteOrder);
RcIO.Write(stream, f.walkableClimb, byteOrder); Write(stream, f.walkableClimb, byteOrder);
RcIO.Write(stream, f.walkableSlopeAngle, byteOrder); Write(stream, f.walkableSlopeAngle, byteOrder);
RcIO.Write(stream, f.cellSize, byteOrder); Write(stream, f.cellSize, byteOrder);
RcIO.Write(stream, f.maxSimplificationError, byteOrder); Write(stream, f.maxSimplificationError, byteOrder);
RcIO.Write(stream, f.maxEdgeLen, byteOrder); Write(stream, f.maxEdgeLen, byteOrder);
RcIO.Write(stream, f.minRegionArea, byteOrder); Write(stream, f.minRegionArea, byteOrder);
RcIO.Write(stream, f.regionMergeArea, byteOrder); Write(stream, f.regionMergeArea, byteOrder);
RcIO.Write(stream, f.vertsPerPoly, byteOrder); Write(stream, f.vertsPerPoly, byteOrder);
RcIO.Write(stream, f.buildMeshDetail); Write(stream, f.buildMeshDetail);
RcIO.Write(stream, f.detailSampleDistance, byteOrder); Write(stream, f.detailSampleDistance, byteOrder);
RcIO.Write(stream, f.detailSampleMaxError, byteOrder); Write(stream, f.detailSampleMaxError, byteOrder);
RcIO.Write(stream, f.useTiles); Write(stream, f.useTiles);
RcIO.Write(stream, f.tileSizeX, byteOrder); Write(stream, f.tileSizeX, byteOrder);
RcIO.Write(stream, f.tileSizeZ, byteOrder); Write(stream, f.tileSizeZ, byteOrder);
RcIO.Write(stream, f.rotation.X, byteOrder); Write(stream, f.rotation.X, byteOrder);
RcIO.Write(stream, f.rotation.Y, byteOrder); Write(stream, f.rotation.Y, byteOrder);
RcIO.Write(stream, f.rotation.Z, byteOrder); Write(stream, f.rotation.Z, byteOrder);
RcIO.Write(stream, f.bounds[0], byteOrder); Write(stream, f.bounds[0], byteOrder);
RcIO.Write(stream, f.bounds[1], byteOrder); Write(stream, f.bounds[1], byteOrder);
RcIO.Write(stream, f.bounds[2], byteOrder); Write(stream, f.bounds[2], byteOrder);
RcIO.Write(stream, f.bounds[3], byteOrder); Write(stream, f.bounds[3], byteOrder);
RcIO.Write(stream, f.bounds[4], byteOrder); Write(stream, f.bounds[4], byteOrder);
RcIO.Write(stream, f.bounds[5], byteOrder); Write(stream, f.bounds[5], byteOrder);
RcIO.Write(stream, f.tiles.Count, byteOrder); Write(stream, f.tiles.Count, byteOrder);
foreach (DtVoxelTile t in f.tiles) foreach (DtVoxelTile t in f.tiles)
{ {
WriteTile(stream, t, byteOrder, compression); WriteTile(stream, t, byteOrder, compression);
@ -75,26 +76,26 @@ namespace DotRecast.Detour.Dynamic.Io
public void WriteTile(BinaryWriter stream, DtVoxelTile tile, RcByteOrder byteOrder, bool compression) public void WriteTile(BinaryWriter stream, DtVoxelTile tile, RcByteOrder byteOrder, bool compression)
{ {
RcIO.Write(stream, tile.tileX, byteOrder); Write(stream, tile.tileX, byteOrder);
RcIO.Write(stream, tile.tileZ, byteOrder); Write(stream, tile.tileZ, byteOrder);
RcIO.Write(stream, tile.width, byteOrder); Write(stream, tile.width, byteOrder);
RcIO.Write(stream, tile.depth, byteOrder); Write(stream, tile.depth, byteOrder);
RcIO.Write(stream, tile.borderSize, byteOrder); Write(stream, tile.borderSize, byteOrder);
RcIO.Write(stream, tile.boundsMin.X, byteOrder); Write(stream, tile.boundsMin.X, byteOrder);
RcIO.Write(stream, tile.boundsMin.Y, byteOrder); Write(stream, tile.boundsMin.Y, byteOrder);
RcIO.Write(stream, tile.boundsMin.Z, byteOrder); Write(stream, tile.boundsMin.Z, byteOrder);
RcIO.Write(stream, tile.boundsMax.X, byteOrder); Write(stream, tile.boundsMax.X, byteOrder);
RcIO.Write(stream, tile.boundsMax.Y, byteOrder); Write(stream, tile.boundsMax.Y, byteOrder);
RcIO.Write(stream, tile.boundsMax.Z, byteOrder); Write(stream, tile.boundsMax.Z, byteOrder);
RcIO.Write(stream, tile.cellSize, byteOrder); Write(stream, tile.cellSize, byteOrder);
RcIO.Write(stream, tile.cellHeight, byteOrder); Write(stream, tile.cellHeight, byteOrder);
byte[] bytes = tile.spanData; byte[] bytes = tile.spanData;
if (compression) if (compression)
{ {
bytes = _compressor.Compress(bytes); bytes = _compressor.Compress(bytes);
} }
RcIO.Write(stream, bytes.Length, byteOrder); Write(stream, bytes.Length, byteOrder);
stream.Write(bytes); stream.Write(bytes);
} }
} }

View File

@ -1,6 +1,6 @@
/* /*
recast4j copyright (c) 2021 Piotr Piastucki piotr@jtilia.org recast4j copyright (c) 2021 Piotr Piastucki piotr@jtilia.org
DotRecast Copyright (c) 2023-2024 Choi Ikpil ikpil@naver.com DotRecast Copyright (c) 2023 Choi Ikpil ikpil@naver.com
This software is provided 'as-is', without any express or implied This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages warranty. In no event will the authors be held liable for any damages

View File

@ -1,6 +1,6 @@
/* /*
recast4j copyright (c) 2021 Piotr Piastucki piotr@jtilia.org recast4j copyright (c) 2021 Piotr Piastucki piotr@jtilia.org
DotRecast Copyright (c) 2023-2024 Choi Ikpil ikpil@naver.com DotRecast Copyright (c) 2023 Choi Ikpil ikpil@naver.com
This software is provided 'as-is', without any express or implied This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages warranty. In no event will the authors be held liable for any damages
@ -40,20 +40,20 @@ namespace DotRecast.Detour.Extras
BVItem it = new BVItem(); BVItem it = new BVItem();
items[i] = it; items[i] = it;
it.i = i; it.i = i;
RcVec3f bmin = RcVec.Create(data.verts, data.polys[i].verts[0] * 3); RcVec3f bmin = RcVecUtils.Create(data.verts, data.polys[i].verts[0] * 3);
RcVec3f bmax = RcVec.Create(data.verts, data.polys[i].verts[0] * 3); RcVec3f bmax = RcVecUtils.Create(data.verts, data.polys[i].verts[0] * 3);
for (int j = 1; j < data.polys[i].vertCount; j++) for (int j = 1; j < data.polys[i].vertCount; j++)
{ {
bmin = RcVec3f.Min(bmin, RcVec.Create(data.verts, data.polys[i].verts[j] * 3)); bmin = RcVecUtils.Min(bmin, data.verts, data.polys[i].verts[j] * 3);
bmax = RcVec3f.Max(bmax, RcVec.Create(data.verts, data.polys[i].verts[j] * 3)); bmax = RcVecUtils.Max(bmax, data.verts, data.polys[i].verts[j] * 3);
} }
it.bmin.X = Math.Clamp((int)((bmin.X - data.header.bmin.X) * quantFactor), 0, 0x7fffffff); it.bmin[0] = Math.Clamp((int)((bmin.X - data.header.bmin.X) * quantFactor), 0, 0x7fffffff);
it.bmin.Y = Math.Clamp((int)((bmin.Y - data.header.bmin.Y) * quantFactor), 0, 0x7fffffff); it.bmin[1] = Math.Clamp((int)((bmin.Y - data.header.bmin.Y) * quantFactor), 0, 0x7fffffff);
it.bmin.Z = Math.Clamp((int)((bmin.Z - data.header.bmin.Z) * quantFactor), 0, 0x7fffffff); it.bmin[2] = Math.Clamp((int)((bmin.Z - data.header.bmin.Z) * quantFactor), 0, 0x7fffffff);
it.bmax.X = Math.Clamp((int)((bmax.X - data.header.bmin.X) * quantFactor), 0, 0x7fffffff); it.bmax[0] = Math.Clamp((int)((bmax.X - data.header.bmin.X) * quantFactor), 0, 0x7fffffff);
it.bmax.Y = Math.Clamp((int)((bmax.Y - data.header.bmin.Y) * quantFactor), 0, 0x7fffffff); it.bmax[1] = Math.Clamp((int)((bmax.Y - data.header.bmin.Y) * quantFactor), 0, 0x7fffffff);
it.bmax.Z = Math.Clamp((int)((bmax.Z - data.header.bmin.Z) * quantFactor), 0, 0x7fffffff); it.bmax[2] = Math.Clamp((int)((bmax.Z - data.header.bmin.Z) * quantFactor), 0, 0x7fffffff);
} }
return DtNavMeshBuilder.Subdivide(items, data.header.polyCount, 0, data.header.polyCount, 0, nodes); return DtNavMeshBuilder.Subdivide(items, data.header.polyCount, 0, data.header.polyCount, 0, nodes);

View File

@ -1,18 +0,0 @@
{
"name": "DotRecast.Detour.Extras",
"rootNamespace": "DotRecast.Detour.Extras",
"references": [
"DotRecast.Core",
"DotRecast.Detour",
"DotRecast.Recast"
],
"includePlatforms": [],
"excludePlatforms": [],
"allowUnsafeCode": false,
"overrideReferences": false,
"precompiledReferences": [],
"autoReferenced": true,
"defineConstraints": [],
"versionDefines": [],
"noEngineReferences": true
}

View File

@ -1,16 +1,15 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<TargetFrameworks>netstandard2.1;net6.0;net7.0;net8.0</TargetFrameworks> <TargetFrameworks>netstandard2.1;net6.0;net7.0</TargetFrameworks>
<PackageId>DotRecast.Detour.Extras</PackageId> <PackageId>DotRecast.Detour.Extras</PackageId>
<PackageReadmeFile>README.md</PackageReadmeFile> <PackageReadmeFile>README.md</PackageReadmeFile>
<Authors>ikpil</Authors> <Authors>ikpil</Authors>
<Description>DotRecast - a port of Recast Detour, Industry-standard navigation mesh toolset for .NET, C#, Unity3D, games, servers</Description> <Description>DotRecast - a port of Recast Detour, navigation mesh toolset for games, Unity3D, servers, C#</Description>
<RepositoryType>git</RepositoryType> <RepositoryType>git</RepositoryType>
<PackageProjectUrl>https://github.com/ikpil/DotRecast</PackageProjectUrl> <PackageProjectUrl>https://github.com/ikpil/DotRecast</PackageProjectUrl>
<RepositoryUrl>https://github.com/ikpil/DotRecast</RepositoryUrl> <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> <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> </PropertyGroup>
<ItemGroup> <ItemGroup>

View File

@ -1,6 +1,5 @@
/* /*
recast4j Copyright (c) 2015-2019 Piotr Piastucki piotr@jtilia.org recast4j copyright (c) 2015-2019 Piotr Piastucki piotr@jtilia.org
DotRecast Copyright (c) 2023-2024 Choi Ikpil ikpil@naver.com
This software is provided 'as-is', without any express or implied This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages warranty. In no event will the authors be held liable for any damages

View File

@ -11,7 +11,7 @@ namespace DotRecast.Detour.Extras.Jumplink
protected void SampleGround(JumpLinkBuilderConfig acfg, EdgeSampler es, ComputeNavMeshHeight heightFunc) protected void SampleGround(JumpLinkBuilderConfig acfg, EdgeSampler es, ComputeNavMeshHeight heightFunc)
{ {
float cs = acfg.cellSize; float cs = acfg.cellSize;
float dist = MathF.Sqrt(RcVec.Dist2DSqr(es.start.p, es.start.q)); float dist = MathF.Sqrt(RcVecUtils.Dist2DSqr(es.start.p, es.start.q));
int ngsamples = Math.Max(2, (int)MathF.Ceiling(dist / cs)); int ngsamples = Math.Max(2, (int)MathF.Ceiling(dist / cs));
SampleGroundSegment(heightFunc, es.start, ngsamples); SampleGroundSegment(heightFunc, es.start, ngsamples);

View File

@ -1,18 +1,17 @@
using System; using System;
using DotRecast.Core;
using DotRecast.Core.Numerics; using DotRecast.Core.Numerics;
namespace DotRecast.Detour.Extras.Jumplink namespace DotRecast.Detour.Extras.Jumplink
{ {
public class ClimbTrajectory : ITrajectory public class ClimbTrajectory : Trajectory
{ {
public RcVec3f Apply(RcVec3f start, RcVec3f end, float u) public override RcVec3f Apply(RcVec3f start, RcVec3f end, float u)
{ {
return new RcVec3f() return new RcVec3f()
{ {
X = RcMath.Lerp(start.X, end.X, Math.Min(2f * u, 1f)), X = Lerp(start.X, end.X, Math.Min(2f * u, 1f)),
Y = RcMath.Lerp(start.Y, end.Y, Math.Max(0f, 2f * u - 1f)), Y = Lerp(start.Y, end.Y, Math.Max(0f, 2f * u - 1f)),
Z = RcMath.Lerp(start.Z, end.Z, Math.Min(2f * u, 1f)) Z = Lerp(start.Z, end.Z, Math.Min(2f * u, 1f))
}; };
} }
} }

View File

@ -1,11 +1,11 @@
using System.Collections.Generic; using System.Collections.Generic;
using DotRecast.Core.Numerics; using DotRecast.Core.Numerics;
using DotRecast.Recast; using DotRecast.Recast;
using static DotRecast.Recast.RcConstants;
namespace DotRecast.Detour.Extras.Jumplink namespace DotRecast.Detour.Extras.Jumplink
{ {
using static RcRecast;
public class EdgeExtractor public class EdgeExtractor
{ {
public JumpEdge[] ExtractEdges(RcPolyMesh mesh) public JumpEdge[] ExtractEdges(RcPolyMesh mesh)

View File

@ -7,13 +7,13 @@ namespace DotRecast.Detour.Extras.Jumplink
{ {
public readonly GroundSegment start = new GroundSegment(); public readonly GroundSegment start = new GroundSegment();
public readonly List<GroundSegment> end = new List<GroundSegment>(); public readonly List<GroundSegment> end = new List<GroundSegment>();
public readonly ITrajectory trajectory; public readonly Trajectory trajectory;
public readonly RcVec3f ax = new RcVec3f(); public readonly RcVec3f ax = new RcVec3f();
public readonly RcVec3f ay = new RcVec3f(); public readonly RcVec3f ay = new RcVec3f();
public readonly RcVec3f az = new RcVec3f(); public readonly RcVec3f az = new RcVec3f();
public EdgeSampler(JumpEdge edge, ITrajectory trajectory) public EdgeSampler(JumpEdge edge, Trajectory trajectory)
{ {
this.trajectory = trajectory; this.trajectory = trajectory;
ax = RcVec3f.Subtract(edge.sq, edge.sp); ax = RcVec3f.Subtract(edge.sq, edge.sp);

View File

@ -3,7 +3,7 @@ using DotRecast.Core.Numerics;
namespace DotRecast.Detour.Extras.Jumplink namespace DotRecast.Detour.Extras.Jumplink
{ {
public class EdgeSamplerFactory class EdgeSamplerFactory
{ {
public EdgeSampler Get(JumpLinkBuilderConfig acfg, JumpLinkType type, JumpEdge edge) public EdgeSampler Get(JumpLinkBuilderConfig acfg, JumpLinkType type, JumpEdge edge)
{ {

View File

@ -4,7 +4,7 @@ namespace DotRecast.Detour.Extras.Jumplink
{ {
public class GroundSample public class GroundSample
{ {
public RcVec3f p; public RcVec3f p = new RcVec3f();
public bool validTrajectory; public bool validTrajectory;
public bool validHeight; public bool validHeight;
} }

View File

@ -4,8 +4,8 @@ namespace DotRecast.Detour.Extras.Jumplink
{ {
public class GroundSegment public class GroundSegment
{ {
public RcVec3f p; public RcVec3f p = new RcVec3f();
public RcVec3f q; public RcVec3f q = new RcVec3f();
public GroundSample[] gsamples; public GroundSample[] gsamples;
public float height; public float height;
} }

View File

@ -1,10 +0,0 @@
using System;
using DotRecast.Core.Numerics;
namespace DotRecast.Detour.Extras.Jumplink
{
public interface ITrajectory
{
RcVec3f Apply(RcVec3f start, RcVec3f end, float u);
}
}

View File

@ -10,6 +10,6 @@ namespace DotRecast.Detour.Extras.Jumplink
public GroundSample[] endSamples; public GroundSample[] endSamples;
public GroundSegment start; public GroundSegment start;
public GroundSegment end; public GroundSegment end;
public ITrajectory trajectory; public Trajectory trajectory;
} }
} }

View File

@ -21,7 +21,7 @@ namespace DotRecast.Detour.Extras.Jumplink
public JumpLinkBuilder(IList<RcBuilderResult> results) public JumpLinkBuilder(IList<RcBuilderResult> results)
{ {
this.results = results; this.results = results;
edges = results.Select(r => edgeExtractor.ExtractEdges(r.Mesh)).ToList(); edges = results.Select(r => edgeExtractor.ExtractEdges(r.GetMesh())).ToList();
} }
public List<JumpLink> Build(JumpLinkBuilderConfig acfg, JumpLinkType type) public List<JumpLink> Build(JumpLinkBuilderConfig acfg, JumpLinkType type)
@ -43,7 +43,7 @@ namespace DotRecast.Detour.Extras.Jumplink
{ {
EdgeSampler es = edgeSamplerFactory.Get(acfg, type, edge); EdgeSampler es = edgeSamplerFactory.Get(acfg, type, edge);
groundSampler.Sample(acfg, result, es); groundSampler.Sample(acfg, result, es);
trajectorySampler.Sample(acfg, result.SolidHeightfiled, es); trajectorySampler.Sample(acfg, result.GetSolidHeightfield(), es);
JumpSegment[] jumpSegments = jumpSegmentBuilder.Build(acfg, es); JumpSegment[] jumpSegments = jumpSegmentBuilder.Build(acfg, es);
return BuildJumpLinks(acfg, es, jumpSegments); return BuildJumpLinks(acfg, es, jumpSegments);
} }
@ -59,13 +59,13 @@ namespace DotRecast.Detour.Extras.Jumplink
GroundSegment end = es.end[js.groundSegment]; GroundSegment end = es.end[js.groundSegment];
RcVec3f ep = end.gsamples[js.startSample].p; RcVec3f ep = end.gsamples[js.startSample].p;
RcVec3f eq = end.gsamples[js.startSample + js.samples - 1].p; RcVec3f eq = end.gsamples[js.startSample + js.samples - 1].p;
float d = Math.Min(RcVec.Dist2DSqr(sp, sq), RcVec.Dist2DSqr(ep, eq)); float d = Math.Min(RcVecUtils.Dist2DSqr(sp, sq), RcVecUtils.Dist2DSqr(ep, eq));
if (d >= 4 * acfg.agentRadius * acfg.agentRadius) if (d >= 4 * acfg.agentRadius * acfg.agentRadius)
{ {
JumpLink link = new JumpLink(); JumpLink link = new JumpLink();
links.Add(link); links.Add(link);
link.startSamples = RcArrays.CopyOf(es.start.gsamples, js.startSample, js.samples); link.startSamples = RcArrayUtils.CopyOf(es.start.gsamples, js.startSample, js.samples);
link.endSamples = RcArrays.CopyOf(end.gsamples, js.startSample, js.samples); link.endSamples = RcArrayUtils.CopyOf(end.gsamples, js.startSample, js.samples);
link.start = es.start; link.start = es.start;
link.end = end; link.end = end;
link.trajectory = es.trajectory; 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_CLIMB_DOWN = new JumpLinkType(EDGE_CLIMB_DOWN_BIT);
public static readonly JumpLinkType EDGE_JUMP_OVER = new JumpLinkType(EDGE_JUMP_OVER_BIT); public static readonly JumpLinkType EDGE_JUMP_OVER = new JumpLinkType(EDGE_JUMP_OVER_BIT);
public readonly int Bit; public int Bit { get; }
private JumpLinkType(int bit) private JumpLinkType(int bit)
{ {

View File

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

View File

@ -1,10 +1,9 @@
using System; using System;
using DotRecast.Core;
using DotRecast.Core.Numerics; using DotRecast.Core.Numerics;
namespace DotRecast.Detour.Extras.Jumplink namespace DotRecast.Detour.Extras.Jumplink
{ {
public class JumpTrajectory : ITrajectory public class JumpTrajectory : Trajectory
{ {
private readonly float jumpHeight; private readonly float jumpHeight;
@ -13,13 +12,13 @@ namespace DotRecast.Detour.Extras.Jumplink
this.jumpHeight = jumpHeight; this.jumpHeight = jumpHeight;
} }
public RcVec3f Apply(RcVec3f start, RcVec3f end, float u) public override RcVec3f Apply(RcVec3f start, RcVec3f end, float u)
{ {
return new RcVec3f return new RcVec3f
{ {
X = RcMath.Lerp(start.X, end.X, u), X = Lerp(start.X, end.X, u),
Y = InterpolateHeight(start.Y, end.Y, u), Y = InterpolateHeight(start.Y, end.Y, u),
Z = RcMath.Lerp(start.Z, end.Z, u) Z = Lerp(start.Z, end.Z, u)
}; };
} }

View File

@ -1,10 +1,11 @@
using DotRecast.Core; using DotRecast.Core;
using DotRecast.Core.Numerics; using DotRecast.Core.Numerics;
using DotRecast.Recast; using DotRecast.Recast;
namespace DotRecast.Detour.Extras.Jumplink namespace DotRecast.Detour.Extras.Jumplink
{ {
public class NavMeshGroundSampler : AbstractGroundSampler class NavMeshGroundSampler : AbstractGroundSampler
{ {
public override void Sample(JumpLinkBuilderConfig acfg, RcBuilderResult result, EdgeSampler es) public override void Sample(JumpLinkBuilderConfig acfg, RcBuilderResult result, EdgeSampler es)
{ {
@ -15,34 +16,27 @@ namespace DotRecast.Detour.Extras.Jumplink
private DtNavMeshQuery CreateNavMesh(RcBuilderResult r, float agentRadius, float agentHeight, float agentClimb) private DtNavMeshQuery CreateNavMesh(RcBuilderResult r, float agentRadius, float agentHeight, float agentClimb)
{ {
DtNavMeshCreateParams option = new DtNavMeshCreateParams(); DtNavMeshCreateParams option = new DtNavMeshCreateParams();
option.verts = r.Mesh.verts; option.verts = r.GetMesh().verts;
option.vertCount = r.Mesh.nverts; option.vertCount = r.GetMesh().nverts;
option.polys = r.Mesh.polys; option.polys = r.GetMesh().polys;
option.polyAreas = r.Mesh.areas; option.polyAreas = r.GetMesh().areas;
option.polyFlags = r.Mesh.flags; option.polyFlags = r.GetMesh().flags;
option.polyCount = r.Mesh.npolys; option.polyCount = r.GetMesh().npolys;
option.nvp = r.Mesh.nvp; option.nvp = r.GetMesh().nvp;
option.detailMeshes = r.MeshDetail.meshes; option.detailMeshes = r.GetMeshDetail().meshes;
option.detailVerts = r.MeshDetail.verts; option.detailVerts = r.GetMeshDetail().verts;
option.detailVertsCount = r.MeshDetail.nverts; option.detailVertsCount = r.GetMeshDetail().nverts;
option.detailTris = r.MeshDetail.tris; option.detailTris = r.GetMeshDetail().tris;
option.detailTriCount = r.MeshDetail.ntris; option.detailTriCount = r.GetMeshDetail().ntris;
option.walkableRadius = agentRadius; option.walkableRadius = agentRadius;
option.walkableHeight = agentHeight; option.walkableHeight = agentHeight;
option.walkableClimb = agentClimb; option.walkableClimb = agentClimb;
option.bmin = r.Mesh.bmin; option.bmin = r.GetMesh().bmin;
option.bmax = r.Mesh.bmax; option.bmax = r.GetMesh().bmax;
option.cs = r.Mesh.cs; option.cs = r.GetMesh().cs;
option.ch = r.Mesh.ch; option.ch = r.GetMesh().ch;
option.buildBvTree = true; option.buildBvTree = true;
var mesh = new DtNavMesh(); return new DtNavMeshQuery(new DtNavMesh(DtNavMeshBuilder.CreateNavMeshData(option), option.nvp, 0));
var status = mesh.Init(DtNavMeshBuilder.CreateNavMeshData(option), option.nvp, 0);
if (status.Failed())
{
return null;
}
return new DtNavMeshQuery(mesh);
} }
@ -52,12 +46,25 @@ namespace DotRecast.Detour.Extras.Jumplink
RcVec3f halfExtents = new RcVec3f { X = cs, Y = heightRange, Z = cs }; RcVec3f halfExtents = new RcVec3f { X = cs, Y = heightRange, Z = cs };
float maxHeight = pt.Y + heightRange; float maxHeight = pt.Y + heightRange;
var query = new DtHeightSamplePolyQuery(navMeshQuery, pt, pt.Y, maxHeight); RcAtomicBoolean found = new RcAtomicBoolean();
navMeshQuery.QueryPolygons(pt, halfExtents, DtQueryNoOpFilter.Shared, ref query); RcAtomicFloat minHeight = new RcAtomicFloat(pt.Y);
if (query.Found) navMeshQuery.QueryPolygons(pt, halfExtents, DtQueryNoOpFilter.Shared, new PolyQueryInvoker((tile, poly, refs) =>
{ {
height = query.MinHeight; var status = navMeshQuery.GetPolyHeight(refs, pt, out var h);
if (status.Succeeded())
{
if (h > minHeight.Get() && h < maxHeight)
{
minHeight.Exchange(h);
found.Set(true);
}
}
}));
if (found.Get())
{
height = minHeight.Get();
return true; return true;
} }

View File

@ -0,0 +1,19 @@
using System;
namespace DotRecast.Detour.Extras.Jumplink
{
public class PolyQueryInvoker : IDtPolyQuery
{
public readonly Action<DtMeshTile, DtPoly, long> _callback;
public PolyQueryInvoker(Action<DtMeshTile, DtPoly, long> callback)
{
_callback = callback;
}
public void Process(DtMeshTile tile, DtPoly poly, long refs)
{
_callback?.Invoke(tile, poly, refs);
}
}
}

View File

@ -0,0 +1,18 @@
using System;
using DotRecast.Core.Numerics;
namespace DotRecast.Detour.Extras.Jumplink
{
public class Trajectory
{
public float Lerp(float f, float g, float u)
{
return u * g + (1f - u) * f;
}
public virtual RcVec3f Apply(RcVec3f start, RcVec3f end, float u)
{
throw new NotImplementedException();
}
}
}

View File

@ -5,7 +5,7 @@ using DotRecast.Recast;
namespace DotRecast.Detour.Extras.Jumplink namespace DotRecast.Detour.Extras.Jumplink
{ {
public class TrajectorySampler class TrajectorySampler
{ {
public void Sample(JumpLinkBuilderConfig acfg, RcHeightfield heightfield, EdgeSampler es) public void Sample(JumpLinkBuilderConfig acfg, RcHeightfield heightfield, EdgeSampler es)
{ {
@ -32,10 +32,10 @@ namespace DotRecast.Detour.Extras.Jumplink
} }
} }
private bool SampleTrajectory(JumpLinkBuilderConfig acfg, RcHeightfield solid, RcVec3f pa, RcVec3f pb, ITrajectory tra) private bool SampleTrajectory(JumpLinkBuilderConfig acfg, RcHeightfield solid, RcVec3f pa, RcVec3f pb, Trajectory tra)
{ {
float cs = Math.Min(acfg.cellSize, acfg.cellHeight); float cs = Math.Min(acfg.cellSize, acfg.cellHeight);
float d = RcVec.Dist2D(pa, pb) + MathF.Abs(pa.Y - pb.Y); float d = RcVecUtils.Dist2D(pa, pb) + MathF.Abs(pa.Y - pb.Y);
int nsamples = Math.Max(2, (int)MathF.Ceiling(d / cs)); int nsamples = Math.Max(2, (int)MathF.Ceiling(d / cs));
for (int i = 0; i < nsamples; ++i) for (int i = 0; i < nsamples; ++i)
{ {

View File

@ -1,6 +1,5 @@
/* /*
recast4j Copyright (c) 2015-2019 Piotr Piastucki piotr@jtilia.org recast4j copyright (c) 2015-2019 Piotr Piastucki piotr@jtilia.org
DotRecast Copyright (c) 2023-2024 Choi Ikpil ikpil@naver.com
This software is provided 'as-is', without any express or implied This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages warranty. In no event will the authors be held liable for any damages
@ -35,7 +34,8 @@ namespace DotRecast.Detour.Extras
{ {
for (int v = 0; v < tile.data.header.vertCount; v++) 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

@ -1,6 +1,5 @@
/* /*
recast4j Copyright (c) 2015-2019 Piotr Piastucki piotr@jtilia.org recast4j copyright (c) 2015-2019 Piotr Piastucki piotr@jtilia.org
DotRecast Copyright (c) 2023-2024 Choi Ikpil ikpil@naver.com
This software is provided 'as-is', without any express or implied This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages warranty. In no event will the authors be held liable for any damages

View File

@ -1,6 +1,5 @@
/* /*
recast4j Copyright (c) 2015-2019 Piotr Piastucki piotr@jtilia.org recast4j copyright (c) 2015-2019 Piotr Piastucki piotr@jtilia.org
DotRecast Copyright (c) 2023-2024 Choi Ikpil ikpil@naver.com
This software is provided 'as-is', without any express or implied This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages warranty. In no event will the authors be held liable for any damages

View File

@ -1,6 +1,6 @@
/* /*
recast4j copyright (c) 2021 Piotr Piastucki piotr@jtilia.org recast4j copyright (c) 2021 Piotr Piastucki piotr@jtilia.org
DotRecast Copyright (c) 2023-2024 Choi Ikpil ikpil@naver.com DotRecast Copyright (c) 2023 Choi Ikpil ikpil@naver.com
This software is provided 'as-is', without any express or implied This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages warranty. In no event will the authors be held liable for any damages

View File

@ -1,6 +1,5 @@
/* /*
recast4j Copyright (c) 2015-2019 Piotr Piastucki piotr@jtilia.org recast4j copyright (c) 2015-2019 Piotr Piastucki piotr@jtilia.org
DotRecast Copyright (c) 2023-2024 Choi Ikpil ikpil@naver.com
This software is provided 'as-is', without any express or implied This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages warranty. In no event will the authors be held liable for any damages

View File

@ -1,6 +1,5 @@
/* /*
recast4j Copyright (c) 2015-2019 Piotr Piastucki piotr@jtilia.org recast4j copyright (c) 2015-2019 Piotr Piastucki piotr@jtilia.org
DotRecast Copyright (c) 2023-2024 Choi Ikpil ikpil@naver.com
This software is provided 'as-is', without any express or implied This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages warranty. In no event will the authors be held liable for any damages
@ -23,8 +22,6 @@ using DotRecast.Core;
namespace DotRecast.Detour.Extras.Unity.Astar namespace DotRecast.Detour.Extras.Unity.Astar
{ {
using static DtDetour;
public class GraphMeshDataReader : ZipBinaryReader public class GraphMeshDataReader : ZipBinaryReader
{ {
public const float INT_PRECISION_FACTOR = 1000f; public const float INT_PRECISION_FACTOR = 1000f;
@ -79,7 +76,7 @@ namespace DotRecast.Detour.Extras.Unity.Astar
int nodeCount = buffer.GetInt(); int nodeCount = buffer.GetInt();
DtPoly[] nodes = new DtPoly[nodeCount]; DtPoly[] nodes = new DtPoly[nodeCount];
DtPolyDetail[] detailNodes = new DtPolyDetail[nodeCount]; DtPolyDetail[] detailNodes = new DtPolyDetail[nodeCount];
float[] detailVerts = Array.Empty<float>(); float[] detailVerts = new float[0];
int[] detailTris = new int[4 * nodeCount]; int[] detailTris = new int[4 * nodeCount];
int vertMask = GetVertMask(vertsCount); int vertMask = GetVertMask(vertsCount);
float ymin = float.PositiveInfinity; float ymin = float.PositiveInfinity;
@ -100,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[0] * 3 + 1]);
ymax = Math.Max(ymax, verts[nodes[i].verts[1] * 3 + 1]); ymax = Math.Max(ymax, verts[nodes[i].verts[1] * 3 + 1]);
ymax = Math.Max(ymax, verts[nodes[i].verts[2] * 3 + 1]); ymax = Math.Max(ymax, verts[nodes[i].verts[2] * 3 + 1]);
int vertBase = 0; detailNodes[i] = new DtPolyDetail();
byte vertCount = 0; detailNodes[i].vertBase = 0;
int triBase = i; detailNodes[i].vertCount = 0;
byte triCount = 1; detailNodes[i].triBase = i;
detailNodes[i] = new DtPolyDetail(vertBase, triBase, vertCount, triCount); detailNodes[i].triCount = 1;
detailTris[4 * i] = 0; detailTris[4 * i] = 0;
detailTris[4 * i + 1] = 1; detailTris[4 * i + 1] = 1;
detailTris[4 * i + 2] = 2; detailTris[4 * i + 2] = 2;
@ -119,25 +116,25 @@ namespace DotRecast.Detour.Extras.Unity.Astar
tiles[tileIndex].detailVerts = detailVerts; tiles[tileIndex].detailVerts = detailVerts;
tiles[tileIndex].detailTris = detailTris; tiles[tileIndex].detailTris = detailTris;
DtMeshHeader header = new DtMeshHeader(); DtMeshHeader header = new DtMeshHeader();
header.magic = DT_NAVMESH_MAGIC; header.magic = DtMeshHeader.DT_NAVMESH_MAGIC;
header.version = DT_NAVMESH_VERSION; header.version = DtMeshHeader.DT_NAVMESH_VERSION;
header.x = x; header.x = x;
header.y = z; header.y = z;
header.polyCount = nodeCount; header.polyCount = nodeCount;
header.vertCount = vertsCount; header.vertCount = vertsCount;
header.detailMeshCount = nodeCount; header.detailMeshCount = nodeCount;
header.detailTriCount = nodeCount; header.detailTriCount = nodeCount;
header.maxLinkCount = nodeCount * 3 * 2; // needed by Recast, not needed by recast4j, needed by DotRecast header.maxLinkCount = nodeCount * 3 * 2; // XXX: Needed by Recast, not needed by recast4j
header.bmin.X = meta.forcedBoundsCenter.x - 0.5f * meta.forcedBoundsSize.x + header.bmin.X = meta.forcedBoundsCenter.x - 0.5f * meta.forcedBoundsSize.x
meta.cellSize * meta.tileSizeX * x; + meta.cellSize * meta.tileSizeX * x;
header.bmin.Y = ymin; header.bmin.Y = ymin;
header.bmin.Z = meta.forcedBoundsCenter.z - 0.5f * meta.forcedBoundsSize.z + header.bmin.Z = meta.forcedBoundsCenter.z - 0.5f * meta.forcedBoundsSize.z
meta.cellSize * meta.tileSizeZ * z; + meta.cellSize * meta.tileSizeZ * z;
header.bmax.X = meta.forcedBoundsCenter.x - 0.5f * meta.forcedBoundsSize.x + header.bmax.X = meta.forcedBoundsCenter.x - 0.5f * meta.forcedBoundsSize.x
meta.cellSize * meta.tileSizeX * (x + 1); + meta.cellSize * meta.tileSizeX * (x + 1);
header.bmax.Y = ymax; header.bmax.Y = ymax;
header.bmax.Z = meta.forcedBoundsCenter.z - 0.5f * meta.forcedBoundsSize.z + header.bmax.Z = meta.forcedBoundsCenter.z - 0.5f * meta.forcedBoundsSize.z
meta.cellSize * meta.tileSizeZ * (z + 1); + meta.cellSize * meta.tileSizeZ * (z + 1);
header.bvQuantFactor = 1.0f / meta.cellSize; header.bvQuantFactor = 1.0f / meta.cellSize;
header.offMeshBase = nodeCount; header.offMeshBase = nodeCount;
header.walkableClimb = meta.walkableClimb; header.walkableClimb = meta.walkableClimb;

View File

@ -1,6 +1,5 @@
/* /*
recast4j Copyright (c) 2015-2019 Piotr Piastucki piotr@jtilia.org recast4j copyright (c) 2015-2019 Piotr Piastucki piotr@jtilia.org
DotRecast Copyright (c) 2023-2024 Choi Ikpil ikpil@naver.com
This software is provided 'as-is', without any express or implied This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages warranty. In no event will the authors be held liable for any damages

View File

@ -1,6 +1,5 @@
/* /*
recast4j Copyright (c) 2015-2019 Piotr Piastucki piotr@jtilia.org recast4j copyright (c) 2015-2019 Piotr Piastucki piotr@jtilia.org
DotRecast Copyright (c) 2023-2024 Choi Ikpil ikpil@naver.com
This software is provided 'as-is', without any express or implied This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages warranty. In no event will the authors be held liable for any damages

View File

@ -1,6 +1,5 @@
/* /*
recast4j Copyright (c) 2015-2019 Piotr Piastucki piotr@jtilia.org recast4j copyright (c) 2015-2019 Piotr Piastucki piotr@jtilia.org
DotRecast Copyright (c) 2023-2024 Choi Ikpil ikpil@naver.com
This software is provided 'as-is', without any express or implied This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages warranty. In no event will the authors be held liable for any damages
@ -22,8 +21,6 @@ using System.Collections.Generic;
namespace DotRecast.Detour.Extras.Unity.Astar namespace DotRecast.Detour.Extras.Unity.Astar
{ {
using static DtDetour;
public class LinkBuilder public class LinkBuilder
{ {
// Process connections and transform them into recast neighbour flags // Process connections and transform them into recast neighbour flags
@ -68,19 +65,19 @@ namespace DotRecast.Detour.Extras.Unity.Astar
{ {
if (neighbourTile.header.bmin.X > tile.header.bmin.X) if (neighbourTile.header.bmin.X > tile.header.bmin.X)
{ {
node.neis[DtPolyUtils.FindEdge(node, tile, neighbourTile.header.bmin.X, 0)] = DT_EXT_LINK; node.neis[DtPolyUtils.FindEdge(node, tile, neighbourTile.header.bmin.X, 0)] = DtNavMesh.DT_EXT_LINK;
} }
else if (neighbourTile.header.bmin.X < tile.header.bmin.X) else if (neighbourTile.header.bmin.X < tile.header.bmin.X)
{ {
node.neis[DtPolyUtils.FindEdge(node, tile, tile.header.bmin.X, 0)] = DT_EXT_LINK | 4; node.neis[DtPolyUtils.FindEdge(node, tile, tile.header.bmin.X, 0)] = DtNavMesh.DT_EXT_LINK | 4;
} }
else if (neighbourTile.header.bmin.Z > tile.header.bmin.Z) else if (neighbourTile.header.bmin.Z > tile.header.bmin.Z)
{ {
node.neis[DtPolyUtils.FindEdge(node, tile, neighbourTile.header.bmin.Z, 2)] = DT_EXT_LINK | 2; node.neis[DtPolyUtils.FindEdge(node, tile, neighbourTile.header.bmin.Z, 2)] = DtNavMesh.DT_EXT_LINK | 2;
} }
else else
{ {
node.neis[DtPolyUtils.FindEdge(node, tile, tile.header.bmin.Z, 2)] = DT_EXT_LINK | 6; node.neis[DtPolyUtils.FindEdge(node, tile, tile.header.bmin.Z, 2)] = DtNavMesh.DT_EXT_LINK | 6;
} }
} }
} }

View File

@ -1,6 +1,5 @@
/* /*
recast4j Copyright (c) 2015-2019 Piotr Piastucki piotr@jtilia.org recast4j copyright (c) 2015-2019 Piotr Piastucki piotr@jtilia.org
DotRecast Copyright (c) 2023-2024 Choi Ikpil ikpil@naver.com
This software is provided 'as-is', without any express or implied This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages warranty. In no event will the authors be held liable for any damages
@ -26,7 +25,7 @@ namespace DotRecast.Detour.Extras.Unity.Astar
{ {
public const string TYPENAME_RECAST_GRAPH = "Pathfinding.RecastGraph"; public const string TYPENAME_RECAST_GRAPH = "Pathfinding.RecastGraph";
public const string MIN_SUPPORTED_VERSION = "4.0.6"; public const string MIN_SUPPORTED_VERSION = "4.0.6";
public const string UPDATED_STRUCT_VERSION = "4.1.0"; public const string UPDATED_STRUCT_VERSION = "4.1.16";
public static readonly Regex VERSION_PATTERN = new Regex(@"(\d+)\.(\d+)\.(\d+)"); public static readonly Regex VERSION_PATTERN = new Regex(@"(\d+)\.(\d+)\.(\d+)");
public string version { get; set; } public string version { get; set; }
public int graphs { get; set; } public int graphs { get; set; }

View File

@ -1,6 +1,5 @@
/* /*
recast4j Copyright (c) 2015-2019 Piotr Piastucki piotr@jtilia.org recast4j copyright (c) 2015-2019 Piotr Piastucki piotr@jtilia.org
DotRecast Copyright (c) 2023-2024 Choi Ikpil ikpil@naver.com
This software is provided 'as-is', without any express or implied This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages warranty. In no event will the authors be held liable for any damages

View File

@ -1,6 +1,5 @@
/* /*
recast4j Copyright (c) 2015-2019 Piotr Piastucki piotr@jtilia.org recast4j copyright (c) 2015-2019 Piotr Piastucki piotr@jtilia.org
DotRecast Copyright (c) 2023-2024 Choi Ikpil ikpil@naver.com
This software is provided 'as-is', without any express or implied This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages warranty. In no event will the authors be held liable for any damages

View File

@ -1,6 +1,5 @@
/* /*
recast4j Copyright (c) 2015-2019 Piotr Piastucki piotr@jtilia.org recast4j copyright (c) 2015-2019 Piotr Piastucki piotr@jtilia.org
DotRecast Copyright (c) 2023-2024 Choi Ikpil ikpil@naver.com
This software is provided 'as-is', without any express or implied This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages warranty. In no event will the authors be held liable for any damages

View File

@ -1,6 +1,5 @@
/* /*
recast4j Copyright (c) 2015-2019 Piotr Piastucki piotr@jtilia.org recast4j copyright (c) 2015-2019 Piotr Piastucki piotr@jtilia.org
DotRecast Copyright (c) 2023-2024 Choi Ikpil ikpil@naver.com
This software is provided 'as-is', without any express or implied This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages warranty. In no event will the authors be held liable for any damages

View File

@ -1,6 +1,5 @@
/* /*
recast4j Copyright (c) 2015-2019 Piotr Piastucki piotr@jtilia.org recast4j copyright (c) 2015-2019 Piotr Piastucki piotr@jtilia.org
DotRecast Copyright (c) 2023-2024 Choi Ikpil ikpil@naver.com
This software is provided 'as-is', without any express or implied This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages warranty. In no event will the authors be held liable for any damages
@ -38,25 +37,26 @@ namespace DotRecast.Detour.Extras.Unity.Astar
if (startNode != null && endNode != null) if (startNode != null && endNode != null)
{ {
// FIXME: Optimise // FIXME: Optimise
startTile.polys = RcArrays.CopyOf(startTile.polys, startTile.polys.Length + 1); startTile.polys = RcArrayUtils.CopyOf(startTile.polys, startTile.polys.Length + 1);
int poly = startTile.header.polyCount; int poly = startTile.header.polyCount;
startTile.polys[poly] = new DtPoly(poly, 2); startTile.polys[poly] = new DtPoly(poly, 2);
startTile.polys[poly].verts[0] = startTile.header.vertCount; startTile.polys[poly].verts[0] = startTile.header.vertCount;
startTile.polys[poly].verts[1] = startTile.header.vertCount + 1; startTile.polys[poly].verts[1] = startTile.header.vertCount + 1;
startTile.polys[poly].SetPolyType(DtPolyTypes.DT_POLYTYPE_OFFMESH_CONNECTION); startTile.polys[poly].SetPolyType(DtPolyTypes.DT_POLYTYPE_OFFMESH_CONNECTION);
startTile.verts = RcArrays.CopyOf(startTile.verts, startTile.verts.Length + 6); startTile.verts = RcArrayUtils.CopyOf(startTile.verts, startTile.verts.Length + 6);
startTile.header.polyCount++; startTile.header.polyCount++;
startTile.header.vertCount += 2; startTile.header.vertCount += 2;
DtOffMeshConnection connection = new DtOffMeshConnection(); DtOffMeshConnection connection = new DtOffMeshConnection();
connection.poly = poly; connection.poly = poly;
connection.pos = new RcVec3f[] connection.pos = new float[]
{ {
l.clamped1, l.clamped2 l.clamped1.X, l.clamped1.Y, l.clamped1.Z,
l.clamped2.X, l.clamped2.Y, l.clamped2.Z
}; };
connection.rad = 0.1f; connection.rad = 0.1f;
connection.side = startTile == endTile connection.side = startTile == endTile
? 0xFF ? 0xFF
: DtNavMeshBuilder.ClassifyOffMeshPoint(connection.pos[1], startTile.header.bmin, startTile.header.bmax); : DtNavMeshBuilder.ClassifyOffMeshPoint(RcVecUtils.Create(connection.pos, 3), startTile.header.bmin, startTile.header.bmax);
connection.userId = (int)l.linkID; connection.userId = (int)l.linkID;
if (startTile.offMeshCons == null) if (startTile.offMeshCons == null)
{ {
@ -64,7 +64,7 @@ namespace DotRecast.Detour.Extras.Unity.Astar
} }
else else
{ {
startTile.offMeshCons = RcArrays.CopyOf(startTile.offMeshCons, startTile.offMeshCons.Length + 1); startTile.offMeshCons = RcArrayUtils.CopyOf(startTile.offMeshCons, startTile.offMeshCons.Length + 1);
} }
startTile.offMeshCons[startTile.offMeshCons.Length - 1] = connection; startTile.offMeshCons[startTile.offMeshCons.Length - 1] = connection;

View File

@ -1,6 +1,5 @@
/* /*
recast4j Copyright (c) 2015-2019 Piotr Piastucki piotr@jtilia.org recast4j copyright (c) 2015-2019 Piotr Piastucki piotr@jtilia.org
DotRecast Copyright (c) 2023-2024 Choi Ikpil ikpil@naver.com
This software is provided 'as-is', without any express or implied This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages warranty. In no event will the authors be held liable for any damages
@ -49,7 +48,8 @@ namespace DotRecast.Detour.Extras.Unity.Astar
int nodeCount = graphMeshData.CountNodes(); int nodeCount = graphMeshData.CountNodes();
if (connections.Count != nodeCount) if (connections.Count != nodeCount)
{ {
throw new ArgumentException($"Inconsistent number of nodes in data file: {nodeCount} and connection files: {connections.Count}"); throw new ArgumentException("Inconsistent number of nodes in data file: " + nodeCount
+ " and connecton files: " + connections.Count);
} }
// Build BV tree // Build BV tree
@ -66,11 +66,10 @@ namespace DotRecast.Detour.Extras.Unity.Astar
option.orig.X = -0.5f * graphMeta.forcedBoundsSize.x + graphMeta.forcedBoundsCenter.x; option.orig.X = -0.5f * graphMeta.forcedBoundsSize.x + graphMeta.forcedBoundsCenter.x;
option.orig.Y = -0.5f * graphMeta.forcedBoundsSize.y + graphMeta.forcedBoundsCenter.y; option.orig.Y = -0.5f * graphMeta.forcedBoundsSize.y + graphMeta.forcedBoundsCenter.y;
option.orig.Z = -0.5f * graphMeta.forcedBoundsSize.z + graphMeta.forcedBoundsCenter.z; option.orig.Z = -0.5f * graphMeta.forcedBoundsSize.z + graphMeta.forcedBoundsCenter.z;
DtNavMesh mesh = new DtNavMesh(); DtNavMesh mesh = new DtNavMesh(option, 3);
mesh.Init(option, 3);
foreach (DtMeshData t in graphMeshData.tiles) foreach (DtMeshData t in graphMeshData.tiles)
{ {
mesh.AddTile(t, 0, 0, out _); mesh.AddTile(t, 0, 0);
} }
meshes[graphIndex] = mesh; meshes[graphIndex] = mesh;

View File

@ -1,6 +1,6 @@
/* /*
recast4j copyright (c) 2021 Piotr Piastucki piotr@jtilia.org recast4j copyright (c) 2021 Piotr Piastucki piotr@jtilia.org
DotRecast Copyright (c) 2023-2024 Choi Ikpil ikpil@naver.com DotRecast Copyright (c) 2023 Choi Ikpil ikpil@naver.com
This software is provided 'as-is', without any express or implied This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages warranty. In no event will the authors be held liable for any damages

View File

@ -1,6 +1,5 @@
/* /*
recast4j Copyright (c) 2015-2019 Piotr Piastucki piotr@jtilia.org recast4j copyright (c) 2015-2019 Piotr Piastucki piotr@jtilia.org
DotRecast Copyright (c) 2023-2024 Choi Ikpil ikpil@naver.com
This software is provided 'as-is', without any express or implied This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages warranty. In no event will the authors be held liable for any damages
@ -32,7 +31,7 @@ namespace DotRecast.Detour.Extras.Unity.Astar
ZipArchiveEntry graphReferences = file.GetEntry(filename); ZipArchiveEntry graphReferences = file.GetEntry(filename);
using var entryStream = graphReferences.Open(); using var entryStream = graphReferences.Open();
using var br = new BinaryReader(entryStream); using var br = new BinaryReader(entryStream);
RcByteBuffer buffer = RcIO.ToByteBuffer(br); RcByteBuffer buffer = IOUtils.ToByteBuffer(br);
buffer.Order(RcByteOrder.LITTLE_ENDIAN); buffer.Order(RcByteOrder.LITTLE_ENDIAN);
return buffer; return buffer;
} }

View File

@ -1,18 +0,0 @@
{
"name": "DotRecast.Detour.TileCache",
"rootNamespace": "DotRecast.Detour.TileCache",
"references": [
"DotRecast.Core",
"DotRecast.Detour",
"DotRecast.Recast"
],
"includePlatforms": [],
"excludePlatforms": [],
"allowUnsafeCode": false,
"overrideReferences": false,
"precompiledReferences": [],
"autoReferenced": true,
"defineConstraints": [],
"versionDefines": [],
"noEngineReferences": true
}

View File

@ -1,16 +1,15 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<TargetFrameworks>netstandard2.1;net6.0;net7.0;net8.0</TargetFrameworks> <TargetFrameworks>netstandard2.1;net6.0;net7.0</TargetFrameworks>
<PackageId>DotRecast.Detour.TileCache</PackageId> <PackageId>DotRecast.Detour.TileCache</PackageId>
<PackageReadmeFile>README.md</PackageReadmeFile> <PackageReadmeFile>README.md</PackageReadmeFile>
<Authors>ikpil</Authors> <Authors>ikpil</Authors>
<Description>DotRecast - a port of Recast Detour, Industry-standard navigation mesh toolset for .NET, C#, Unity3D, games, servers</Description> <Description>DotRecast - a port of Recast Detour, navigation mesh toolset for games, Unity3D, servers, C#</Description>
<RepositoryType>git</RepositoryType> <RepositoryType>git</RepositoryType>
<PackageProjectUrl>https://github.com/ikpil/DotRecast</PackageProjectUrl> <PackageProjectUrl>https://github.com/ikpil/DotRecast</PackageProjectUrl>
<RepositoryUrl>https://github.com/ikpil/DotRecast</RepositoryUrl> <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> <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> </PropertyGroup>
<ItemGroup> <ItemGroup>

View File

@ -1,7 +1,7 @@
/* /*
Copyright (c) 2009-2010 Mikko Mononen memon@inside.org Copyright (c) 2009-2010 Mikko Mononen memon@inside.org
recast4j copyright (c) 2015-2019 Piotr Piastucki piotr@jtilia.org recast4j copyright (c) 2015-2019 Piotr Piastucki piotr@jtilia.org
DotRecast Copyright (c) 2023-2024 Choi Ikpil ikpil@naver.com DotRecast Copyright (c) 2023 Choi Ikpil ikpil@naver.com
This software is provided 'as-is', without any express or implied This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages warranty. In no event will the authors be held liable for any damages

View File

@ -1,4 +1,4 @@
namespace DotRecast.Detour.TileCache namespace DotRecast.Detour.TileCache
{ {
/// Flags for addTile /// Flags for addTile
public class DtCompressedTileFlags public class DtCompressedTileFlags

View File

@ -1,4 +1,4 @@
using System.Collections.Generic; using System.Collections.Generic;
namespace DotRecast.Detour.TileCache namespace DotRecast.Detour.TileCache
{ {
@ -7,9 +7,8 @@ namespace DotRecast.Detour.TileCache
public const int DT_LAYER_MAX_NEIS = 16; public const int DT_LAYER_MAX_NEIS = 16;
public int area; public int area;
public byte[] neis = new byte[DT_LAYER_MAX_NEIS]; public List<int> neis = new List<int>(DT_LAYER_MAX_NEIS);
public byte nneis; public int regId;
public byte regId; public int areaId;
public byte areaId;
}; };
} }

View File

@ -0,0 +1,9 @@
namespace DotRecast.Detour.TileCache
{
public class DtLayerSweepSpan
{
public int ns; // number samples
public int id; // region id
public int nei; // neighbour id
};
}

View File

@ -1,10 +0,0 @@
using DotRecast.Core.Numerics;
namespace DotRecast.Detour.TileCache
{
public class DtObstacleBox
{
public RcVec3f bmin;
public RcVec3f bmax;
}
}

View File

@ -1,11 +0,0 @@
using DotRecast.Core.Numerics;
namespace DotRecast.Detour.TileCache
{
public class DtObstacleCylinder
{
public RcVec3f pos;
public float radius;
public float height;
}
}

View File

@ -1,11 +0,0 @@
using DotRecast.Core.Numerics;
namespace DotRecast.Detour.TileCache
{
public class DtObstacleOrientedBox
{
public RcVec3f center;
public RcVec3f extents;
public readonly float[] rotAux = new float[2]; // { Cos(0.5f*angle)*Sin(-0.5f*angle); Cos(0.5f*angle)*Cos(0.5f*angle) - 0.5 }
}
}

View File

@ -1,7 +1,7 @@
/* /*
Copyright (c) 2009-2010 Mikko Mononen memon@inside.org Copyright (c) 2009-2010 Mikko Mononen memon@inside.org
recast4j copyright (c) 2015-2019 Piotr Piastucki piotr@jtilia.org recast4j copyright (c) 2015-2019 Piotr Piastucki piotr@jtilia.org
DotRecast Copyright (c) 2023-2024 Choi Ikpil ikpil@naver.com DotRecast Copyright (c) 2023 Choi Ikpil ikpil@naver.com
This software is provided 'as-is', without any express or implied This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages warranty. In no event will the authors be held liable for any damages

View File

@ -1,7 +1,7 @@
/* /*
Copyright (c) 2009-2010 Mikko Mononen memon@inside.org Copyright (c) 2009-2010 Mikko Mononen memon@inside.org
recast4j copyright (c) 2015-2019 Piotr Piastucki piotr@jtilia.org recast4j copyright (c) 2015-2019 Piotr Piastucki piotr@jtilia.org
DotRecast Copyright (c) 2023-2024 Choi Ikpil ikpil@naver.com DotRecast Copyright (c) 2023 Choi Ikpil ikpil@naver.com
This software is provided 'as-is', without any express or implied This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages warranty. In no event will the authors be held liable for any damages

View File

@ -1,7 +1,7 @@
/* /*
Copyright (c) 2009-2010 Mikko Mononen memon@inside.org Copyright (c) 2009-2010 Mikko Mononen memon@inside.org
recast4j copyright (c) 2015-2019 Piotr Piastucki piotr@jtilia.org recast4j copyright (c) 2015-2019 Piotr Piastucki piotr@jtilia.org
DotRecast Copyright (c) 2023-2024 Choi Ikpil ikpil@naver.com DotRecast Copyright (c) 2023 Choi Ikpil ikpil@naver.com
This software is provided 'as-is', without any express or implied This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages warranty. In no event will the authors be held liable for any damages

View File

@ -1,4 +1,4 @@
using System.Collections.Generic; using System.Collections.Generic;
namespace DotRecast.Detour.TileCache namespace DotRecast.Detour.TileCache
{ {

View File

@ -1,7 +1,7 @@
/* /*
Copyright (c) 2009-2010 Mikko Mononen memon@inside.org Copyright (c) 2009-2010 Mikko Mononen memon@inside.org
recast4j copyright (c) 2015-2019 Piotr Piastucki piotr@jtilia.org recast4j copyright (c) 2015-2019 Piotr Piastucki piotr@jtilia.org
DotRecast Copyright (c) 2023-2024 Choi Ikpil ikpil@naver.com DotRecast Copyright (c) 2023 Choi Ikpil ikpil@naver.com
This software is provided 'as-is', without any express or implied This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages warranty. In no event will the authors be held liable for any damages
@ -26,8 +26,6 @@ using DotRecast.Detour.TileCache.Io;
namespace DotRecast.Detour.TileCache namespace DotRecast.Detour.TileCache
{ {
using static DtDetour;
public class DtTileCache public class DtTileCache
{ {
private int m_tileLutSize; // < Tile hash lookup size (must be pot). private int m_tileLutSize; // < Tile hash lookup size (must be pot).
@ -53,6 +51,9 @@ namespace DotRecast.Detour.TileCache
private readonly List<DtObstacleRequest> m_reqs = new List<DtObstacleRequest>(); private readonly List<DtObstacleRequest> m_reqs = new List<DtObstacleRequest>();
private readonly List<long> m_update = new List<long>(); private readonly List<long> m_update = new List<long>();
private readonly DtTileCacheBuilder builder = new DtTileCacheBuilder();
private readonly DtTileCacheLayerHeaderReader tileReader = new DtTileCacheLayerHeaderReader();
public DtTileCache(DtTileCacheParams option, DtTileCacheStorageParams storageParams, DtNavMesh navmesh, IRcCompressor tcomp, IDtTileCacheMeshProcess tmprocs) public DtTileCache(DtTileCacheParams option, DtTileCacheStorageParams storageParams, DtNavMesh navmesh, IRcCompressor tcomp, IDtTileCacheMeshProcess tmprocs)
{ {
m_params = option; m_params = option;
@ -159,7 +160,7 @@ namespace DotRecast.Detour.TileCache
List<long> tiles = new List<long>(); List<long> tiles = new List<long>();
// Find tile based on hash. // Find tile based on hash.
int h = ComputeTileHash(tx, ty, m_tileLutMask); int h = DtNavMesh.ComputeTileHash(tx, ty, m_tileLutMask);
DtCompressedTile tile = m_posLookup[h]; DtCompressedTile tile = m_posLookup[h];
while (tile != null) while (tile != null)
{ {
@ -177,7 +178,7 @@ namespace DotRecast.Detour.TileCache
DtCompressedTile GetTileAt(int tx, int ty, int tlayer) DtCompressedTile GetTileAt(int tx, int ty, int tlayer)
{ {
// Find tile based on hash. // Find tile based on hash.
int h = ComputeTileHash(tx, ty, m_tileLutMask); int h = DtNavMesh.ComputeTileHash(tx, ty, m_tileLutMask);
DtCompressedTile tile = m_posLookup[h]; DtCompressedTile tile = m_posLookup[h];
while (tile != null) while (tile != null)
{ {
@ -242,7 +243,7 @@ namespace DotRecast.Detour.TileCache
// Make sure the data is in right format. // Make sure the data is in right format.
RcByteBuffer buf = new RcByteBuffer(data); RcByteBuffer buf = new RcByteBuffer(data);
buf.Order(m_storageParams.Order); buf.Order(m_storageParams.Order);
DtTileCacheLayerHeader header = DtTileCacheLayerHeaderReader.Read(buf, m_storageParams.Compatibility); DtTileCacheLayerHeader header = tileReader.Read(buf, m_storageParams.Compatibility);
// Make sure the location is free. // Make sure the location is free.
if (GetTileAt(header.tx, header.ty, header.tlayer) != null) if (GetTileAt(header.tx, header.ty, header.tlayer) != null)
{ {
@ -265,7 +266,7 @@ namespace DotRecast.Detour.TileCache
} }
// Insert tile into the position lut. // Insert tile into the position lut.
int h = ComputeTileHash(header.tx, header.ty, m_tileLutMask); int h = DtNavMesh.ComputeTileHash(header.tx, header.ty, m_tileLutMask);
tile.next = m_posLookup[h]; tile.next = m_posLookup[h];
m_posLookup[h] = tile; m_posLookup[h] = tile;
@ -304,7 +305,7 @@ namespace DotRecast.Detour.TileCache
} }
// Remove tile from hash lookup. // Remove tile from hash lookup.
int h = ComputeTileHash(tile.header.tx, tile.header.ty, m_tileLutMask); int h = DtNavMesh.ComputeTileHash(tile.header.tx, tile.header.ty, m_tileLutMask);
DtCompressedTile prev = null; DtCompressedTile prev = null;
DtCompressedTile cur = m_posLookup[h]; DtCompressedTile cur = m_posLookup[h];
while (cur != null) while (cur != null)
@ -348,11 +349,11 @@ namespace DotRecast.Detour.TileCache
public long AddObstacle(RcVec3f pos, float radius, float height) public long AddObstacle(RcVec3f pos, float radius, float height)
{ {
DtTileCacheObstacle ob = AllocObstacle(); DtTileCacheObstacle ob = AllocObstacle();
ob.type = DtTileCacheObstacleType.DT_OBSTACLE_CYLINDER; ob.type = DtTileCacheObstacleType.CYLINDER;
ob.cylinder.pos = pos; ob.pos = pos;
ob.cylinder.radius = radius; ob.radius = radius;
ob.cylinder.height = height; ob.height = height;
return AddObstacleRequest(ob).refs; return AddObstacleRequest(ob).refs;
} }
@ -361,10 +362,10 @@ namespace DotRecast.Detour.TileCache
public long AddBoxObstacle(RcVec3f bmin, RcVec3f bmax) public long AddBoxObstacle(RcVec3f bmin, RcVec3f bmax)
{ {
DtTileCacheObstacle ob = AllocObstacle(); DtTileCacheObstacle ob = AllocObstacle();
ob.type = DtTileCacheObstacleType.DT_OBSTACLE_BOX; ob.type = DtTileCacheObstacleType.BOX;
ob.box.bmin = bmin; ob.bmin = bmin;
ob.box.bmax = bmax; ob.bmax = bmax;
return AddObstacleRequest(ob).refs; return AddObstacleRequest(ob).refs;
} }
@ -373,13 +374,13 @@ namespace DotRecast.Detour.TileCache
public long AddBoxObstacle(RcVec3f center, RcVec3f extents, float yRadians) public long AddBoxObstacle(RcVec3f center, RcVec3f extents, float yRadians)
{ {
DtTileCacheObstacle ob = AllocObstacle(); DtTileCacheObstacle ob = AllocObstacle();
ob.type = DtTileCacheObstacleType.DT_OBSTACLE_ORIENTED_BOX; ob.type = DtTileCacheObstacleType.ORIENTED_BOX;
ob.orientedBox.center = center; ob.center = center;
ob.orientedBox.extents = extents; ob.extents = extents;
float coshalf = MathF.Cos(0.5f * yRadians); float coshalf = MathF.Cos(0.5f * yRadians);
float sinhalf = MathF.Sin(-0.5f * yRadians); float sinhalf = MathF.Sin(-0.5f * yRadians);
ob.orientedBox.rotAux[0] = coshalf * sinhalf; ob.rotAux[0] = coshalf * sinhalf;
ob.orientedBox.rotAux[1] = coshalf * coshalf - 0.5f; ob.rotAux[1] = coshalf * coshalf - 0.5f;
return AddObstacleRequest(ob).refs; return AddObstacleRequest(ob).refs;
} }
@ -612,26 +613,26 @@ namespace DotRecast.Detour.TileCache
if (Contains(ob.touched, refs)) if (Contains(ob.touched, refs))
{ {
if (ob.type == DtTileCacheObstacleType.DT_OBSTACLE_CYLINDER) if (ob.type == DtTileCacheObstacleType.CYLINDER)
{ {
DtTileCacheBuilder.MarkCylinderArea(layer, tile.header.bmin, m_params.cs, m_params.ch, ob.cylinder.pos, ob.cylinder.radius, ob.cylinder.height, 0); builder.MarkCylinderArea(layer, tile.header.bmin, m_params.cs, m_params.ch, ob.pos, ob.radius, ob.height, 0);
} }
else if (ob.type == DtTileCacheObstacleType.DT_OBSTACLE_BOX) else if (ob.type == DtTileCacheObstacleType.BOX)
{ {
DtTileCacheBuilder.MarkBoxArea(layer, tile.header.bmin, m_params.cs, m_params.ch, ob.box.bmin, ob.box.bmax, 0); builder.MarkBoxArea(layer, tile.header.bmin, m_params.cs, m_params.ch, ob.bmin, ob.bmax, 0);
} }
else if (ob.type == DtTileCacheObstacleType.DT_OBSTACLE_ORIENTED_BOX) else if (ob.type == DtTileCacheObstacleType.ORIENTED_BOX)
{ {
DtTileCacheBuilder.MarkBoxArea(layer, tile.header.bmin, m_params.cs, m_params.ch, ob.orientedBox.center, ob.orientedBox.extents, ob.orientedBox.rotAux, 0); builder.MarkBoxArea(layer, tile.header.bmin, m_params.cs, m_params.ch, ob.center, ob.extents, ob.rotAux, 0);
} }
} }
} }
// Build navmesh // Build navmesh
DtTileCacheBuilder.BuildTileCacheRegions(layer, walkableClimbVx); builder.BuildTileCacheRegions(layer, walkableClimbVx);
DtTileCacheContourSet lcset = DtTileCacheBuilder.BuildTileCacheContours(layer, walkableClimbVx, m_params.maxSimplificationError); DtTileCacheContourSet lcset = builder.BuildTileCacheContours(layer, walkableClimbVx,
DtTileCachePolyMesh polyMesh = DtTileCacheBuilder.BuildTileCachePolyMesh(lcset, m_navmesh.GetMaxVertsPerPoly()); m_params.maxSimplificationError);
DtTileCachePolyMesh polyMesh = builder.BuildTileCachePolyMesh(lcset, m_navmesh.GetMaxVertsPerPoly());
// Early out if the mesh tile is empty. // Early out if the mesh tile is empty.
if (polyMesh.npolys == 0) if (polyMesh.npolys == 0)
{ {
@ -669,13 +670,13 @@ namespace DotRecast.Detour.TileCache
// Add new tile, or leave the location empty. if (navData) { // Let the // Add new tile, or leave the location empty. if (navData) { // Let the
if (meshData != null) if (meshData != null)
{ {
m_navmesh.AddTile(meshData, 0, 0, out var result); m_navmesh.AddTile(meshData, 0, 0);
} }
} }
public DtTileCacheLayer DecompressTile(DtCompressedTile tile) public DtTileCacheLayer DecompressTile(DtCompressedTile tile)
{ {
DtTileCacheLayer layer = DtTileCacheBuilder.DecompressTileCacheLayer(m_tcomp, tile.data, m_storageParams.Order, m_storageParams.Compatibility); DtTileCacheLayer layer = builder.DecompressTileCacheLayer(m_tcomp, tile.data, m_storageParams.Order, m_storageParams.Compatibility);
return layer; return layer;
} }
@ -692,29 +693,29 @@ namespace DotRecast.Detour.TileCache
public void GetObstacleBounds(DtTileCacheObstacle ob, ref RcVec3f bmin, ref RcVec3f bmax) public void GetObstacleBounds(DtTileCacheObstacle ob, ref RcVec3f bmin, ref RcVec3f bmax)
{ {
if (ob.type == DtTileCacheObstacleType.DT_OBSTACLE_CYLINDER) if (ob.type == DtTileCacheObstacleType.CYLINDER)
{ {
bmin.X = ob.cylinder.pos.X - ob.cylinder.radius; bmin.X = ob.pos.X - ob.radius;
bmin.Y = ob.cylinder.pos.Y; bmin.Y = ob.pos.Y;
bmin.Z = ob.cylinder.pos.Z - ob.cylinder.radius; bmin.Z = ob.pos.Z - ob.radius;
bmax.X = ob.cylinder.pos.X + ob.cylinder.radius; bmax.X = ob.pos.X + ob.radius;
bmax.Y = ob.cylinder.pos.Y + ob.cylinder.height; bmax.Y = ob.pos.Y + ob.height;
bmax.Z = ob.cylinder.pos.Z + ob.cylinder.radius; bmax.Z = ob.pos.Z + ob.radius;
} }
else if (ob.type == DtTileCacheObstacleType.DT_OBSTACLE_BOX) else if (ob.type == DtTileCacheObstacleType.BOX)
{ {
bmin = ob.box.bmin; bmin = ob.bmin;
bmax = ob.box.bmax; bmax = ob.bmax;
} }
else if (ob.type == DtTileCacheObstacleType.DT_OBSTACLE_ORIENTED_BOX) else if (ob.type == DtTileCacheObstacleType.ORIENTED_BOX)
{ {
float maxr = 1.41f * Math.Max(ob.orientedBox.extents.X, ob.orientedBox.extents.Z); float maxr = 1.41f * Math.Max(ob.extents.X, ob.extents.Z);
bmin.X = ob.orientedBox.center.X - maxr; bmin.X = ob.center.X - maxr;
bmax.X = ob.orientedBox.center.X + maxr; bmax.X = ob.center.X + maxr;
bmin.Y = ob.orientedBox.center.Y - ob.orientedBox.extents.Y; bmin.Y = ob.center.Y - ob.extents.Y;
bmax.Y = ob.orientedBox.center.Y + ob.orientedBox.extents.Y; bmax.Y = ob.center.Y + ob.extents.Y;
bmin.Z = ob.orientedBox.center.Z - maxr; bmin.Z = ob.center.Z - maxr;
bmax.Z = ob.orientedBox.center.Z + maxr; bmax.Z = ob.center.Z + maxr;
} }
} }

View File

@ -1,7 +1,7 @@
/* /*
Copyright (c) 2009-2010 Mikko Mononen memon@inside.org Copyright (c) 2009-2010 Mikko Mononen memon@inside.org
recast4j copyright (c) 2015-2019 Piotr Piastucki piotr@jtilia.org recast4j copyright (c) 2015-2019 Piotr Piastucki piotr@jtilia.org
DotRecast Copyright (c) 2023-2024 Choi Ikpil ikpil@naver.com DotRecast Copyright (c) 2023 Choi Ikpil ikpil@naver.com
This software is provided 'as-is', without any express or implied This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages warranty. In no event will the authors be held liable for any damages
@ -24,42 +24,43 @@ using System.IO;
using DotRecast.Core; using DotRecast.Core;
using DotRecast.Core.Numerics; using DotRecast.Core.Numerics;
using DotRecast.Detour.TileCache.Io; using DotRecast.Detour.TileCache.Io;
using DotRecast.Recast; using DotRecast.Detour.TileCache.Io.Compress;
namespace DotRecast.Detour.TileCache namespace DotRecast.Detour.TileCache
{ {
public static class DtTileCacheBuilder public class DtTileCacheBuilder
{ {
public const byte DT_TILECACHE_NULL_AREA = 0; public const int DT_TILECACHE_NULL_AREA = 0;
public const byte DT_TILECACHE_WALKABLE_AREA = 63; public const int DT_TILECACHE_WALKABLE_AREA = 63;
public const int DT_TILECACHE_NULL_IDX = 0xffff; public const int DT_TILECACHE_NULL_IDX = 0xffff;
private static readonly int[] DirOffsetX = { -1, 0, 1, 0, }; private static readonly int[] DirOffsetX = { -1, 0, 1, 0, };
private static readonly int[] DirOffsetY = { 0, 1, 0, -1 }; private static readonly int[] DirOffsetY = { 0, 1, 0, -1 };
public static void BuildTileCacheRegions(DtTileCacheLayer layer, int walkableClimb) private readonly DtTileCacheLayerHeaderReader reader = new DtTileCacheLayerHeaderReader();
public void BuildTileCacheRegions(DtTileCacheLayer layer, int walkableClimb)
{ {
int w = layer.header.width; int w = layer.header.width;
int h = layer.header.height; int h = layer.header.height;
Array.Fill(layer.regs, (byte)0xFF); Array.Fill(layer.regs, (short)0x00FF);
int nsweeps = w; int nsweeps = w;
RcLayerSweepSpan[] sweeps = new RcLayerSweepSpan[nsweeps]; DtLayerSweepSpan[] sweeps = new DtLayerSweepSpan[nsweeps];
for (int i = 0; i < sweeps.Length; i++) for (int i = 0; i < sweeps.Length; i++)
{ {
sweeps[i] = new RcLayerSweepSpan(); sweeps[i] = new DtLayerSweepSpan();
} }
// Partition walkable area into monotone regions. // Partition walkable area into monotone regions.
Span<byte> prevCount = stackalloc byte[256]; int[] prevCount = new int[256];
byte regId = 0; int regId = 0;
for (int y = 0; y < h; ++y) for (int y = 0; y < h; ++y)
{ {
if (regId > 0) if (regId > 0)
{ {
RcSpans.Fill<byte>(prevCount, 0, 0, regId); Array.Fill(prevCount, 0, 0, regId);
} }
// Memset(prevCount,0,Sizeof(char)*regId); // Memset(prevCount,0,Sizeof(char)*regId);
@ -92,7 +93,7 @@ namespace DotRecast.Detour.TileCache
int yidx = x + (y - 1) * w; int yidx = x + (y - 1) * w;
if (y > 0 && IsConnected(layer, idx, yidx, walkableClimb)) if (y > 0 && IsConnected(layer, idx, yidx, walkableClimb))
{ {
byte nr = layer.regs[yidx]; int nr = layer.regs[yidx];
if (nr != 0xff) if (nr != 0xff)
{ {
// Set neighbour when first valid neighbour is // Set neighbour when first valid neighbour is
@ -146,12 +147,12 @@ namespace DotRecast.Detour.TileCache
{ {
int idx = x + y * w; int idx = x + y * w;
if (layer.regs[idx] != 0xff) if (layer.regs[idx] != 0xff)
layer.regs[idx] = sweeps[layer.regs[idx]].id; layer.regs[idx] = (short)sweeps[layer.regs[idx]].id;
} }
} }
// Allocate and init layer regions. // Allocate and init layer regions.
byte nregs = regId; int nregs = regId;
DtLayerMonotoneRegion[] regs = new DtLayerMonotoneRegion[nregs]; DtLayerMonotoneRegion[] regs = new DtLayerMonotoneRegion[nregs];
for (int i = 0; i < nregs; ++i) for (int i = 0; i < nregs; ++i)
@ -166,7 +167,7 @@ namespace DotRecast.Detour.TileCache
for (int x = 0; x < w; ++x) for (int x = 0; x < w; ++x)
{ {
int idx = x + y * w; int idx = x + y * w;
byte ri = layer.regs[idx]; int ri = layer.regs[idx];
if (ri == 0xff) if (ri == 0xff)
continue; continue;
@ -178,17 +179,17 @@ namespace DotRecast.Detour.TileCache
int ymi = x + (y - 1) * w; int ymi = x + (y - 1) * w;
if (y > 0 && IsConnected(layer, idx, ymi, walkableClimb)) if (y > 0 && IsConnected(layer, idx, ymi, walkableClimb))
{ {
byte rai = layer.regs[ymi]; int rai = layer.regs[ymi];
if (rai != 0xff && rai != ri) if (rai != 0xff && rai != ri)
{ {
AddUniqueLast(regs[ri].neis, ref regs[ri].nneis, rai); AddUniqueLast(regs[ri].neis, rai);
AddUniqueLast(regs[rai].neis, ref regs[rai].nneis, ri); AddUniqueLast(regs[rai].neis, ri);
} }
} }
} }
} }
for (byte i = 0; i < nregs; ++i) for (int i = 0; i < nregs; ++i)
regs[i].regId = i; regs[i].regId = i;
for (int i = 0; i < nregs; ++i) for (int i = 0; i < nregs; ++i)
@ -197,9 +198,8 @@ namespace DotRecast.Detour.TileCache
int merge = -1; int merge = -1;
int mergea = 0; int mergea = 0;
for (int j = 0; j < reg.nneis; ++j) foreach (int nei in reg.neis)
{ {
byte nei = reg.neis[j];
DtLayerMonotoneRegion regn = regs[nei]; DtLayerMonotoneRegion regn = regs[nei];
if (reg.regId == regn.regId) if (reg.regId == regn.regId)
continue; continue;
@ -218,7 +218,7 @@ namespace DotRecast.Detour.TileCache
if (merge != -1) if (merge != -1)
{ {
int oldId = reg.regId; int oldId = reg.regId;
byte newId = regs[merge].regId; int newId = regs[merge].regId;
for (int j = 0; j < nregs; ++j) for (int j = 0; j < nregs; ++j)
if (regs[j].regId == oldId) if (regs[j].regId == oldId)
regs[j].regId = newId; regs[j].regId = newId;
@ -226,7 +226,7 @@ namespace DotRecast.Detour.TileCache
} }
// Compact ids. // Compact ids.
Span<byte> remap = stackalloc byte[256]; int[] remap = new int[256];
// Find number of unique regions. // Find number of unique regions.
regId = 0; regId = 0;
for (int i = 0; i < nregs; ++i) for (int i = 0; i < nregs; ++i)
@ -243,20 +243,19 @@ namespace DotRecast.Detour.TileCache
for (int i = 0; i < w * h; ++i) for (int i = 0; i < w * h; ++i)
{ {
if (layer.regs[i] != 0xff) if (layer.regs[i] != 0xff)
layer.regs[i] = regs[layer.regs[i]].regId; layer.regs[i] = (short)regs[layer.regs[i]].regId;
} }
} }
public static void AddUniqueLast(byte[] a, ref byte an, byte v) void AddUniqueLast(List<int> a, int v)
{ {
int n = an; int n = a.Count;
if (n > 0 && a[n - 1] == v) if (n > 0 && a[n - 1] == v)
return; return;
a[an] = v; a.Add(v);
an++;
} }
public static bool IsConnected(DtTileCacheLayer layer, int ia, int ib, int walkableClimb) bool IsConnected(DtTileCacheLayer layer, int ia, int ib, int walkableClimb)
{ {
if (layer.areas[ia] != layer.areas[ib]) if (layer.areas[ia] != layer.areas[ib])
return false; return false;
@ -265,7 +264,7 @@ namespace DotRecast.Detour.TileCache
return true; return true;
} }
public static bool CanMerge(int oldRegId, int newRegId, DtLayerMonotoneRegion[] regs, int nregs) bool CanMerge(int oldRegId, int newRegId, DtLayerMonotoneRegion[] regs, int nregs)
{ {
int count = 0; int count = 0;
for (int i = 0; i < nregs; ++i) for (int i = 0; i < nregs; ++i)
@ -273,11 +272,9 @@ namespace DotRecast.Detour.TileCache
DtLayerMonotoneRegion reg = regs[i]; DtLayerMonotoneRegion reg = regs[i];
if (reg.regId != oldRegId) if (reg.regId != oldRegId)
continue; continue;
foreach (int nei in reg.neis)
int nnei = reg.nneis;
for (int j = 0; j < nnei ; ++j)
{ {
if (regs[reg.neis[j]].regId == newRegId) if (regs[nei].regId == newRegId)
count++; count++;
} }
} }
@ -285,7 +282,7 @@ namespace DotRecast.Detour.TileCache
return count == 1; return count == 1;
} }
public static void AppendVertex(DtTempContour cont, int x, int y, int z, int r) private void AppendVertex(DtTempContour cont, int x, int y, int z, int r)
{ {
// Try to merge with existing segments. // Try to merge with existing segments.
if (cont.nverts > 1) if (cont.nverts > 1)
@ -319,7 +316,7 @@ namespace DotRecast.Detour.TileCache
cont.nverts++; cont.nverts++;
} }
public static int GetNeighbourReg(DtTileCacheLayer layer, int ax, int ay, int dir) private int GetNeighbourReg(DtTileCacheLayer layer, int ax, int ay, int dir)
{ {
int w = layer.header.width; int w = layer.header.width;
int ia = ax + ay * w; int ia = ax + ay * w;
@ -342,17 +339,17 @@ namespace DotRecast.Detour.TileCache
return layer.regs[ib]; return layer.regs[ib];
} }
public static int GetDirOffsetX(int dir) private int GetDirOffsetX(int dir)
{ {
return DirOffsetX[dir & 0x03]; return DirOffsetX[dir & 0x03];
} }
public static int GetDirOffsetY(int dir) private int GetDirOffsetY(int dir)
{ {
return DirOffsetY[dir & 0x03]; return DirOffsetY[dir & 0x03];
} }
public static void WalkContour(DtTileCacheLayer layer, int x, int y, DtTempContour cont) private void WalkContour(DtTileCacheLayer layer, int x, int y, DtTempContour cont)
{ {
int w = layer.header.width; int w = layer.header.width;
int h = layer.header.height; int h = layer.header.height;
@ -437,7 +434,7 @@ namespace DotRecast.Detour.TileCache
cont.nverts--; cont.nverts--;
} }
public static float DistancePtSeg(int x, int z, int px, int pz, int qx, int qz) private float DistancePtSeg(int x, int z, int px, int pz, int qx, int qz)
{ {
float pqx = qx - px; float pqx = qx - px;
float pqz = qz - pz; float pqz = qz - pz;
@ -458,7 +455,7 @@ namespace DotRecast.Detour.TileCache
return dx * dx + dz * dz; return dx * dx + dz * dz;
} }
public static void SimplifyContour(DtTempContour cont, float maxError) private void SimplifyContour(DtTempContour cont, float maxError)
{ {
cont.poly.Clear(); cont.poly.Clear();
@ -587,7 +584,7 @@ namespace DotRecast.Detour.TileCache
} }
} }
public static int GetCornerHeight(DtTileCacheLayer layer, int x, int y, int z, int walkableClimb, out bool shouldRemove) static int GetCornerHeight(DtTileCacheLayer layer, int x, int y, int z, int walkableClimb, out bool shouldRemove)
{ {
int w = layer.header.width; int w = layer.header.width;
int h = layer.header.height; int h = layer.header.height;
@ -637,7 +634,7 @@ namespace DotRecast.Detour.TileCache
} }
// TODO: move this somewhere else, once the layer meshing is done. // TODO: move this somewhere else, once the layer meshing is done.
public static DtTileCacheContourSet BuildTileCacheContours(DtTileCacheLayer layer, int walkableClimb, float maxError) public DtTileCacheContourSet BuildTileCacheContours(DtTileCacheLayer layer, int walkableClimb, float maxError)
{ {
int w = layer.header.width; int w = layer.header.width;
int h = layer.header.height; int h = layer.header.height;
@ -659,7 +656,7 @@ namespace DotRecast.Detour.TileCache
for (int x = 0; x < w; ++x) for (int x = 0; x < w; ++x)
{ {
int idx = x + y * w; int idx = x + y * w;
byte ri = layer.regs[idx]; int ri = layer.regs[idx];
if (ri == 0xff) if (ri == 0xff)
continue; continue;
@ -714,7 +711,7 @@ namespace DotRecast.Detour.TileCache
const uint VERTEX_BUCKET_COUNT2 = (1 << 8); const uint VERTEX_BUCKET_COUNT2 = (1 << 8);
public static int ComputeVertexHash2(int x, int y, int z) private int ComputeVertexHash2(int x, int y, int z)
{ {
uint h1 = 0x8da6b343; // Large multiplicative constants; uint h1 = 0x8da6b343; // Large multiplicative constants;
uint h2 = 0xd8163841; // here arbitrarily chosen primes uint h2 = 0xd8163841; // here arbitrarily chosen primes
@ -723,7 +720,7 @@ namespace DotRecast.Detour.TileCache
return (int)(n & (VERTEX_BUCKET_COUNT2 - 1)); return (int)(n & (VERTEX_BUCKET_COUNT2 - 1));
} }
public static int AddVertex(int x, int y, int z, int[] verts, int[] firstVert, int[] nextVert, int nv) private int AddVertex(int x, int y, int z, int[] verts, int[] firstVert, int[] nextVert, int nv)
{ {
int bucket = ComputeVertexHash2(x, 0, z); int bucket = ComputeVertexHash2(x, 0, z);
int i = firstVert[bucket]; int i = firstVert[bucket];
@ -746,7 +743,7 @@ namespace DotRecast.Detour.TileCache
return i; return i;
} }
public static void BuildMeshAdjacency(int[] polys, int npolys, int[] verts, int nverts, DtTileCacheContourSet lcset, private void BuildMeshAdjacency(int[] polys, int npolys, int[] verts, int nverts, DtTileCacheContourSet lcset,
int maxVertsPerPoly) int maxVertsPerPoly)
{ {
// Based on code by Eric Lengyel from: // Based on code by Eric Lengyel from:
@ -957,22 +954,22 @@ namespace DotRecast.Detour.TileCache
} }
} }
public static bool OverlapRangeExl(int amin, int amax, int bmin, int bmax) private bool OverlapRangeExl(int amin, int amax, int bmin, int bmax)
{ {
return (amin >= bmax || amax <= bmin) ? false : true; return (amin >= bmax || amax <= bmin) ? false : true;
} }
public static int Prev(int i, int n) private int Prev(int i, int n)
{ {
return i - 1 >= 0 ? i - 1 : n - 1; return i - 1 >= 0 ? i - 1 : n - 1;
} }
public static int Next(int i, int n) private int Next(int i, int n)
{ {
return i + 1 < n ? i + 1 : 0; return i + 1 < n ? i + 1 : 0;
} }
public static int Area2(int[] verts, int a, int b, int c) private int Area2(int[] verts, int a, int b, int c)
{ {
return (verts[b] - verts[a]) * (verts[c + 2] - verts[a + 2]) return (verts[b] - verts[a]) * (verts[c + 2] - verts[a + 2])
- (verts[c] - verts[a]) * (verts[b + 2] - verts[a + 2]); - (verts[c] - verts[a]) * (verts[b + 2] - verts[a + 2]);
@ -980,17 +977,17 @@ namespace DotRecast.Detour.TileCache
// Returns true iff c is strictly to the left of the directed // Returns true iff c is strictly to the left of the directed
// line through a to b. // line through a to b.
public static bool Left(int[] verts, int a, int b, int c) private bool Left(int[] verts, int a, int b, int c)
{ {
return Area2(verts, a, b, c) < 0; return Area2(verts, a, b, c) < 0;
} }
public static bool LeftOn(int[] verts, int a, int b, int c) private bool LeftOn(int[] verts, int a, int b, int c)
{ {
return Area2(verts, a, b, c) <= 0; return Area2(verts, a, b, c) <= 0;
} }
public static bool Collinear(int[] verts, int a, int b, int c) private bool Collinear(int[] verts, int a, int b, int c)
{ {
return Area2(verts, a, b, c) == 0; return Area2(verts, a, b, c) == 0;
} }
@ -998,7 +995,7 @@ namespace DotRecast.Detour.TileCache
// Returns true iff ab properly intersects cd: they share // Returns true iff ab properly intersects cd: they share
// a point interior to both segments. The properness of the // a point interior to both segments. The properness of the
// intersection is ensured by using strict leftness. // intersection is ensured by using strict leftness.
public static bool IntersectProp(int[] verts, int a, int b, int c, int d) private bool IntersectProp(int[] verts, int a, int b, int c, int d)
{ {
// Eliminate improper cases. // Eliminate improper cases.
if (Collinear(verts, a, b, c) || Collinear(verts, a, b, d) || Collinear(verts, c, d, a) if (Collinear(verts, a, b, c) || Collinear(verts, a, b, d) || Collinear(verts, c, d, a)
@ -1010,7 +1007,7 @@ namespace DotRecast.Detour.TileCache
// Returns T iff (a,b,c) are collinear and point c lies // Returns T iff (a,b,c) are collinear and point c lies
// on the closed segment ab. // on the closed segment ab.
public static bool Between(int[] verts, int a, int b, int c) private bool Between(int[] verts, int a, int b, int c)
{ {
if (!Collinear(verts, a, b, c)) if (!Collinear(verts, a, b, c))
return false; return false;
@ -1024,7 +1021,7 @@ namespace DotRecast.Detour.TileCache
} }
// Returns true iff segments ab and cd intersect, properly or improperly. // Returns true iff segments ab and cd intersect, properly or improperly.
public static bool Intersect(int[] verts, int a, int b, int c, int d) private bool Intersect(int[] verts, int a, int b, int c, int d)
{ {
if (IntersectProp(verts, a, b, c, d)) if (IntersectProp(verts, a, b, c, d))
return true; return true;
@ -1035,14 +1032,14 @@ namespace DotRecast.Detour.TileCache
return false; return false;
} }
public static bool Vequal(int[] verts, int a, int b) private bool Vequal(int[] verts, int a, int b)
{ {
return verts[a] == verts[b] && verts[a + 2] == verts[b + 2]; return verts[a] == verts[b] && verts[a + 2] == verts[b + 2];
} }
// Returns T iff (v_i, v_j) is a proper internal *or* external // Returns T iff (v_i, v_j) is a proper internal *or* external
// diagonal of P, *ignoring edges incident to v_i and v_j*. // diagonal of P, *ignoring edges incident to v_i and v_j*.
public static bool Diagonalie(int i, int j, int n, int[] verts, int[] indices) private bool Diagonalie(int i, int j, int n, int[] verts, int[] indices)
{ {
int d0 = (indices[i] & 0x7fff) * 4; int d0 = (indices[i] & 0x7fff) * 4;
int d1 = (indices[j] & 0x7fff) * 4; int d1 = (indices[j] & 0x7fff) * 4;
@ -1070,7 +1067,7 @@ namespace DotRecast.Detour.TileCache
// Returns true iff the diagonal (i,j) is strictly internal to the // Returns true iff the diagonal (i,j) is strictly internal to the
// polygon P in the neighborhood of the i endpoint. // polygon P in the neighborhood of the i endpoint.
public static bool InCone(int i, int j, int n, int[] verts, int[] indices) private bool InCone(int i, int j, int n, int[] verts, int[] indices)
{ {
int pi = (indices[i] & 0x7fff) * 4; int pi = (indices[i] & 0x7fff) * 4;
int pj = (indices[j] & 0x7fff) * 4; int pj = (indices[j] & 0x7fff) * 4;
@ -1087,12 +1084,12 @@ namespace DotRecast.Detour.TileCache
// Returns T iff (v_i, v_j) is a proper internal // Returns T iff (v_i, v_j) is a proper internal
// diagonal of P. // diagonal of P.
public static bool Diagonal(int i, int j, int n, int[] verts, int[] indices) private bool Diagonal(int i, int j, int n, int[] verts, int[] indices)
{ {
return InCone(i, j, n, verts, indices) && Diagonalie(i, j, n, verts, indices); return InCone(i, j, n, verts, indices) && Diagonalie(i, j, n, verts, indices);
} }
public static int Triangulate(int n, int[] verts, int[] indices, int[] tris) private int Triangulate(int n, int[] verts, int[] indices, int[] tris)
{ {
int ntris = 0; int ntris = 0;
int dst = 0; // tris; int dst = 0; // tris;
@ -1177,7 +1174,7 @@ namespace DotRecast.Detour.TileCache
return ntris; return ntris;
} }
public static int CountPolyVerts(int[] polys, int p, int maxVertsPerPoly) private int CountPolyVerts(int[] polys, int p, int maxVertsPerPoly)
{ {
for (int i = 0; i < maxVertsPerPoly; ++i) for (int i = 0; i < maxVertsPerPoly; ++i)
if (polys[p + i] == DT_TILECACHE_NULL_IDX) if (polys[p + i] == DT_TILECACHE_NULL_IDX)
@ -1185,13 +1182,13 @@ namespace DotRecast.Detour.TileCache
return maxVertsPerPoly; return maxVertsPerPoly;
} }
public static bool Uleft(int[] verts, int a, int b, int c) private bool Uleft(int[] verts, int a, int b, int c)
{ {
return (verts[b] - verts[a]) * (verts[c + 2] - verts[a + 2]) return (verts[b] - verts[a]) * (verts[c + 2] - verts[a + 2])
- (verts[c] - verts[a]) * (verts[b + 2] - verts[a + 2]) < 0; - (verts[c] - verts[a]) * (verts[b + 2] - verts[a + 2]) < 0;
} }
public static int GetPolyMergeValue(int[] polys, int pa, int pb, int[] verts, out int ea, out int eb, int maxVertsPerPoly) private int GetPolyMergeValue(int[] polys, int pa, int pb, int[] verts, out int ea, out int eb, int maxVertsPerPoly)
{ {
ea = 0; ea = 0;
eb = 0; eb = 0;
@ -1262,7 +1259,7 @@ namespace DotRecast.Detour.TileCache
return (dx * dx) + (dy * dy); return (dx * dx) + (dy * dy);
} }
public static void MergePolys(int[] polys, int pa, int pb, int ea, int eb, int maxVertsPerPoly) private void MergePolys(int[] polys, int pa, int pb, int ea, int eb, int maxVertsPerPoly)
{ {
int[] tmp = new int[maxVertsPerPoly * 2]; int[] tmp = new int[maxVertsPerPoly * 2];
@ -1278,22 +1275,22 @@ namespace DotRecast.Detour.TileCache
// Add pb // Add pb
for (int i = 0; i < nb - 1; ++i) for (int i = 0; i < nb - 1; ++i)
tmp[n++] = polys[pb + (eb + 1 + i) % nb]; tmp[n++] = polys[pb + (eb + 1 + i) % nb];
RcArrays.Copy(tmp, 0, polys, pa, maxVertsPerPoly); Array.Copy(tmp, 0, polys, pa, maxVertsPerPoly);
} }
public static int PushFront(int v, List<int> arr) private int PushFront(int v, List<int> arr)
{ {
arr.Insert(0, v); arr.Insert(0, v);
return arr.Count; return arr.Count;
} }
public static int PushBack(int v, List<int> arr) private int PushBack(int v, List<int> arr)
{ {
arr.Add(v); arr.Add(v);
return arr.Count; return arr.Count;
} }
public static bool CanRemoveVertex(DtTileCachePolyMesh mesh, int rem) private bool CanRemoveVertex(DtTileCachePolyMesh mesh, int rem)
{ {
// Count number of polygons to remove. // Count number of polygons to remove.
int maxVertsPerPoly = mesh.nvp; int maxVertsPerPoly = mesh.nvp;
@ -1391,7 +1388,7 @@ namespace DotRecast.Detour.TileCache
return true; return true;
} }
public static void RemoveVertex(DtTileCachePolyMesh mesh, int rem, int maxTris) private void RemoveVertex(DtTileCachePolyMesh mesh, int rem, int maxTris)
{ {
// Count number of polygons to remove. // Count number of polygons to remove.
int maxVertsPerPoly = mesh.nvp; int maxVertsPerPoly = mesh.nvp;
@ -1437,7 +1434,7 @@ namespace DotRecast.Detour.TileCache
// Remove the polygon. // Remove the polygon.
int p2 = (mesh.npolys - 1) * maxVertsPerPoly * 2; int p2 = (mesh.npolys - 1) * maxVertsPerPoly * 2;
RcArrays.Copy(mesh.polys, p2, mesh.polys, p, maxVertsPerPoly); Array.Copy(mesh.polys, p2, mesh.polys, p, maxVertsPerPoly);
Array.Fill(mesh.polys, DT_TILECACHE_NULL_IDX, p + maxVertsPerPoly, maxVertsPerPoly); Array.Fill(mesh.polys, DT_TILECACHE_NULL_IDX, p + maxVertsPerPoly, maxVertsPerPoly);
mesh.areas[i] = mesh.areas[mesh.npolys - 1]; mesh.areas[i] = mesh.areas[mesh.npolys - 1];
mesh.npolys--; mesh.npolys--;
@ -1600,7 +1597,7 @@ namespace DotRecast.Detour.TileCache
int pa = bestPa * maxVertsPerPoly; int pa = bestPa * maxVertsPerPoly;
int pb = bestPb * maxVertsPerPoly; int pb = bestPb * maxVertsPerPoly;
MergePolys(polys, pa, pb, bestEa, bestEb, maxVertsPerPoly); MergePolys(polys, pa, pb, bestEa, bestEb, maxVertsPerPoly);
RcArrays.Copy(polys, (npolys - 1) * maxVertsPerPoly, polys, pb, maxVertsPerPoly); Array.Copy(polys, (npolys - 1) * maxVertsPerPoly, polys, pb, maxVertsPerPoly);
pareas[bestPb] = pareas[npolys - 1]; pareas[bestPb] = pareas[npolys - 1];
npolys--; npolys--;
} }
@ -1630,7 +1627,7 @@ namespace DotRecast.Detour.TileCache
} }
} }
public static DtTileCachePolyMesh BuildTileCachePolyMesh(DtTileCacheContourSet lcset, int maxVertsPerPoly) public DtTileCachePolyMesh BuildTileCachePolyMesh(DtTileCacheContourSet lcset, int maxVertsPerPoly)
{ {
int maxVertices = 0; int maxVertices = 0;
int maxTris = 0; int maxTris = 0;
@ -1756,7 +1753,7 @@ namespace DotRecast.Detour.TileCache
int pa = bestPa * maxVertsPerPoly; int pa = bestPa * maxVertsPerPoly;
int pb = bestPb * maxVertsPerPoly; int pb = bestPb * maxVertsPerPoly;
MergePolys(polys, pa, pb, bestEa, bestEb, maxVertsPerPoly); MergePolys(polys, pa, pb, bestEa, bestEb, maxVertsPerPoly);
RcArrays.Copy(polys, (npolys - 1) * maxVertsPerPoly, polys, pb, maxVertsPerPoly); Array.Copy(polys, (npolys - 1) * maxVertsPerPoly, polys, pb, maxVertsPerPoly);
npolys--; npolys--;
} }
else else
@ -1804,7 +1801,7 @@ namespace DotRecast.Detour.TileCache
return mesh; return mesh;
} }
public static void MarkCylinderArea(DtTileCacheLayer layer, RcVec3f orig, float cs, float ch, RcVec3f pos, float radius, float height, byte areaId) public void MarkCylinderArea(DtTileCacheLayer layer, RcVec3f orig, float cs, float ch, RcVec3f pos, float radius, float height, int areaId)
{ {
RcVec3f bmin = new RcVec3f(); RcVec3f bmin = new RcVec3f();
RcVec3f bmax = new RcVec3f(); RcVec3f bmax = new RcVec3f();
@ -1860,12 +1857,12 @@ namespace DotRecast.Detour.TileCache
int y = layer.heights[x + z * w]; int y = layer.heights[x + z * w];
if (y < miny || y > maxy) if (y < miny || y > maxy)
continue; continue;
layer.areas[x + z * w] = areaId; layer.areas[x + z * w] = (short)areaId;
} }
} }
} }
public static void MarkBoxArea(DtTileCacheLayer layer, RcVec3f orig, float cs, float ch, RcVec3f bmin, RcVec3f bmax, byte areaId) public void MarkBoxArea(DtTileCacheLayer layer, RcVec3f orig, float cs, float ch, RcVec3f bmin, RcVec3f bmax, int areaId)
{ {
int w = layer.header.width; int w = layer.header.width;
int h = layer.header.height; int h = layer.header.height;
@ -1904,12 +1901,12 @@ namespace DotRecast.Detour.TileCache
int y = layer.heights[x + z * w]; int y = layer.heights[x + z * w];
if (y < miny || y > maxy) if (y < miny || y > maxy)
continue; continue;
layer.areas[x + z * w] = areaId; layer.areas[x + z * w] = (short)areaId;
} }
} }
} }
public static byte[] CompressTileCacheLayer(IRcCompressor comp, DtTileCacheLayer layer, RcByteOrder order, bool cCompatibility) public byte[] CompressTileCacheLayer(IRcCompressor comp, DtTileCacheLayer layer, RcByteOrder order, bool cCompatibility)
{ {
using var ms = new MemoryStream(); using var ms = new MemoryStream();
using var bw = new BinaryWriter(ms); using var bw = new BinaryWriter(ms);
@ -1936,7 +1933,7 @@ namespace DotRecast.Detour.TileCache
} }
} }
public static byte[] CompressTileCacheLayer(DtTileCacheLayerHeader header, int[] heights, int[] areas, int[] cons, RcByteOrder order, bool cCompatibility, IRcCompressor comp) public byte[] CompressTileCacheLayer(DtTileCacheLayerHeader header, int[] heights, int[] areas, int[] cons, RcByteOrder order, bool cCompatibility, IRcCompressor comp)
{ {
using var ms = new MemoryStream(); using var ms = new MemoryStream();
using var bw = new BinaryWriter(ms); using var bw = new BinaryWriter(ms);
@ -1963,14 +1960,14 @@ namespace DotRecast.Detour.TileCache
} }
} }
public static DtTileCacheLayer DecompressTileCacheLayer(IRcCompressor comp, byte[] compressed, RcByteOrder order, bool cCompatibility) public DtTileCacheLayer DecompressTileCacheLayer(IRcCompressor comp, byte[] compressed, RcByteOrder order, bool cCompatibility)
{ {
RcByteBuffer buf = new RcByteBuffer(compressed); RcByteBuffer buf = new RcByteBuffer(compressed);
buf.Order(order); buf.Order(order);
DtTileCacheLayer layer = new DtTileCacheLayer(); DtTileCacheLayer layer = new DtTileCacheLayer();
try try
{ {
layer.header = DtTileCacheLayerHeaderReader.Read(buf, cCompatibility); layer.header = reader.Read(buf, cCompatibility);
} }
catch (IOException e) catch (IOException e)
{ {
@ -1979,22 +1976,22 @@ namespace DotRecast.Detour.TileCache
int gridSize = layer.header.width * layer.header.height; int gridSize = layer.header.width * layer.header.height;
byte[] grids = comp.Decompress(compressed, buf.Position(), compressed.Length - buf.Position(), gridSize * 3); byte[] grids = comp.Decompress(compressed, buf.Position(), compressed.Length - buf.Position(), gridSize * 3);
layer.heights = new byte[gridSize]; layer.heights = new short[gridSize];
layer.areas = new byte[gridSize]; layer.areas = new short[gridSize];
layer.cons = new byte[gridSize]; layer.cons = new short[gridSize];
layer.regs = new byte[gridSize]; layer.regs = new short[gridSize];
for (int i = 0; i < gridSize; i++) for (int i = 0; i < gridSize; i++)
{ {
layer.heights[i] = (byte)(grids[i] & 0xFF); layer.heights[i] = (short)(grids[i] & 0xFF);
layer.areas[i] = (byte)(grids[i + gridSize] & 0xFF); layer.areas[i] = (short)(grids[i + gridSize] & 0xFF);
layer.cons[i] = (byte)(grids[i + gridSize * 2] & 0xFF); layer.cons[i] = (short)(grids[i + gridSize * 2] & 0xFF);
} }
return layer; return layer;
} }
public static void MarkBoxArea(DtTileCacheLayer layer, RcVec3f orig, float cs, float ch, RcVec3f center, RcVec3f extents, public void MarkBoxArea(DtTileCacheLayer layer, RcVec3f orig, float cs, float ch, RcVec3f center, RcVec3f extents,
float[] rotAux, byte areaId) float[] rotAux, int areaId)
{ {
int w = layer.header.width; int w = layer.header.width;
int h = layer.header.height; int h = layer.header.height;
@ -2047,7 +2044,7 @@ namespace DotRecast.Detour.TileCache
int y = layer.heights[x + z * w]; int y = layer.heights[x + z * w];
if (y < miny || y > maxy) if (y < miny || y > maxy)
continue; continue;
layer.areas[x + z * w] = areaId; layer.areas[x + z * w] = (short)areaId;
} }
} }
} }

View File

@ -1,7 +1,7 @@
/* /*
Copyright (c) 2009-2010 Mikko Mononen memon@inside.org Copyright (c) 2009-2010 Mikko Mononen memon@inside.org
recast4j copyright (c) 2015-2019 Piotr Piastucki piotr@jtilia.org recast4j copyright (c) 2015-2019 Piotr Piastucki piotr@jtilia.org
DotRecast Copyright (c) 2023-2024 Choi Ikpil ikpil@naver.com DotRecast Copyright (c) 2023 Choi Ikpil ikpil@naver.com
This software is provided 'as-is', without any express or implied This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages warranty. In no event will the authors be held liable for any damages
@ -24,7 +24,7 @@ namespace DotRecast.Detour.TileCache
{ {
public int nverts; public int nverts;
public int[] verts; public int[] verts;
public byte reg; public int reg;
public byte area; public int area;
} }
} }

View File

@ -1,7 +1,7 @@
/* /*
Copyright (c) 2009-2010 Mikko Mononen memon@inside.org Copyright (c) 2009-2010 Mikko Mononen memon@inside.org
recast4j copyright (c) 2015-2019 Piotr Piastucki piotr@jtilia.org recast4j copyright (c) 2015-2019 Piotr Piastucki piotr@jtilia.org
DotRecast Copyright (c) 2023-2024 Choi Ikpil ikpil@naver.com DotRecast Copyright (c) 2023 Choi Ikpil ikpil@naver.com
This software is provided 'as-is', without any express or implied This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages warranty. In no event will the authors be held liable for any damages

View File

@ -1,7 +1,7 @@
/* /*
Copyright (c) 2009-2010 Mikko Mononen memon@inside.org Copyright (c) 2009-2010 Mikko Mononen memon@inside.org
recast4j copyright (c) 2015-2019 Piotr Piastucki piotr@jtilia.org recast4j copyright (c) 2015-2019 Piotr Piastucki piotr@jtilia.org
DotRecast Copyright (c) 2023-2024 Choi Ikpil ikpil@naver.com DotRecast Copyright (c) 2023 Choi Ikpil ikpil@naver.com
This software is provided 'as-is', without any express or implied This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages warranty. In no event will the authors be held liable for any damages
@ -23,10 +23,13 @@ namespace DotRecast.Detour.TileCache
public class DtTileCacheLayer public class DtTileCacheLayer
{ {
public DtTileCacheLayerHeader header; public DtTileCacheLayerHeader header;
public byte regCount; // < Region count. public int regCount;
public byte[] heights; // unsigned char
public byte[] areas; // unsigned char /// < Region count.
public byte[] cons; // unsigned char public short[] heights; // char
public byte[] regs; // unsigned char
public short[] areas; // char
public short[] cons; // char
public short[] regs; // char
} }
} }

View File

@ -1,4 +1,4 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.Threading.Tasks; using System.Threading.Tasks;
namespace DotRecast.Detour.TileCache namespace DotRecast.Detour.TileCache

View File

@ -1,7 +1,7 @@
/* /*
Copyright (c) 2009-2010 Mikko Mononen memon@inside.org Copyright (c) 2009-2010 Mikko Mononen memon@inside.org
recast4j copyright (c) 2015-2019 Piotr Piastucki piotr@jtilia.org recast4j copyright (c) 2015-2019 Piotr Piastucki piotr@jtilia.org
DotRecast Copyright (c) 2023-2024 Choi Ikpil ikpil@naver.com DotRecast Copyright (c) 2023 Choi Ikpil ikpil@naver.com
This software is provided 'as-is', without any express or implied This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages warranty. In no event will the authors be held liable for any damages
@ -100,6 +100,7 @@ namespace DotRecast.Detour.TileCache
List<byte[]> result = new List<byte[]>(); List<byte[]> result = new List<byte[]>();
if (lset != null) if (lset != null)
{ {
DtTileCacheBuilder builder = new DtTileCacheBuilder();
for (int i = 0; i < lset.layers.Length; ++i) for (int i = 0; i < lset.layers.Length; ++i)
{ {
RcHeightfieldLayer layer = lset.layers[i]; RcHeightfieldLayer layer = lset.layers[i];
@ -127,7 +128,7 @@ namespace DotRecast.Detour.TileCache
header.hmax = layer.hmax; header.hmax = layer.hmax;
var comp = _compFactory.Create(storageParams.Compatibility ? 0 : 1); var comp = _compFactory.Create(storageParams.Compatibility ? 0 : 1);
var bytes = DtTileCacheBuilder.CompressTileCacheLayer(header, layer.heights, layer.areas, layer.cons, storageParams.Order, storageParams.Compatibility, comp); var bytes = builder.CompressTileCacheLayer(header, layer.heights, layer.areas, layer.cons, storageParams.Order, storageParams.Compatibility, comp);
result.Add(bytes); result.Add(bytes);
} }
} }

View File

@ -1,7 +1,7 @@
/* /*
Copyright (c) 2009-2010 Mikko Mononen memon@inside.org Copyright (c) 2009-2010 Mikko Mononen memon@inside.org
recast4j copyright (c) 2015-2019 Piotr Piastucki piotr@jtilia.org recast4j copyright (c) 2015-2019 Piotr Piastucki piotr@jtilia.org
DotRecast Copyright (c) 2023-2024 Choi Ikpil ikpil@naver.com DotRecast Copyright (c) 2023 Choi Ikpil ikpil@naver.com
This software is provided 'as-is', without any express or implied This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages warranty. In no event will the authors be held liable for any damages

View File

@ -1,7 +1,7 @@
/* /*
Copyright (c) 2009-2010 Mikko Mononen memon@inside.org Copyright (c) 2009-2010 Mikko Mononen memon@inside.org
recast4j copyright (c) 2015-2019 Piotr Piastucki piotr@jtilia.org recast4j copyright (c) 2015-2019 Piotr Piastucki piotr@jtilia.org
DotRecast Copyright (c) 2023-2024 Choi Ikpil ikpil@naver.com DotRecast Copyright (c) 2023 Choi Ikpil ikpil@naver.com
This software is provided 'as-is', without any express or implied This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages warranty. In no event will the authors be held liable for any damages
@ -19,28 +19,32 @@ freely, subject to the following restrictions:
*/ */
using System.Collections.Generic; using System.Collections.Generic;
using DotRecast.Core.Numerics;
namespace DotRecast.Detour.TileCache namespace DotRecast.Detour.TileCache
{ {
public class DtTileCacheObstacle public class DtTileCacheObstacle
{ {
public readonly int index; public readonly int index;
public DtTileCacheObstacleType type;
public DtObstacleCylinder cylinder = new DtObstacleCylinder(); public RcVec3f pos = new RcVec3f();
public DtObstacleBox box = new DtObstacleBox(); public RcVec3f bmin = new RcVec3f();
public DtObstacleOrientedBox orientedBox = new DtObstacleOrientedBox(); public RcVec3f bmax = new RcVec3f();
public float radius, height;
public RcVec3f center = new RcVec3f();
public RcVec3f extents = new RcVec3f();
public readonly float[] rotAux = new float[2]; // { Cos(0.5f*angle)*Sin(-0.5f*angle); Cos(0.5f*angle)*Cos(0.5f*angle) - 0.5 }
public List<long> touched = new List<long>(); public List<long> touched = new List<long>();
public readonly List<long> pending = new List<long>(); public readonly List<long> pending = new List<long>();
public int salt; public int salt;
public DtTileCacheObstacleType type;
public DtObstacleState state = DtObstacleState.DT_OBSTACLE_EMPTY; public DtObstacleState state = DtObstacleState.DT_OBSTACLE_EMPTY;
public DtTileCacheObstacle next; public DtTileCacheObstacle next;
public DtTileCacheObstacle(int index) public DtTileCacheObstacle(int index)
{ {
this.index = index;
salt = 1; salt = 1;
this.index = index;
} }
} }
} }

View File

@ -1,9 +1,9 @@
namespace DotRecast.Detour.TileCache namespace DotRecast.Detour.TileCache
{ {
public enum DtTileCacheObstacleType public enum DtTileCacheObstacleType
{ {
DT_OBSTACLE_CYLINDER, CYLINDER,
DT_OBSTACLE_BOX, // AABB BOX,
DT_OBSTACLE_ORIENTED_BOX // OBB ORIENTED_BOX
}; };
} }

View File

@ -1,7 +1,7 @@
/* /*
Copyright (c) 2009-2010 Mikko Mononen memon@inside.org Copyright (c) 2009-2010 Mikko Mononen memon@inside.org
recast4j copyright (c) 2015-2019 Piotr Piastucki piotr@jtilia.org recast4j copyright (c) 2015-2019 Piotr Piastucki piotr@jtilia.org
DotRecast Copyright (c) 2023-2024 Choi Ikpil ikpil@naver.com DotRecast Copyright (c) 2023 Choi Ikpil ikpil@naver.com
This software is provided 'as-is', without any express or implied This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages warranty. In no event will the authors be held liable for any damages

View File

@ -1,7 +1,7 @@
/* /*
Copyright (c) 2009-2010 Mikko Mononen memon@inside.org Copyright (c) 2009-2010 Mikko Mononen memon@inside.org
recast4j copyright (c) 2015-2019 Piotr Piastucki piotr@jtilia.org recast4j copyright (c) 2015-2019 Piotr Piastucki piotr@jtilia.org
DotRecast Copyright (c) 2023-2024 Choi Ikpil ikpil@naver.com DotRecast Copyright (c) 2023 Choi Ikpil ikpil@naver.com
This software is provided 'as-is', without any express or implied This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages warranty. In no event will the authors be held liable for any damages
@ -23,13 +23,24 @@ namespace DotRecast.Detour.TileCache
public class DtTileCachePolyMesh public class DtTileCachePolyMesh
{ {
public int nvp; public int nvp;
public int nverts; // < Number of vertices. public int nverts;
public int npolys; // < Number of polygons.
public int[] verts; // < Vertices of the mesh, 3 elements per vertex.
public int[] polys; // < Polygons of the mesh, nvp*2 elements per polygon.
public int[] flags; // < Per polygon flags.
public int[] areas; // < Area ID of polygons.
/// < Number of vertices.
public int npolys;
/// < Number of polygons.
public int[] verts;
/// < Vertices of the mesh, 3 elements per vertex.
public int[] polys;
/// < Polygons of the mesh, nvp*2 elements per polygon.
public int[] flags;
/// < Per polygon flags.
public int[] areas;
/// < Area ID of polygons.
public DtTileCachePolyMesh(int nvp) public DtTileCachePolyMesh(int nvp)
{ {
this.nvp = nvp; this.nvp = nvp;

View File

@ -1,7 +1,7 @@
/* /*
Copyright (c) 2009-2010 Mikko Mononen memon@inside.org Copyright (c) 2009-2010 Mikko Mononen memon@inside.org
recast4j copyright (c) 2015-2019 Piotr Piastucki piotr@jtilia.org recast4j copyright (c) 2015-2019 Piotr Piastucki piotr@jtilia.org
DotRecast Copyright (c) 2023-2024 Choi Ikpil ikpil@naver.com DotRecast Copyright (c) 2023 Choi Ikpil ikpil@naver.com
This software is provided 'as-is', without any express or implied This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages warranty. In no event will the authors be held liable for any damages

View File

@ -1,7 +1,7 @@
/* /*
Copyright (c) 2009-2010 Mikko Mononen memon@inside.org Copyright (c) 2009-2010 Mikko Mononen memon@inside.org
recast4j copyright (c) 2015-2019 Piotr Piastucki piotr@jtilia.org recast4j copyright (c) 2015-2019 Piotr Piastucki piotr@jtilia.org
DotRecast Copyright (c) 2023-2024 Choi Ikpil ikpil@naver.com DotRecast Copyright (c) 2023 Choi Ikpil ikpil@naver.com
This software is provided 'as-is', without any express or implied This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages warranty. In no event will the authors be held liable for any damages

View File

@ -1,4 +1,4 @@
using System.Collections.Generic; using System.Collections.Generic;
using DotRecast.Core; using DotRecast.Core;
namespace DotRecast.Detour.TileCache.Io.Compress namespace DotRecast.Detour.TileCache.Io.Compress

View File

@ -1,7 +1,7 @@
/* /*
Copyright (c) 2009-2010 Mikko Mononen memon@inside.org Copyright (c) 2009-2010 Mikko Mononen memon@inside.org
recast4j copyright (c) 2015-2019 Piotr Piastucki piotr@jtilia.org recast4j copyright (c) 2015-2019 Piotr Piastucki piotr@jtilia.org
DotRecast Copyright (c) 2023-2024 Choi Ikpil ikpil@naver.com DotRecast Copyright (c) 2023 Choi Ikpil ikpil@naver.com
This software is provided 'as-is', without any express or implied This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages warranty. In no event will the authors be held liable for any damages
@ -47,7 +47,7 @@ namespace DotRecast.Detour.TileCache.Io.Compress
{ {
byte[] output = new byte[FastLZ.EstimateCompressedSize(buf.Length)]; byte[] output = new byte[FastLZ.EstimateCompressedSize(buf.Length)];
long len = FastLZ.CompressLevel(2, buf, 0, buf.Length, output); long len = FastLZ.CompressLevel(2, buf, 0, buf.Length, output);
return RcArrays.CopyOf(output, len); return RcArrayUtils.CopyOf(output, len);
} }
} }
} }

View File

@ -1,7 +1,7 @@
/* /*
Copyright (c) 2009-2010 Mikko Mononen memon@inside.org Copyright (c) 2009-2010 Mikko Mononen memon@inside.org
recast4j copyright (c) 2015-2019 Piotr Piastucki piotr@jtilia.org recast4j copyright (c) 2015-2019 Piotr Piastucki piotr@jtilia.org
DotRecast Copyright (c) 2023-2024 Choi Ikpil ikpil@naver.com DotRecast Copyright (c) 2023 Choi Ikpil ikpil@naver.com
This software is provided 'as-is', without any express or implied This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages warranty. In no event will the authors be held liable for any damages

View File

@ -1,7 +1,7 @@
/* /*
Copyright (c) 2009-2010 Mikko Mononen memon@inside.org Copyright (c) 2009-2010 Mikko Mononen memon@inside.org
recast4j copyright (c) 2015-2019 Piotr Piastucki piotr@jtilia.org recast4j copyright (c) 2015-2019 Piotr Piastucki piotr@jtilia.org
DotRecast Copyright (c) 2023-2024 Choi Ikpil ikpil@naver.com DotRecast Copyright (c) 2023 Choi Ikpil ikpil@naver.com
This software is provided 'as-is', without any express or implied This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages warranty. In no event will the authors be held liable for any damages
@ -23,9 +23,9 @@ using DotRecast.Core;
namespace DotRecast.Detour.TileCache.Io namespace DotRecast.Detour.TileCache.Io
{ {
public static class DtTileCacheLayerHeaderReader public class DtTileCacheLayerHeaderReader
{ {
public static DtTileCacheLayerHeader Read(RcByteBuffer data, bool cCompatibility) public DtTileCacheLayerHeader Read(RcByteBuffer data, bool cCompatibility)
{ {
DtTileCacheLayerHeader header = new DtTileCacheLayerHeader(); DtTileCacheLayerHeader header = new DtTileCacheLayerHeader();
header.magic = data.GetInt(); header.magic = data.GetInt();

View File

@ -1,7 +1,7 @@
/* /*
Copyright (c) 2009-2010 Mikko Mononen memon@inside.org Copyright (c) 2009-2010 Mikko Mononen memon@inside.org
recast4j copyright (c) 2015-2019 Piotr Piastucki piotr@jtilia.org recast4j copyright (c) 2015-2019 Piotr Piastucki piotr@jtilia.org
DotRecast Copyright (c) 2023-2024 Choi Ikpil ikpil@naver.com DotRecast Copyright (c) 2023 Choi Ikpil ikpil@naver.com
This software is provided 'as-is', without any express or implied This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages warranty. In no event will the authors be held liable for any damages
@ -24,34 +24,34 @@ using DotRecast.Detour.Io;
namespace DotRecast.Detour.TileCache.Io namespace DotRecast.Detour.TileCache.Io
{ {
public class DtTileCacheLayerHeaderWriter public class DtTileCacheLayerHeaderWriter : DtWriter
{ {
public void Write(BinaryWriter stream, DtTileCacheLayerHeader header, RcByteOrder order, bool cCompatibility) public void Write(BinaryWriter stream, DtTileCacheLayerHeader header, RcByteOrder order, bool cCompatibility)
{ {
RcIO.Write(stream, header.magic, order); Write(stream, header.magic, order);
RcIO.Write(stream, header.version, order); Write(stream, header.version, order);
RcIO.Write(stream, header.tx, order); Write(stream, header.tx, order);
RcIO.Write(stream, header.ty, order); Write(stream, header.ty, order);
RcIO.Write(stream, header.tlayer, order); Write(stream, header.tlayer, order);
RcIO.Write(stream, header.bmin.X, order); Write(stream, header.bmin.X, order);
RcIO.Write(stream, header.bmin.Y, order); Write(stream, header.bmin.Y, order);
RcIO.Write(stream, header.bmin.Z, order); Write(stream, header.bmin.Z, order);
RcIO.Write(stream, header.bmax.X, order); Write(stream, header.bmax.X, order);
RcIO.Write(stream, header.bmax.Y, order); Write(stream, header.bmax.Y, order);
RcIO.Write(stream, header.bmax.Z, order); Write(stream, header.bmax.Z, order);
RcIO.Write(stream, (short)header.hmin, order); Write(stream, (short)header.hmin, order);
RcIO.Write(stream, (short)header.hmax, order); Write(stream, (short)header.hmax, order);
RcIO.Write(stream, (byte)header.width); Write(stream, (byte)header.width);
RcIO.Write(stream, (byte)header.height); Write(stream, (byte)header.height);
RcIO.Write(stream, (byte)header.minx); Write(stream, (byte)header.minx);
RcIO.Write(stream, (byte)header.maxx); Write(stream, (byte)header.maxx);
RcIO.Write(stream, (byte)header.miny); Write(stream, (byte)header.miny);
RcIO.Write(stream, (byte)header.maxy); Write(stream, (byte)header.maxy);
if (cCompatibility) if (cCompatibility)
{ {
RcIO.Write(stream, (short)0, order); // C struct padding Write(stream, (short)0, order); // C struct padding
} }
} }
} }

View File

@ -1,7 +1,7 @@
/* /*
Copyright (c) 2009-2010 Mikko Mononen memon@inside.org Copyright (c) 2009-2010 Mikko Mononen memon@inside.org
recast4j copyright (c) 2015-2019 Piotr Piastucki piotr@jtilia.org recast4j copyright (c) 2015-2019 Piotr Piastucki piotr@jtilia.org
DotRecast Copyright (c) 2023-2024 Choi Ikpil ikpil@naver.com DotRecast Copyright (c) 2023 Choi Ikpil ikpil@naver.com
This software is provided 'as-is', without any express or implied This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages warranty. In no event will the authors be held liable for any damages
@ -37,7 +37,7 @@ namespace DotRecast.Detour.TileCache.Io
public DtTileCache Read(BinaryReader @is, int maxVertPerPoly, IDtTileCacheMeshProcess meshProcessor) public DtTileCache Read(BinaryReader @is, int maxVertPerPoly, IDtTileCacheMeshProcess meshProcessor)
{ {
RcByteBuffer bb = RcIO.ToByteBuffer(@is); RcByteBuffer bb = IOUtils.ToByteBuffer(@is);
return Read(bb, maxVertPerPoly, meshProcessor); return Read(bb, maxVertPerPoly, meshProcessor);
} }
@ -47,7 +47,7 @@ namespace DotRecast.Detour.TileCache.Io
header.magic = bb.GetInt(); header.magic = bb.GetInt();
if (header.magic != DtTileCacheSetHeader.TILECACHESET_MAGIC) if (header.magic != DtTileCacheSetHeader.TILECACHESET_MAGIC)
{ {
header.magic = RcIO.SwapEndianness(header.magic); header.magic = IOUtils.SwapEndianness(header.magic);
if (header.magic != DtTileCacheSetHeader.TILECACHESET_MAGIC) if (header.magic != DtTileCacheSetHeader.TILECACHESET_MAGIC)
{ {
throw new IOException("Invalid magic"); throw new IOException("Invalid magic");
@ -69,8 +69,7 @@ namespace DotRecast.Detour.TileCache.Io
header.numTiles = bb.GetInt(); header.numTiles = bb.GetInt();
header.meshParams = paramReader.Read(bb); header.meshParams = paramReader.Read(bb);
header.cacheParams = ReadCacheParams(bb, cCompatibility); header.cacheParams = ReadCacheParams(bb, cCompatibility);
DtNavMesh mesh = new DtNavMesh(); DtNavMesh mesh = new DtNavMesh(header.meshParams, maxVertPerPoly);
mesh.Init(header.meshParams, maxVertPerPoly);
IRcCompressor comp = _compFactory.Create(cCompatibility ? 0 : 1); IRcCompressor comp = _compFactory.Create(cCompatibility ? 0 : 1);
DtTileCacheStorageParams storageParams = new DtTileCacheStorageParams(bb.Order(), cCompatibility); DtTileCacheStorageParams storageParams = new DtTileCacheStorageParams(bb.Order(), cCompatibility);
DtTileCache tc = new DtTileCache(header.cacheParams, storageParams, mesh, comp, meshProcessor); DtTileCache tc = new DtTileCache(header.cacheParams, storageParams, mesh, comp, meshProcessor);

View File

@ -1,7 +1,7 @@
/* /*
Copyright (c) 2009-2010 Mikko Mononen memon@inside.org Copyright (c) 2009-2010 Mikko Mononen memon@inside.org
recast4j copyright (c) 2015-2019 Piotr Piastucki piotr@jtilia.org recast4j copyright (c) 2015-2019 Piotr Piastucki piotr@jtilia.org
DotRecast Copyright (c) 2023-2024 Choi Ikpil ikpil@naver.com DotRecast Copyright (c) 2023 Choi Ikpil ikpil@naver.com
This software is provided 'as-is', without any express or implied This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages warranty. In no event will the authors be held liable for any damages

View File

@ -1,7 +1,7 @@
/* /*
Copyright (c) 2009-2010 Mikko Mononen memon@inside.org Copyright (c) 2009-2010 Mikko Mononen memon@inside.org
recast4j copyright (c) 2015-2019 Piotr Piastucki piotr@jtilia.org recast4j copyright (c) 2015-2019 Piotr Piastucki piotr@jtilia.org
DotRecast Copyright (c) 2023-2024 Choi Ikpil ikpil@naver.com DotRecast Copyright (c) 2023 Choi Ikpil ikpil@naver.com
This software is provided 'as-is', without any express or implied This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages warranty. In no event will the authors be held liable for any damages
@ -25,9 +25,10 @@ using DotRecast.Detour.TileCache.Io.Compress;
namespace DotRecast.Detour.TileCache.Io namespace DotRecast.Detour.TileCache.Io
{ {
public class DtTileCacheWriter public class DtTileCacheWriter : DtWriter
{ {
private readonly DtNavMeshParamWriter paramWriter = new DtNavMeshParamWriter(); private readonly DtNavMeshParamWriter paramWriter = new DtNavMeshParamWriter();
private readonly DtTileCacheBuilder builder = new DtTileCacheBuilder();
private readonly IDtTileCacheCompressorFactory _compFactory; private readonly IDtTileCacheCompressorFactory _compFactory;
public DtTileCacheWriter(IDtTileCacheCompressorFactory compFactory) public DtTileCacheWriter(IDtTileCacheCompressorFactory compFactory)
@ -38,8 +39,8 @@ namespace DotRecast.Detour.TileCache.Io
public void Write(BinaryWriter stream, DtTileCache cache, RcByteOrder order, bool cCompatibility) public void Write(BinaryWriter stream, DtTileCache cache, RcByteOrder order, bool cCompatibility)
{ {
RcIO.Write(stream, DtTileCacheSetHeader.TILECACHESET_MAGIC, order); Write(stream, DtTileCacheSetHeader.TILECACHESET_MAGIC, order);
RcIO.Write(stream, cCompatibility Write(stream, cCompatibility
? DtTileCacheSetHeader.TILECACHESET_VERSION ? DtTileCacheSetHeader.TILECACHESET_VERSION
: DtTileCacheSetHeader.TILECACHESET_VERSION_RECAST4J, order); : DtTileCacheSetHeader.TILECACHESET_VERSION_RECAST4J, order);
int numTiles = 0; int numTiles = 0;
@ -51,7 +52,7 @@ namespace DotRecast.Detour.TileCache.Io
numTiles++; numTiles++;
} }
RcIO.Write(stream, numTiles, order); Write(stream, numTiles, order);
paramWriter.Write(stream, cache.GetNavMesh().GetParams(), order); paramWriter.Write(stream, cache.GetNavMesh().GetParams(), order);
WriteCacheParams(stream, cache.GetParams(), order); WriteCacheParams(stream, cache.GetParams(), order);
for (int i = 0; i < cache.GetTileCount(); i++) for (int i = 0; i < cache.GetTileCount(); i++)
@ -59,32 +60,32 @@ namespace DotRecast.Detour.TileCache.Io
DtCompressedTile tile = cache.GetTile(i); DtCompressedTile tile = cache.GetTile(i);
if (tile == null || tile.data == null) if (tile == null || tile.data == null)
continue; continue;
RcIO.Write(stream, (int)cache.GetTileRef(tile), order); Write(stream, (int)cache.GetTileRef(tile), order);
byte[] data = tile.data; byte[] data = tile.data;
DtTileCacheLayer layer = cache.DecompressTile(tile); DtTileCacheLayer layer = cache.DecompressTile(tile);
var comp = _compFactory.Create(cCompatibility ? 0 : 1); var comp = _compFactory.Create(cCompatibility ? 0 : 1);
data = DtTileCacheBuilder.CompressTileCacheLayer(comp, layer, order, cCompatibility); data = builder.CompressTileCacheLayer(comp, layer, order, cCompatibility);
RcIO.Write(stream, data.Length, order); Write(stream, data.Length, order);
stream.Write(data); stream.Write(data);
} }
} }
private void WriteCacheParams(BinaryWriter stream, DtTileCacheParams option, RcByteOrder order) private void WriteCacheParams(BinaryWriter stream, DtTileCacheParams option, RcByteOrder order)
{ {
RcIO.Write(stream, option.orig.X, order); Write(stream, option.orig.X, order);
RcIO.Write(stream, option.orig.Y, order); Write(stream, option.orig.Y, order);
RcIO.Write(stream, option.orig.Z, order); Write(stream, option.orig.Z, order);
RcIO.Write(stream, option.cs, order); Write(stream, option.cs, order);
RcIO.Write(stream, option.ch, order); Write(stream, option.ch, order);
RcIO.Write(stream, option.width, order); Write(stream, option.width, order);
RcIO.Write(stream, option.height, order); Write(stream, option.height, order);
RcIO.Write(stream, option.walkableHeight, order); Write(stream, option.walkableHeight, order);
RcIO.Write(stream, option.walkableRadius, order); Write(stream, option.walkableRadius, order);
RcIO.Write(stream, option.walkableClimb, order); Write(stream, option.walkableClimb, order);
RcIO.Write(stream, option.maxSimplificationError, order); Write(stream, option.maxSimplificationError, order);
RcIO.Write(stream, option.maxTiles, order); Write(stream, option.maxTiles, order);
RcIO.Write(stream, option.maxObstacles, order); Write(stream, option.maxObstacles, order);
} }
} }
} }

View File

@ -1,11 +1,9 @@
using DotRecast.Core.Numerics; namespace DotRecast.Detour
namespace DotRecast.Detour
{ {
public class BVItem public class BVItem
{ {
public RcVec3i bmin; public readonly int[] bmin = new int[3];
public RcVec3i bmax; public readonly int[] bmax = new int[3];
public int i; public int i;
}; };
} }

View File

@ -1,4 +1,4 @@
using System.Collections.Generic; using System.Collections.Generic;
namespace DotRecast.Detour namespace DotRecast.Detour
{ {
@ -12,7 +12,7 @@ namespace DotRecast.Detour
public int Compare(BVItem a, BVItem b) public int Compare(BVItem a, BVItem b)
{ {
return a.bmin.X.CompareTo(b.bmin.X); return a.bmin[0].CompareTo(b.bmin[0]);
} }
} }
} }

View File

@ -1,4 +1,4 @@
using System.Collections.Generic; using System.Collections.Generic;
namespace DotRecast.Detour namespace DotRecast.Detour
{ {
@ -12,7 +12,7 @@ namespace DotRecast.Detour
public int Compare(BVItem a, BVItem b) public int Compare(BVItem a, BVItem b)
{ {
return a.bmin.Y.CompareTo(b.bmin.Y); return a.bmin[1].CompareTo(b.bmin[1]);
} }
} }
} }

View File

@ -1,4 +1,4 @@
using System.Collections.Generic; using System.Collections.Generic;
namespace DotRecast.Detour namespace DotRecast.Detour
{ {
@ -12,7 +12,7 @@ namespace DotRecast.Detour
public int Compare(BVItem a, BVItem b) public int Compare(BVItem a, BVItem b)
{ {
return a.bmin.Z.CompareTo(b.bmin.Z); return a.bmin[2].CompareTo(b.bmin[2]);
} }
} }
} }

View File

@ -1,7 +1,7 @@
/* /*
Copyright (c) 2009-2010 Mikko Mononen memon@inside.org Copyright (c) 2009-2010 Mikko Mononen memon@inside.org
recast4j copyright (c) 2015-2019 Piotr Piastucki piotr@jtilia.org recast4j copyright (c) 2015-2019 Piotr Piastucki piotr@jtilia.org
DotRecast Copyright (c) 2023-2024 Choi Ikpil ikpil@naver.com DotRecast Copyright (c) 2023 Choi Ikpil ikpil@naver.com
This software is provided 'as-is', without any express or implied This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages warranty. In no event will the authors be held liable for any damages

View File

@ -1,16 +0,0 @@
{
"name": "DotRecast.Detour",
"rootNamespace": "DotRecast.Detour",
"references": [
"DotRecast.Core"
],
"includePlatforms": [],
"excludePlatforms": [],
"allowUnsafeCode": false,
"overrideReferences": false,
"precompiledReferences": [],
"autoReferenced": true,
"defineConstraints": [],
"versionDefines": [],
"noEngineReferences": true
}

View File

@ -1,16 +1,15 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<TargetFrameworks>netstandard2.1;net6.0;net7.0;net8.0</TargetFrameworks> <TargetFrameworks>netstandard2.1;net6.0;net7.0</TargetFrameworks>
<PackageId>DotRecast.Detour</PackageId> <PackageId>DotRecast.Detour</PackageId>
<PackageReadmeFile>README.md</PackageReadmeFile> <PackageReadmeFile>README.md</PackageReadmeFile>
<Authors>ikpil</Authors> <Authors>ikpil</Authors>
<Description>DotRecast - a port of Recast Detour, Industry-standard navigation mesh toolset for .NET, C#, Unity3D, games, servers</Description> <Description>DotRecast - a port of Recast Detour, navigation mesh toolset for games, Unity3D, servers, C#</Description>
<RepositoryType>git</RepositoryType> <RepositoryType>git</RepositoryType>
<PackageProjectUrl>https://github.com/ikpil/DotRecast</PackageProjectUrl> <PackageProjectUrl>https://github.com/ikpil/DotRecast</PackageProjectUrl>
<RepositoryUrl>https://github.com/ikpil/DotRecast</RepositoryUrl> <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> <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> </PropertyGroup>
<ItemGroup> <ItemGroup>

View File

@ -1,7 +1,7 @@
/* /*
Copyright (c) 2009-2010 Mikko Mononen memon@inside.org Copyright (c) 2009-2010 Mikko Mononen memon@inside.org
recast4j copyright (c) 2015-2019 Piotr Piastucki piotr@jtilia.org recast4j copyright (c) 2015-2019 Piotr Piastucki piotr@jtilia.org
DotRecast Copyright (c) 2023-2024 Choi Ikpil ikpil@naver.com DotRecast Copyright (c) 2023 Choi Ikpil ikpil@naver.com
This software is provided 'as-is', without any express or implied This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages warranty. In no event will the authors be held liable for any damages
@ -18,17 +18,23 @@ freely, subject to the following restrictions:
3. This notice may not be removed or altered from any source distribution. 3. This notice may not be removed or altered from any source distribution.
*/ */
using DotRecast.Core.Numerics;
namespace DotRecast.Detour namespace DotRecast.Detour
{ {
/// Bounding volume node. /**
/// @note This structure is rarely if ever used by the end user. * Bounding volume node.
/// @see dtMeshTile *
* @note This structure is rarely if ever used by the end user.
* @see MeshTile
*/
public class DtBVNode public class DtBVNode
{ {
public RcVec3i bmin; //< Minimum bounds of the node's AABB. [(x, y, z)] /** Minimum bounds of the node's AABB. [(x, y, z)] */
public RcVec3i bmax; //< Maximum bounds of the node's AABB. [(x, y, z)] public int[] bmin = new int[3];
public int i; //< The node's index. (Negative for escape sequence.)
/** Maximum bounds of the node's AABB. [(x, y, z)] */
public int[] bmax = new int[3];
/** The node's index. (Negative for escape sequence.) */
public int i;
} }
} }

View File

@ -1,22 +0,0 @@
using System;
namespace DotRecast.Detour
{
public struct DtCallbackPolyQuery : IDtPolyQuery
{
private readonly Action<DtMeshTile, DtPoly, long> _callback;
public DtCallbackPolyQuery(Action<DtMeshTile, DtPoly, long> callback)
{
_callback = callback;
}
public void Process(DtMeshTile tile, Span<DtPoly> poly, Span<long> refs, int count)
{
for (int i = 0; i < count; ++i)
{
_callback?.Invoke(tile, poly[i], refs[i]);
}
}
}
}

View File

@ -1,43 +0,0 @@
using System;
using DotRecast.Core;
namespace DotRecast.Detour
{
public class DtCollectPolysQuery : IDtPolyQuery
{
private long[] m_polys;
private int m_maxPolys;
private int m_numCollected;
private bool m_overflow;
public DtCollectPolysQuery(long[] polys, int maxPolys)
{
m_polys = polys;
m_maxPolys = maxPolys;
}
public int NumCollected()
{
return m_numCollected;
}
public bool Overflowed()
{
return m_overflow;
}
public void Process(DtMeshTile tile, Span<DtPoly> poly, Span<long> refs, int count)
{
int numLeft = m_maxPolys - m_numCollected;
int toCopy = count;
if (toCopy > numLeft)
{
m_overflow = true;
toCopy = numLeft;
}
RcSpans.Copy<long>(refs, 0, m_polys, m_numCollected, toCopy);
m_numCollected += toCopy;
}
}
}

View File

@ -1,4 +1,4 @@
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
namespace DotRecast.Detour namespace DotRecast.Detour
{ {

View File

@ -1,4 +1,4 @@
namespace DotRecast.Detour namespace DotRecast.Detour
{ {
public enum DtConvexConvexInFlag public enum DtConvexConvexInFlag
{ {

View File

@ -1,4 +1,4 @@
namespace DotRecast.Detour namespace DotRecast.Detour
{ {
public enum DtConvexConvexIntersection public enum DtConvexConvexIntersection
{ {

View File

@ -1,6 +1,6 @@
/* /*
recast4j copyright (c) 2021 Piotr Piastucki piotr@jtilia.org recast4j copyright (c) 2021 Piotr Piastucki piotr@jtilia.org
DotRecast Copyright (c) 2023-2024 Choi Ikpil ikpil@naver.com DotRecast Copyright (c) 2023 Choi Ikpil ikpil@naver.com
This software is provided 'as-is', without any express or implied This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages warranty. In no event will the authors be held liable for any damages
@ -22,16 +22,18 @@ using DotRecast.Core.Numerics;
namespace DotRecast.Detour namespace DotRecast.Detour
{ {
// Convex-convex intersection based on "Computational Geometry in C" by Joseph O'Rourke /**
* Convex-convex intersection based on "Computational Geometry in C" by Joseph O'Rourke
*/
public static class DtConvexConvexIntersections public static class DtConvexConvexIntersections
{ {
private const float EPSILON = 0.0001f; private static readonly float EPSILON = 0.0001f;
public static Span<float> Intersect(Span<float> p, Span<float> q, Span<float> buffer) public static float[] Intersect(float[] p, float[] q)
{ {
int n = p.Length / 3; int n = p.Length / 3;
int m = q.Length / 3; int m = q.Length / 3;
Span<float> inters = stackalloc float[Math.Max(m, n) * 3 * 3]; float[] inters = new float[Math.Max(m, n) * 3 * 3];
int ii = 0; int ii = 0;
/* Initialize variables. */ /* Initialize variables. */
RcVec3f a = new RcVec3f(); RcVec3f a = new RcVec3f();
@ -51,10 +53,10 @@ namespace DotRecast.Detour
do do
{ {
a = RcVec.Create(p, 3 * (ai % n)); a = RcVecUtils.Create(p, 3 * (ai % n));
b = RcVec.Create(q, 3 * (bi % m)); b = RcVecUtils.Create(q, 3 * (bi % m));
a1 = RcVec.Create(p, 3 * ((ai + n - 1) % n)); // prev a a1 = RcVecUtils.Create(p, 3 * ((ai + n - 1) % n)); // prev a
b1 = RcVec.Create(q, 3 * ((bi + m - 1) % m)); // prev b b1 = RcVecUtils.Create(q, 3 * ((bi + m - 1) % m)); // prev b
RcVec3f A = RcVec3f.Subtract(a, a1); RcVec3f A = RcVec3f.Subtract(a, a1);
RcVec3f B = RcVec3f.Subtract(b, b1); RcVec3f B = RcVec3f.Subtract(b, b1);
@ -95,7 +97,7 @@ namespace DotRecast.Detour
/* Special case: A & B parallel and separated. */ /* Special case: A & B parallel and separated. */
if (parallel && aHB < 0f && bHA < 0f) if (parallel && aHB < 0f && bHA < 0f)
{ {
return Span<float>.Empty; return null;
} }
/* Special case: A & B collinear. */ /* Special case: A & B collinear. */
else if (parallel && MathF.Abs(aHB) < EPSILON && MathF.Abs(bHA) < EPSILON) else if (parallel && MathF.Abs(aHB) < EPSILON && MathF.Abs(bHA) < EPSILON)
@ -168,12 +170,12 @@ namespace DotRecast.Detour
return null; return null;
} }
Span<float> result = buffer.Slice(0, ii); float[] copied = new float[ii];
inters.Slice(0, ii).CopyTo(result); Array.Copy(inters, copied, ii);
return result; return copied;
} }
private static int AddVertex(Span<float> inters, int ii, RcVec3f p) private static int AddVertex(float[] inters, int ii, RcVec3f p)
{ {
if (ii > 0) if (ii > 0)
{ {

View File

@ -1,6 +1,6 @@
/* /*
recast4j copyright (c) 2021 Piotr Piastucki piotr@jtilia.org recast4j copyright (c) 2021 Piotr Piastucki piotr@jtilia.org
DotRecast Copyright (c) 2023-2024 Choi Ikpil ikpil@naver.com DotRecast Copyright (c) 2023 Choi Ikpil ikpil@naver.com
This software is provided 'as-is', without any express or implied This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages warranty. In no event will the authors be held liable for any damages

View File

@ -1,4 +1,4 @@
namespace DotRecast.Detour namespace DotRecast.Detour
{ {
public static class DtDetailTriEdgeFlags public static class DtDetailTriEdgeFlags
{ {

View File

@ -1,184 +0,0 @@
using DotRecast.Core.Numerics;
namespace DotRecast.Detour
{
public static class DtDetour
{
/// The maximum number of vertices per navigation polygon.
/// @ingroup detour
public const int DT_VERTS_PER_POLYGON = 6;
/** 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;
/// A flag that indicates that an entity links to an external entity.
/// (E.g. A polygon edge is a portal that links to another polygon.)
public const int DT_EXT_LINK = 0x8000;
/// A value that indicates the entity does not link to anything.
public const int DT_NULL_LINK = unchecked((int)0xffffffff);
public const int DT_NODE_PARENT_BITS = 24;
public const int DT_NODE_STATE_BITS = 2;
public const int DT_MAX_STATES_PER_NODE = 1 << DT_NODE_STATE_BITS; // number of extra states per node. See dtNode::state
/// A flag that indicates that an off-mesh connection can be traversed in
/// both directions. (Is bidirectional.)
public const int DT_OFFMESH_CON_BIDIR = 1;
/// The maximum number of user defined area ids.
public const int DT_MAX_AREAS = 64;
/// Limit raycasting during any angle pahfinding
/// The limit is given as a multiple of the character radius
public const float DT_RAY_CAST_LIMIT_PROPORTIONS = 50.0f;
/// @{
/// @name Encoding and Decoding
/// These functions are generally meant for internal use only.
/// Derives a standard polygon reference.
/// @note This function is generally meant for internal use only.
/// @param[in] salt The tile's salt value.
/// @param[in] it The index of the tile.
/// @param[in] ip The index of the polygon within the tile.
public static long EncodePolyId(int salt, int it, int ip)
{
return (((long)salt) << (DT_POLY_BITS + DT_TILE_BITS)) | ((long)it << DT_POLY_BITS) | (long)ip;
}
/// Decodes a standard polygon reference.
/// @note This function is generally meant for internal use only.
/// @param[in] ref The polygon reference to decode.
/// @param[out] salt The tile's salt value.
/// @param[out] it The index of the tile.
/// @param[out] ip The index of the polygon within the tile.
/// @see #encodePolyId
public static void DecodePolyId(long refs, out int salt, out int it, out int ip)
{
long saltMask = (1L << DT_SALT_BITS) - 1;
long tileMask = (1L << DT_TILE_BITS) - 1;
long polyMask = (1L << DT_POLY_BITS) - 1;
salt = (int)((refs >> (DT_POLY_BITS + DT_TILE_BITS)) & saltMask);
it = (int)((refs >> DT_POLY_BITS) & tileMask);
ip = (int)(refs & polyMask);
}
/// Extracts a tile's salt value from the specified polygon reference.
/// @note This function is generally meant for internal use only.
/// @param[in] ref The polygon reference.
/// @see #encodePolyId
public static int DecodePolyIdSalt(long refs)
{
long saltMask = (1L << DT_SALT_BITS) - 1;
return (int)((refs >> (DT_POLY_BITS + DT_TILE_BITS)) & saltMask);
}
/// Extracts the tile's index from the specified polygon reference.
/// @note This function is generally meant for internal use only.
/// @param[in] ref The polygon reference.
/// @see #encodePolyId
public static int DecodePolyIdTile(long refs)
{
long tileMask = (1L << DT_TILE_BITS) - 1;
return (int)((refs >> DT_POLY_BITS) & tileMask);
}
/// Extracts the polygon's index (within its tile) from the specified
/// polygon reference.
/// @note This function is generally meant for internal use only.
/// @param[in] ref The polygon reference.
/// @see #encodePolyId
public static int DecodePolyIdPoly(long refs)
{
long polyMask = (1L << DT_POLY_BITS) - 1;
return (int)(refs & polyMask);
}
public static int ComputeTileHash(int x, int y, int mask)
{
uint h1 = 0x8da6b343; // Large multiplicative constants;
uint h2 = 0xd8163841; // here arbitrarily chosen primes
uint n = h1 * (uint)x + h2 * (uint)y;
return (int)(n & mask);
}
public static float GetSlabCoord(float[] verts, int va, int side)
{
if (side == 0 || side == 4)
{
return verts[va];
}
else if (side == 2 || side == 6)
{
return verts[va + 2];
}
return 0;
}
public static void CalcSlabEndPoints(float[] verts, int va, int vb, ref RcVec2f bmin, ref RcVec2f bmax, int side)
{
if (side == 0 || side == 4)
{
if (verts[va + 2] < verts[vb + 2])
{
bmin.X = verts[va + 2];
bmin.Y = verts[va + 1];
bmax.X = verts[vb + 2];
bmax.Y = verts[vb + 1];
}
else
{
bmin.X = verts[vb + 2];
bmin.Y = verts[vb + 1];
bmax.X = verts[va + 2];
bmax.Y = verts[va + 1];
}
}
else if (side == 2 || side == 6)
{
if (verts[va + 0] < verts[vb + 0])
{
bmin.X = verts[va + 0];
bmin.Y = verts[va + 1];
bmax.X = verts[vb + 0];
bmax.Y = verts[vb + 1];
}
else
{
bmin.X = verts[vb + 0];
bmin.Y = verts[vb + 1];
bmax.X = verts[va + 0];
bmax.Y = verts[va + 1];
}
}
}
/// Get flags for edge in detail triangle.
/// @param[in] triFlags The flags for the triangle (last component of detail vertices above).
/// @param[in] edgeIndex The index of the first vertex of the edge. For instance, if 0,
/// returns flags for edge AB.
public static int GetDetailTriEdgeFlags(int triFlags, int edgeIndex)
{
return (triFlags >> (edgeIndex * 2)) & 0x3;
}
}
}

View File

@ -3,38 +3,31 @@ using DotRecast.Core.Numerics;
namespace DotRecast.Detour namespace DotRecast.Detour
{ {
public struct DtFindNearestPolyQuery : IDtPolyQuery public class DtFindNearestPolyQuery : IDtPolyQuery
{ {
private readonly DtNavMeshQuery _query; private readonly DtNavMeshQuery _query;
private readonly RcVec3f _center; private readonly RcVec3f _center;
private float _nearestDistanceSqr;
private long _nearestRef; private long _nearestRef;
private RcVec3f _nearestPoint; private RcVec3f _nearestPt;
private bool _overPoly; private bool _overPoly;
private float _nearestDistanceSqr;
public DtFindNearestPolyQuery(DtNavMeshQuery query, RcVec3f center) public DtFindNearestPolyQuery(DtNavMeshQuery query, RcVec3f center)
{ {
_query = query; this._query = query;
_center = center; this._center = center;
_nearestDistanceSqr = float.MaxValue; _nearestDistanceSqr = float.MaxValue;
_nearestPoint = center; _nearestPt = center;
_nearestRef = default;
_overPoly = default;
} }
public void Process(DtMeshTile tile, Span<DtPoly> poly, Span<long> refs, int count) public void Process(DtMeshTile tile, DtPoly poly, long refs)
{ {
for (int i = 0; i < count; ++i)
{
long polyRef = refs[i];
float d;
// Find nearest polygon amongst the nearby polygons. // Find nearest polygon amongst the nearby polygons.
_query.ClosestPointOnPoly(polyRef, _center, out var closestPtPoly, out var posOverPoly); _query.ClosestPointOnPoly(refs, _center, out var closestPtPoly, out var posOverPoly);
// If a point is directly over a polygon and closer than // If a point is directly over a polygon and closer than
// climb height, favor that instead of straight line nearest point. // climb height, favor that instead of straight line nearest point.
float d = 0;
RcVec3f diff = RcVec3f.Subtract(_center, closestPtPoly); RcVec3f diff = RcVec3f.Subtract(_center, closestPtPoly);
if (posOverPoly) if (posOverPoly)
{ {
@ -48,13 +41,12 @@ namespace DotRecast.Detour
if (d < _nearestDistanceSqr) if (d < _nearestDistanceSqr)
{ {
_nearestPoint = closestPtPoly; _nearestPt = closestPtPoly;
_nearestDistanceSqr = d; _nearestDistanceSqr = d;
_nearestRef = polyRef; _nearestRef = refs;
_overPoly = posOverPoly; _overPoly = posOverPoly;
} }
} }
}
public long NearestRef() public long NearestRef()
{ {
@ -63,7 +55,7 @@ namespace DotRecast.Detour
public RcVec3f NearestPt() public RcVec3f NearestPt()
{ {
return _nearestPoint; return _nearestPt;
} }
public bool OverPoly() public bool OverPoly()

View File

@ -1,4 +1,4 @@
namespace DotRecast.Detour namespace DotRecast.Detour
{ {
public readonly struct DtFindPathOption public readonly struct DtFindPathOption
{ {

View File

@ -1,4 +1,4 @@
namespace DotRecast.Detour namespace DotRecast.Detour
{ {
/// Options for dtNavMeshQuery::initSlicedFindPath and updateSlicedFindPath /// Options for dtNavMeshQuery::initSlicedFindPath and updateSlicedFindPath
public static class DtFindPathOptions public static class DtFindPathOptions

View File

@ -1,45 +0,0 @@
using System;
using DotRecast.Core;
using DotRecast.Core.Numerics;
namespace DotRecast.Detour
{
public struct DtHeightSamplePolyQuery : IDtPolyQuery
{
private readonly DtNavMeshQuery _navMeshQuery;
private readonly RcVec3f _pt;
private readonly float _maxHeight;
public float MinHeight { get; private set; }
public bool Found { get; private set; }
public DtHeightSamplePolyQuery(DtNavMeshQuery navMeshQuery, RcVec3f pt, float minHeight, float maxHeight)
{
_navMeshQuery = navMeshQuery;
_pt = pt;
MinHeight = minHeight;
_maxHeight = maxHeight;
Found = default;
}
public void Process(DtMeshTile tile, Span<DtPoly> poly, Span<long> refs, int count)
{
for (int i = 0; i < count; i++)
{
ProcessSingle(refs[i]);
}
}
private void ProcessSingle(long refs)
{
var status = _navMeshQuery.GetPolyHeight(refs, _pt, out var h);
if (!status.Succeeded())
return;
if (!(h > MinHeight) || !(h < _maxHeight))
return;
MinHeight = h;
Found = true;
}
}
}

View File

@ -1,7 +1,7 @@
/* /*
Copyright (c) 2009-2010 Mikko Mononen memon@inside.org Copyright (c) 2009-2010 Mikko Mononen memon@inside.org
recast4j copyright (c) 2015-2019 Piotr Piastucki piotr@jtilia.org recast4j copyright (c) 2015-2019 Piotr Piastucki piotr@jtilia.org
DotRecast Copyright (c) 2023-2024 Choi Ikpil ikpil@naver.com DotRecast Copyright (c) 2023 Choi Ikpil ikpil@naver.com
This software is provided 'as-is', without any express or implied This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages warranty. In no event will the authors be held liable for any damages
@ -18,20 +18,32 @@ freely, subject to the following restrictions:
3. This notice may not be removed or altered from any source distribution. 3. This notice may not be removed or altered from any source distribution.
*/ */
using System;
namespace DotRecast.Detour namespace DotRecast.Detour
{ {
/// Defines a link between polygons. /**
/// @note This structure is rarely if ever used by the end user. * Defines a link between polygons.
/// @see dtMeshTile *
* @note This structure is rarely if ever used by the end user.
* @see MeshTile
*/
public class DtLink public class DtLink
{ {
public long refs; //< Neighbour reference. (The neighbor that is linked to.) /** Neighbour reference. (The neighbor that is linked to.) */
public int next; //< Index of the next link. public long refs;
public byte edge; //< Index of the polygon edge that owns this link.
public byte side; //< If a boundary link, defines on which side the link is. /** Index of the next link. */
public byte bmin; //< If a boundary link, defines the minimum sub-edge area. public int next;
public byte bmax; //< If a boundary link, defines the maximum sub-edge area.
/** Index of the polygon edge that owns this link. */
public int edge;
/** If a boundary link, defines on which side the link is. */
public int side;
/** If a boundary link, defines the minimum sub-edge area. */
public int bmin;
/** If a boundary link, defines the maximum sub-edge area. */
public int bmax;
} }
} }

View File

@ -1,7 +1,7 @@
/* /*
Copyright (c) 2009-2010 Mikko Mononen memon@inside.org Copyright (c) 2009-2010 Mikko Mononen memon@inside.org
recast4j copyright (c) 2015-2019 Piotr Piastucki piotr@jtilia.org recast4j copyright (c) 2015-2019 Piotr Piastucki piotr@jtilia.org
DotRecast Copyright (c) 2023-2024 Choi Ikpil ikpil@naver.com DotRecast Copyright (c) 2023 Choi Ikpil ikpil@naver.com
This software is provided 'as-is', without any express or implied This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages warranty. In no event will the authors be held liable for any damages
@ -18,29 +18,37 @@ freely, subject to the following restrictions:
3. This notice may not be removed or altered from any source distribution. 3. This notice may not be removed or altered from any source distribution.
*/ */
using System;
namespace DotRecast.Detour namespace DotRecast.Detour
{ {
[Serializable]
public class DtMeshData public class DtMeshData
{ {
public DtMeshHeader header; //< The tile header. /** The tile header. */
public DtPoly[] polys; //< The tile polygons. [Size: dtMeshHeader::polyCount] public DtMeshHeader header;
public float[] verts; //< The tile vertices. [(x, y, z) * dtMeshHeader::vertCount]
public DtPolyDetail[] detailMeshes; //< The tile's detail sub-meshes. [Size: dtMeshHeader::detailMeshCount]
/// The detail mesh's unique vertices. [(x, y, z) * dtMeshHeader::detailVertCount] /** The tile vertices. [Size: MeshHeader::vertCount] */
public float[] verts;
/** The tile polygons. [Size: MeshHeader::polyCount] */
public DtPoly[] polys;
/** The tile's detail sub-meshes. [Size: MeshHeader::detailMeshCount] */
public DtPolyDetail[] detailMeshes;
/** The detail mesh's unique vertices. [(x, y, z) * MeshHeader::detailVertCount] */
public float[] detailVerts; public float[] detailVerts;
/// The detail mesh's triangles. [(vertA, vertB, vertC, triFlags) * dtMeshHeader::detailTriCount]. /**
/// See dtDetailTriEdgeFlags and dtGetDetailTriEdgeFlags. * The detail mesh's triangles. [(vertA, vertB, vertC) * MeshHeader::detailTriCount] See DetailTriEdgeFlags and
* NavMesh::getDetailTriEdgeFlags.
*/
public int[] detailTris; public int[] detailTris;
/// The tile bounding volume nodes. [Size: dtMeshHeader::bvNodeCount] /**
/// (Will be null if bounding volumes are disabled.) * The tile bounding volume nodes. [Size: MeshHeader::bvNodeCount] (Will be null if bounding volumes are disabled.)
*/
public DtBVNode[] bvTree; public DtBVNode[] bvTree;
public DtOffMeshConnection[] offMeshCons; //< The tile off-mesh connections. [Size: dtMeshHeader::offMeshConCount] /** The tile off-mesh connections. [Size: MeshHeader::offMeshConCount] */
public DtOffMeshConnection[] offMeshCons;
} }
} }

View File

@ -1,7 +1,7 @@
/* /*
Copyright (c) 2009-2010 Mikko Mononen memon@inside.org Copyright (c) 2009-2010 Mikko Mononen memon@inside.org
recast4j copyright (c) 2015-2019 Piotr Piastucki piotr@jtilia.org recast4j copyright (c) 2015-2019 Piotr Piastucki piotr@jtilia.org
DotRecast Copyright (c) 2023-2024 Choi Ikpil ikpil@naver.com DotRecast Copyright (c) 2023 Choi Ikpil ikpil@naver.com
This software is provided 'as-is', without any express or implied This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages warranty. In no event will the authors be held liable for any damages
@ -18,15 +18,30 @@ freely, subject to the following restrictions:
3. This notice may not be removed or altered from any source distribution. 3. This notice may not be removed or altered from any source distribution.
*/ */
using System;
using DotRecast.Core.Numerics; using DotRecast.Core.Numerics;
namespace DotRecast.Detour namespace DotRecast.Detour
{ {
/** Provides high level information related to a dtMeshTile object. */ /** Provides high level information related to a dtMeshTile object. */
[Serializable]
public class DtMeshHeader 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.) */ /** Tile magic number. (Used to identify the data format.) */
public int magic; public int magic;

View File

@ -1,7 +1,7 @@
/* /*
Copyright (c) 2009-2010 Mikko Mononen memon@inside.org Copyright (c) 2009-2010 Mikko Mononen memon@inside.org
recast4j copyright (c) 2015-2019 Piotr Piastucki piotr@jtilia.org recast4j copyright (c) 2015-2019 Piotr Piastucki piotr@jtilia.org
DotRecast Copyright (c) 2023-2024 Choi Ikpil ikpil@naver.com DotRecast Copyright (c) 2023 Choi Ikpil ikpil@naver.com
This software is provided 'as-is', without any express or implied This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages warranty. In no event will the authors be held liable for any damages
@ -18,22 +18,33 @@ freely, subject to the following restrictions:
3. This notice may not be removed or altered from any source distribution. 3. This notice may not be removed or altered from any source distribution.
*/ */
using System.Collections.Generic;
namespace DotRecast.Detour namespace DotRecast.Detour
{ {
using static DtDetour; /**
* Defines a navigation mesh tile.
/// Defines a navigation mesh tile. */
/// @ingroup detour
public class DtMeshTile public class DtMeshTile
{ {
public readonly int index; // DtNavMesh.m_tiles array index public readonly int index;
public int linksFreeList = DT_NULL_LINK; //< Index to the next free link.
public int salt; //< Counter describing modifications to the tile.
public DtMeshData data; // The tile data.
public DtLink[] links; // The tile links. [Size: dtMeshHeader::maxLinkCount]
public int flags; //< Tile flags. (See: #dtTileFlags) /** Counter describing modifications to the tile. */
public DtMeshTile next; //< The next free tile, or the next tile in the spatial grid. public int salt;
/** The tile data. */
public DtMeshData data;
public int[] polyLinks;
/** The tile links. */
public readonly List<DtLink> links = new List<DtLink>();
/** Index to the next free link. */
public int linksFreeList = DtNavMesh.DT_NULL_LINK; // FIXME: Remove
/** Tile flags. (See: #dtTileFlags) */
public int flags;
public DtMeshTile(int index) public DtMeshTile(int index)
{ {

File diff suppressed because it is too large Load Diff

View File

@ -1,7 +1,7 @@
/* /*
Copyright (c) 2009-2010 Mikko Mononen memon@inside.org Copyright (c) 2009-2010 Mikko Mononen memon@inside.org
recast4j copyright (c) 2015-2019 Piotr Piastucki piotr@jtilia.org recast4j copyright (c) 2015-2019 Piotr Piastucki piotr@jtilia.org
DotRecast Copyright (c) 2023-2024 Choi Ikpil ikpil@naver.com DotRecast Copyright (c) 2023 Choi Ikpil ikpil@naver.com
This software is provided 'as-is', without any express or implied This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages warranty. In no event will the authors be held liable for any damages
@ -19,40 +19,46 @@ freely, subject to the following restrictions:
*/ */
using System; using System;
using DotRecast.Core;
using DotRecast.Core.Numerics; using DotRecast.Core.Numerics;
namespace DotRecast.Detour namespace DotRecast.Detour
{ {
using static DtDetour;
public static class DtNavMeshBuilder public static class DtNavMeshBuilder
{ {
const int MESH_NULL_IDX = 0xffff; const int MESH_NULL_IDX = 0xffff;
private static void CalcExtends(BVItem[] items, int nitems, int imin, int imax, ref RcVec3i bmin, ref RcVec3i bmax) private static int[][] CalcExtends(BVItem[] items, int nitems, int imin, int imax)
{ {
bmin = items[imin].bmin; int[] bmin = new int[3];
bmax = items[imin].bmax; int[] bmax = new int[3];
bmin[0] = items[imin].bmin[0];
bmin[1] = items[imin].bmin[1];
bmin[2] = items[imin].bmin[2];
bmax[0] = items[imin].bmax[0];
bmax[1] = items[imin].bmax[1];
bmax[2] = items[imin].bmax[2];
for (int i = imin + 1; i < imax; ++i) for (int i = imin + 1; i < imax; ++i)
{ {
BVItem it = items[i]; BVItem it = items[i];
if (it.bmin.X < bmin.X) if (it.bmin[0] < bmin[0])
bmin.X = it.bmin.X; bmin[0] = it.bmin[0];
if (it.bmin.Y < bmin.Y) if (it.bmin[1] < bmin[1])
bmin.Y = it.bmin.Y; bmin[1] = it.bmin[1];
if (it.bmin.Z < bmin.Z) if (it.bmin[2] < bmin[2])
bmin.Z = it.bmin.Z; bmin[2] = it.bmin[2];
if (it.bmax.X > bmax.X) if (it.bmax[0] > bmax[0])
bmax.X = it.bmax.X; bmax[0] = it.bmax[0];
if (it.bmax.Y > bmax.Y) if (it.bmax[1] > bmax[1])
bmax.Y = it.bmax.Y; bmax[1] = it.bmax[1];
if (it.bmax.Z > bmax.Z) if (it.bmax[2] > bmax[2])
bmax.Z = it.bmax.Z; bmax[2] = it.bmax[2];
} }
return new int[][] { bmin, bmax };
} }
private static int LongestAxis(int x, int y, int z) private static int LongestAxis(int x, int y, int z)
@ -85,21 +91,25 @@ namespace DotRecast.Detour
if (inum == 1) if (inum == 1)
{ {
// Leaf // Leaf
node.bmin = items[imin].bmin; node.bmin[0] = items[imin].bmin[0];
node.bmax = items[imin].bmax; node.bmin[1] = items[imin].bmin[1];
node.bmin[2] = items[imin].bmin[2];
node.bmax[0] = items[imin].bmax[0];
node.bmax[1] = items[imin].bmax[1];
node.bmax[2] = items[imin].bmax[2];
node.i = items[imin].i; node.i = items[imin].i;
} }
else else
{ {
// Split // Split
CalcExtends(items, nitems, imin, imax, ref node.bmin, ref node.bmax); int[][] minmax = CalcExtends(items, nitems, imin, imax);
node.bmin = minmax[0];
node.bmax = minmax[1];
int axis = LongestAxis( int axis = LongestAxis(node.bmax[0] - node.bmin[0], node.bmax[1] - node.bmin[1],
node.bmax.X - node.bmin.X, node.bmax[2] - node.bmin[2]);
node.bmax.Y - node.bmin.Y,
node.bmax.Z - node.bmin.Z
);
if (axis == 0) if (axis == 0)
{ {
@ -148,29 +158,29 @@ namespace DotRecast.Detour
int vb = option.detailMeshes[i * 4 + 0]; int vb = option.detailMeshes[i * 4 + 0];
int ndv = option.detailMeshes[i * 4 + 1]; int ndv = option.detailMeshes[i * 4 + 1];
int dv = vb * 3; int dv = vb * 3;
var bmin = RcVec.Create(option.detailVerts, dv); var bmin = RcVecUtils.Create(option.detailVerts, dv);
var bmax = RcVec.Create(option.detailVerts, dv); var bmax = RcVecUtils.Create(option.detailVerts, dv);
for (int j = 1; j < ndv; j++) for (int j = 1; j < ndv; j++)
{ {
bmin = RcVec3f.Min(bmin, RcVec.Create(option.detailVerts, dv + j * 3)); bmin = RcVecUtils.Min(bmin, option.detailVerts, dv + j * 3);
bmax = RcVec3f.Max(bmax, RcVec.Create(option.detailVerts, dv + j * 3)); bmax = RcVecUtils.Max(bmax, option.detailVerts, dv + j * 3);
} }
// BV-tree uses cs for all dimensions // BV-tree uses cs for all dimensions
it.bmin.X = Math.Clamp((int)((bmin.X - option.bmin.X) * quantFactor), 0, int.MaxValue); it.bmin[0] = Math.Clamp((int)((bmin.X - option.bmin.X) * quantFactor), 0, int.MaxValue);
it.bmin.Y = Math.Clamp((int)((bmin.Y - option.bmin.Y) * quantFactor), 0, int.MaxValue); it.bmin[1] = Math.Clamp((int)((bmin.Y - option.bmin.Y) * quantFactor), 0, int.MaxValue);
it.bmin.Z = Math.Clamp((int)((bmin.Z - option.bmin.Z) * quantFactor), 0, int.MaxValue); it.bmin[2] = Math.Clamp((int)((bmin.Z - option.bmin.Z) * quantFactor), 0, int.MaxValue);
it.bmax.X = Math.Clamp((int)((bmax.X - option.bmin.X) * quantFactor), 0, int.MaxValue); it.bmax[0] = Math.Clamp((int)((bmax.X - option.bmin.X) * quantFactor), 0, int.MaxValue);
it.bmax.Y = Math.Clamp((int)((bmax.Y - option.bmin.Y) * quantFactor), 0, int.MaxValue); it.bmax[1] = Math.Clamp((int)((bmax.Y - option.bmin.Y) * quantFactor), 0, int.MaxValue);
it.bmax.Z = Math.Clamp((int)((bmax.Z - option.bmin.Z) * quantFactor), 0, int.MaxValue); it.bmax[2] = Math.Clamp((int)((bmax.Z - option.bmin.Z) * quantFactor), 0, int.MaxValue);
} }
else else
{ {
int p = i * option.nvp * 2; int p = i * option.nvp * 2;
it.bmin.X = it.bmax.X = option.verts[option.polys[p] * 3 + 0]; it.bmin[0] = it.bmax[0] = option.verts[option.polys[p] * 3 + 0];
it.bmin.Y = it.bmax.Y = option.verts[option.polys[p] * 3 + 1]; it.bmin[1] = it.bmax[1] = option.verts[option.polys[p] * 3 + 1];
it.bmin.Z = it.bmax.Z = option.verts[option.polys[p] * 3 + 2]; it.bmin[2] = it.bmax[2] = option.verts[option.polys[p] * 3 + 2];
for (int j = 1; j < option.nvp; ++j) for (int j = 1; j < option.nvp; ++j)
{ {
@ -180,24 +190,24 @@ namespace DotRecast.Detour
int y = option.verts[option.polys[p + j] * 3 + 1]; int y = option.verts[option.polys[p + j] * 3 + 1];
int z = option.verts[option.polys[p + j] * 3 + 2]; int z = option.verts[option.polys[p + j] * 3 + 2];
if (x < it.bmin.X) if (x < it.bmin[0])
it.bmin.X = x; it.bmin[0] = x;
if (y < it.bmin.Y) if (y < it.bmin[1])
it.bmin.Y = y; it.bmin[1] = y;
if (z < it.bmin.Z) if (z < it.bmin[2])
it.bmin.Z = z; it.bmin[2] = z;
if (x > it.bmax.X) if (x > it.bmax[0])
it.bmax.X = x; it.bmax[0] = x;
if (y > it.bmax.Y) if (y > it.bmax[1])
it.bmax.Y = y; it.bmax[1] = y;
if (z > it.bmax.Z) if (z > it.bmax[2])
it.bmax.Z = z; it.bmax[2] = z;
} }
// Remap y // Remap y
it.bmin.Y = (int)MathF.Floor(it.bmin.Y * option.ch * quantFactor); it.bmin[1] = (int)MathF.Floor(it.bmin[1] * option.ch * quantFactor);
it.bmax.Y = (int)MathF.Ceiling(it.bmax.Y * option.ch * quantFactor); it.bmax[1] = (int)MathF.Ceiling(it.bmax[1] * option.ch * quantFactor);
} }
} }
@ -240,15 +250,14 @@ namespace DotRecast.Detour
return 0xff; return 0xff;
} }
// TODO: Better error handling. /**
* Builds navigation mesh tile data from the provided tile creation data.
/// @par *
/// * @param option
/// The output data array is allocated using the detour allocator (dtAlloc()). The method * Tile creation data.
/// used to free the memory will be determined by how the tile is added to the navigation *
/// mesh. * @return created tile data
/// */
/// @see dtNavMesh, dtNavMesh::addTile()
public static DtMeshData CreateNavMeshData(DtNavMeshCreateParams option) public static DtMeshData CreateNavMeshData(DtNavMeshCreateParams option)
{ {
if (option.vertCount >= 0xffff) if (option.vertCount >= 0xffff)
@ -306,8 +315,8 @@ namespace DotRecast.Detour
for (int i = 0; i < option.offMeshConCount; ++i) for (int i = 0; i < option.offMeshConCount; ++i)
{ {
var p0 = RcVec.Create(option.offMeshConVerts, (i * 2 + 0) * 3); var p0 = RcVecUtils.Create(option.offMeshConVerts, (i * 2 + 0) * 3);
var p1 = RcVec.Create(option.offMeshConVerts, (i * 2 + 1) * 3); var p1 = RcVecUtils.Create(option.offMeshConVerts, (i * 2 + 1) * 3);
offMeshConClass[i * 2 + 0] = ClassifyOffMeshPoint(p0, bmin, bmax); offMeshConClass[i * 2 + 0] = ClassifyOffMeshPoint(p0, bmin, bmax);
offMeshConClass[i * 2 + 1] = ClassifyOffMeshPoint(p1, bmin, bmax); offMeshConClass[i * 2 + 1] = ClassifyOffMeshPoint(p1, bmin, bmax);
@ -414,8 +423,8 @@ namespace DotRecast.Detour
DtOffMeshConnection[] offMeshCons = new DtOffMeshConnection[storedOffMeshConCount]; DtOffMeshConnection[] offMeshCons = new DtOffMeshConnection[storedOffMeshConCount];
// Store header // Store header
header.magic = DT_NAVMESH_MAGIC; header.magic = DtMeshHeader.DT_NAVMESH_MAGIC;
header.version = DT_NAVMESH_VERSION; header.version = DtMeshHeader.DT_NAVMESH_VERSION;
header.x = option.tileX; header.x = option.tileX;
header.y = option.tileZ; header.y = option.tileZ;
header.layer = option.tileLayer; header.layer = option.tileLayer;
@ -459,7 +468,7 @@ namespace DotRecast.Detour
{ {
int linkv = i * 2 * 3; int linkv = i * 2 * 3;
int v = (offMeshVertsBase + n * 2) * 3; int v = (offMeshVertsBase + n * 2) * 3;
RcArrays.Copy(option.offMeshConVerts, linkv, navVerts, v, 6); Array.Copy(option.offMeshConVerts, linkv, navVerts, v, 6);
n++; n++;
} }
} }
@ -487,13 +496,13 @@ namespace DotRecast.Detour
if (dir == 0xf) // Border if (dir == 0xf) // Border
p.neis[j] = 0; p.neis[j] = 0;
else if (dir == 0) // Portal x- else if (dir == 0) // Portal x-
p.neis[j] = DT_EXT_LINK | 4; p.neis[j] = DtNavMesh.DT_EXT_LINK | 4;
else if (dir == 1) // Portal z+ else if (dir == 1) // Portal z+
p.neis[j] = DT_EXT_LINK | 2; p.neis[j] = DtNavMesh.DT_EXT_LINK | 2;
else if (dir == 2) // Portal x+ else if (dir == 2) // Portal x+
p.neis[j] = DT_EXT_LINK | 0; p.neis[j] = DtNavMesh.DT_EXT_LINK | 0;
else if (dir == 3) // Portal z- else if (dir == 3) // Portal z-
p.neis[j] = DT_EXT_LINK | 6; p.neis[j] = DtNavMesh.DT_EXT_LINK | 6;
} }
else else
{ {
@ -536,25 +545,26 @@ namespace DotRecast.Detour
int vbase = 0; int vbase = 0;
for (int i = 0; i < option.polyCount; ++i) for (int i = 0; i < option.polyCount; ++i)
{ {
DtPolyDetail dtl = new DtPolyDetail();
navDMeshes[i] = dtl;
int vb = option.detailMeshes[i * 4 + 0]; int vb = option.detailMeshes[i * 4 + 0];
int ndv = option.detailMeshes[i * 4 + 1]; int ndv = option.detailMeshes[i * 4 + 1];
int nv = navPolys[i].vertCount; int nv = navPolys[i].vertCount;
int vertBase = vbase; dtl.vertBase = vbase;
byte vertCount = (byte)(ndv - nv); dtl.vertCount = (ndv - nv);
int triBase = option.detailMeshes[i * 4 + 2]; dtl.triBase = option.detailMeshes[i * 4 + 2];
byte triCount = (byte)option.detailMeshes[i * 4 + 3]; dtl.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 // Copy vertices except the first 'nv' verts which are equal to
// nav poly verts. // nav poly verts.
if (ndv - nv != 0) if (ndv - nv != 0)
{ {
RcArrays.Copy(option.detailVerts, (vb + nv) * 3, navDVerts, vbase * 3, 3 * (ndv - nv)); Array.Copy(option.detailVerts, (vb + nv) * 3, navDVerts, vbase * 3, 3 * (ndv - nv));
vbase += ndv - nv; vbase += ndv - nv;
} }
} }
// Store triangles. // Store triangles.
RcArrays.Copy(option.detailTris, 0, navDTris, 0, 4 * option.detailTriCount); Array.Copy(option.detailTris, 0, navDTris, 0, 4 * option.detailTriCount);
} }
else else
{ {
@ -562,12 +572,13 @@ namespace DotRecast.Detour
int tbase = 0; int tbase = 0;
for (int i = 0; i < option.polyCount; ++i) for (int i = 0; i < option.polyCount; ++i)
{ {
DtPolyDetail dtl = new DtPolyDetail();
navDMeshes[i] = dtl;
int nv = navPolys[i].vertCount; int nv = navPolys[i].vertCount;
int vertBase = 0; dtl.vertBase = 0;
byte vertCount = 0; dtl.vertCount = 0;
int triBase = tbase; dtl.triBase = tbase;
byte triCount = (byte)(nv - 2); dtl.triCount = (nv - 2);
navDMeshes[i] = new DtPolyDetail(vertBase, triBase, vertCount, triCount);
// Triangulate polygon (local indices). // Triangulate polygon (local indices).
for (int j = 2; j < nv; ++j) for (int j = 2; j < nv; ++j)
{ {
@ -606,13 +617,9 @@ namespace DotRecast.Detour
con.poly = (offMeshPolyBase + n); con.poly = (offMeshPolyBase + n);
// Copy connection end-points. // Copy connection end-points.
int endPts = i * 2 * 3; int endPts = i * 2 * 3;
for (int j = 0; j < 2; ++j) Array.Copy(option.offMeshConVerts, endPts, con.pos, 0, 6);
{
con.pos[j] = RcVec.Create(option.offMeshConVerts, endPts + (j * 3));
}
con.rad = option.offMeshConRad[i]; con.rad = option.offMeshConRad[i];
con.flags = option.offMeshConDir[i] != 0 ? DT_OFFMESH_CON_BIDIR : 0; con.flags = option.offMeshConDir[i] != 0 ? DtNavMesh.DT_OFFMESH_CON_BIDIR : 0;
con.side = offMeshConClass[i * 2 + 1]; con.side = offMeshConClass[i * 2 + 1];
if (option.offMeshConUserID != null) if (option.offMeshConUserID != null)
con.userId = option.offMeshConUserID[i]; con.userId = option.offMeshConUserID[i];

View File

@ -1,7 +1,7 @@
/* /*
Copyright (c) 2009-2010 Mikko Mononen memon@inside.org Copyright (c) 2009-2010 Mikko Mononen memon@inside.org
recast4j copyright (c) 2015-2019 Piotr Piastucki piotr@jtilia.org recast4j copyright (c) 2015-2019 Piotr Piastucki piotr@jtilia.org
DotRecast Copyright (c) 2023-2024 Choi Ikpil ikpil@naver.com DotRecast Copyright (c) 2023 Choi Ikpil ikpil@naver.com
This software is provided 'as-is', without any express or implied This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages warranty. In no event will the authors be held liable for any damages

View File

@ -1,7 +1,7 @@
/* /*
Copyright (c) 2009-2010 Mikko Mononen memon@inside.org Copyright (c) 2009-2010 Mikko Mononen memon@inside.org
recast4j copyright (c) 2015-2019 Piotr Piastucki piotr@jtilia.org recast4j copyright (c) 2015-2019 Piotr Piastucki piotr@jtilia.org
DotRecast Copyright (c) 2023-2024 Choi Ikpil ikpil@naver.com DotRecast Copyright (c) 2023 Choi Ikpil ikpil@naver.com
This software is provided 'as-is', without any express or implied This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages warranty. In no event will the authors be held liable for any damages
@ -18,21 +18,31 @@ freely, subject to the following restrictions:
3. This notice may not be removed or altered from any source distribution. 3. This notice may not be removed or altered from any source distribution.
*/ */
using System;
using DotRecast.Core.Numerics; using DotRecast.Core.Numerics;
namespace DotRecast.Detour namespace DotRecast.Detour
{ {
/// Configuration parameters used to define multi-tile navigation meshes. /**
/// The values are used to allocate space during the initialization of a navigation mesh. * Configuration parameters used to define multi-tile navigation meshes. The values are used to allocate space during
/// @see dtNavMesh::init() * the initialization of a navigation mesh.
/// @ingroup detour *
* @see NavMesh
*/
public struct DtNavMeshParams public struct DtNavMeshParams
{ {
public RcVec3f orig; //< The world space origin of the navigation mesh's tile space. [(x, y, z)] /** The world space origin of the navigation mesh's tile space. [(x, y, z)] */
public float tileWidth; //< The width of each tile. (Along the x-axis.) public RcVec3f orig;
public float tileHeight; //< The height of each tile. (Along the z-axis.)
public int maxTiles; //< The maximum number of tiles the navigation mesh can contain. This and maxPolys are used to calculate how many bits are needed to identify tiles and polygons uniquely. /** The width of each tile. (Along the x-axis.) */
public int maxPolys; //< The maximum number of polygons each tile can contain. This and maxTiles are used to calculate how many bits are needed to identify tiles and polygons uniquely. public float tileWidth;
/** The height of each tile. (Along the z-axis.) */
public float tileHeight;
/** The maximum number of tiles the navigation mesh can contain. */
public int maxTiles;
/** The maximum number of polygons each tile can contain. */
public int maxPolys;
} }
} }

File diff suppressed because it is too large Load Diff

View File

@ -1,34 +0,0 @@
using System;
using System.Collections.Generic;
using DotRecast.Core.Numerics;
namespace DotRecast.Detour
{
public class DtNavMeshQueryMock : DtNavMeshQuery
{
private readonly DtStraightPath[] _straightPath;
private readonly DtStatus _status;
public DtNavMeshQueryMock(DtStraightPath[] straightPath, DtStatus status)
: base(null)
{
_straightPath = straightPath;
_status = status;
}
public override DtStatus FindStraightPath(RcVec3f startPos, RcVec3f endPos,
List<long> path, int pathSize,
Span<DtStraightPath> straightPath, out int straightPathCount, int maxStraightPath,
int options)
{
straightPathCount = 0;
for (int i = 0; i < _straightPath.Length && i < maxStraightPath; ++i)
{
straightPath[i] = _straightPath[i];
straightPathCount += 1;
}
return _status;
}
}
}

View File

@ -1,6 +1,6 @@
/* /*
recast4j copyright (c) 2021 Piotr Piastucki piotr@jtilia.org recast4j copyright (c) 2021 Piotr Piastucki piotr@jtilia.org
DotRecast Copyright (c) 2023-2024 Choi Ikpil ikpil@naver.com DotRecast Copyright (c) 2023 Choi Ikpil ikpil@naver.com
This software is provided 'as-is', without any express or implied This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages warranty. In no event will the authors be held liable for any damages
@ -17,7 +17,6 @@ freely, subject to the following restrictions:
3. This notice may not be removed or altered from any source distribution. 3. This notice may not be removed or altered from any source distribution.
*/ */
using System;
using DotRecast.Core; using DotRecast.Core;
using DotRecast.Core.Numerics; using DotRecast.Core.Numerics;
@ -49,7 +48,6 @@ namespace DotRecast.Detour
private static bool Raycast(DtMeshTile tile, RcVec3f sp, RcVec3f sq, out float hitTime) private static bool Raycast(DtMeshTile tile, RcVec3f sp, RcVec3f sq, out float hitTime)
{ {
hitTime = 0.0f; hitTime = 0.0f;
Span<RcVec3f> tempVerts = stackalloc RcVec3f[3];
for (int i = 0; i < tile.data.header.polyCount; ++i) for (int i = 0; i < tile.data.header.polyCount; ++i)
{ {
DtPoly p = tile.data.polys[i]; DtPoly p = tile.data.polys[i];
@ -58,9 +56,11 @@ namespace DotRecast.Detour
continue; continue;
} }
ref DtPolyDetail pd = ref tile.data.detailMeshes[i]; DtPolyDetail pd = tile.data.detailMeshes[i];
Span<RcVec3f> verts = tempVerts; if (pd != null)
{
RcVec3f[] verts = new RcVec3f[3];
for (int j = 0; j < pd.triCount; ++j) for (int j = 0; j < pd.triCount; ++j)
{ {
int t = (pd.triBase + j) * 4; int t = (pd.triBase + j) * 4;
@ -87,6 +87,11 @@ namespace DotRecast.Detour
} }
} }
} }
else
{
// FIXME: Use Poly if PolyDetail is unavailable
}
}
return false; return false;
} }

View File

@ -1,5 +1,4 @@
using System; using DotRecast.Core.Numerics;
using DotRecast.Core.Numerics;
namespace DotRecast.Detour namespace DotRecast.Detour
{ {
@ -11,11 +10,9 @@ namespace DotRecast.Detour
{ {
} }
public Span<float> Apply(Span<float> polyVerts, RcVec3f circleCenter, float radius, Span<float> resultBuffer) public float[] Apply(float[] polyVerts, RcVec3f circleCenter, float radius)
{ {
var result = resultBuffer.Slice(0, polyVerts.Length); return polyVerts;
polyVerts.CopyTo(result);
return result;
} }
} }
} }

View File

@ -1,7 +1,7 @@
/* /*
Copyright (c) 2009-2010 Mikko Mononen memon@inside.org Copyright (c) 2009-2010 Mikko Mononen memon@inside.org
recast4j copyright (c) 2015-2019 Piotr Piastucki piotr@jtilia.org recast4j copyright (c) 2015-2019 Piotr Piastucki piotr@jtilia.org
DotRecast Copyright (c) 2023-2024 Choi Ikpil ikpil@naver.com DotRecast Copyright (c) 2023 Choi Ikpil ikpil@naver.com
This software is provided 'as-is', without any express or implied This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages warranty. In no event will the authors be held liable for any damages
@ -25,34 +25,42 @@ namespace DotRecast.Detour
{ {
public class DtNode public class DtNode
{ {
public readonly int ptr; public readonly int index;
public RcVec3f pos; // Position of the node. /** Position of the node. */
public float cost; // Cost from previous node to current node. public RcVec3f pos = new RcVec3f();
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.
public DtNode(int ptr) /** 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)
{ {
this.ptr = ptr; this.index = index;
}
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() public override string ToString()
{ {
return $"Node [ptr={ptr} id={id} cost={cost} total={total}]"; return "Node [id=" + id + "]";
} }
} }
} }

View File

@ -1,4 +1,4 @@
namespace DotRecast.Detour namespace DotRecast.Detour
{ {
public static class DtNodeFlags public static class DtNodeFlags
{ {

View File

@ -1,7 +1,7 @@
/* /*
Copyright (c) 2009-2010 Mikko Mononen memon@inside.org Copyright (c) 2009-2010 Mikko Mononen memon@inside.org
recast4j copyright (c) 2015-2019 Piotr Piastucki piotr@jtilia.org recast4j copyright (c) 2015-2019 Piotr Piastucki piotr@jtilia.org
DotRecast Copyright (c) 2023-2024 Choi Ikpil ikpil@naver.com DotRecast Copyright (c) 2023 Choi Ikpil ikpil@naver.com
This software is provided 'as-is', without any express or implied This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages warranty. In no event will the authors be held liable for any damages
@ -18,95 +18,41 @@ freely, subject to the following restrictions:
3. This notice may not be removed or altered from any source distribution. 3. This notice may not be removed or altered from any source distribution.
*/ */
using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq;
using DotRecast.Core.Buffers;
namespace DotRecast.Detour namespace DotRecast.Detour
{ {
public class DtNodePool public class DtNodePool
{ {
private readonly Dictionary<long, List<DtNode>> m_map; private readonly Dictionary<long, List<DtNode>> m_map = new Dictionary<long, List<DtNode>>();
private readonly List<DtNode> m_nodes = new List<DtNode>();
private int m_usedNodesCount; public DtNodePool()
private List<DtNode[]> m_buckets;
private readonly int m_initialBufferCapacityBase;
private readonly RcObjectPool<List<DtNode>> m_listPool;
public DtNodePool(int initialBufferCapacityBase = 6) // initial size 64
{ {
m_map = new Dictionary<long, List<DtNode>>();
m_listPool = new RcObjectPool<List<DtNode>>(() => new List<DtNode>());
m_buckets = new List<DtNode[]>();
m_initialBufferCapacityBase = initialBufferCapacityBase;
}
private void AddNewBucket()
{
var bucketIndex = m_buckets.Count;
var bucket = new DtNode[1 << (bucketIndex + m_initialBufferCapacityBase)];
m_buckets.Add(bucket);
FillBucket(bucketIndex);
}
private void FillBucket(int bucketIndex)
{
var bucket = m_buckets[bucketIndex];
var startIndex = GetBucketStartIndex(bucketIndex);
for (int i = 0; i < bucket.Length; i++)
{
bucket[i] = new DtNode(startIndex + i);
}
}
private int GetBucketStartIndex(int bucketIndex)
{
return ((1 << (bucketIndex + m_initialBufferCapacityBase)) - 1) ^ ((1 << m_initialBufferCapacityBase) - 1);
}
private DtNode[] EnsureBucket(int bucketIndex)
{
if (m_buckets.Count == bucketIndex)
AddNewBucket();
else if (m_buckets.Count < bucketIndex)
throw new Exception();
return m_buckets[bucketIndex];
}
private int GetBucketIndexByElementIndex(int elementIndex)
{
return DtUtils.Ilog2((elementIndex >> m_initialBufferCapacityBase) + 1);
} }
public void Clear() public void Clear()
{ {
foreach (var pair in m_map) m_nodes.Clear();
m_listPool.Return(pair.Value);
m_map.Clear(); m_map.Clear();
m_usedNodesCount = 0;
} }
public int GetNodeCount() public List<DtNode> FindNodes(long id)
{ {
return m_usedNodesCount; var hasNode = m_map.TryGetValue(id, out var nodes);
;
if (nodes == null)
{
nodes = new List<DtNode>();
} }
public int FindNodes(long id, out List<DtNode> nodes) return nodes;
{
var hasNode = m_map.TryGetValue(id, out nodes);
if (hasNode)
{
return nodes.Count;
}
return 0;
} }
public DtNode FindNode(long id) public DtNode FindNode(long id)
{ {
m_map.TryGetValue(id, out var nodes); var hasNode = m_map.TryGetValue(id, out var nodes);
;
if (nodes != null && 0 != nodes.Count) if (nodes != null && 0 != nodes.Count)
{ {
return nodes[0]; return nodes[0];
@ -117,7 +63,7 @@ namespace DotRecast.Detour
public DtNode GetNode(long id, int state) public DtNode GetNode(long id, int state)
{ {
m_map.TryGetValue(id, out var nodes); var hasNode = m_map.TryGetValue(id, out var nodes);
if (nodes != null) if (nodes != null)
{ {
foreach (DtNode node in nodes) foreach (DtNode node in nodes)
@ -130,8 +76,7 @@ namespace DotRecast.Detour
} }
else else
{ {
nodes = m_listPool.Get(); nodes = new List<DtNode>();
nodes.Clear();
m_map.Add(id, nodes); m_map.Add(id, nodes);
} }
@ -140,17 +85,10 @@ namespace DotRecast.Detour
private DtNode Create(long id, int state, List<DtNode> nodes) private DtNode Create(long id, int state, List<DtNode> nodes)
{ {
int i = m_usedNodesCount++; DtNode node = new DtNode(m_nodes.Count + 1);
int bucketIndex = GetBucketIndexByElementIndex(i);
int bucketStartIndex = GetBucketStartIndex(bucketIndex);
var node = EnsureBucket(bucketIndex)[i - bucketStartIndex];
node.pidx = 0;
node.cost = 0;
node.total = 0;
node.id = id; node.id = id;
node.state = state; node.state = state;
node.flags = 0; m_nodes.Add(node);
node.shortcut = null;
nodes.Add(node); nodes.Add(node);
return node; return node;
@ -158,23 +96,12 @@ namespace DotRecast.Detour
public int GetNodeIdx(DtNode node) public int GetNodeIdx(DtNode node)
{ {
return node != null return node != null ? node.index : 0;
? node.ptr + 1
: 0;
} }
public DtNode GetNodeAtIdx(int idx) public DtNode GetNodeAtIdx(int idx)
{ {
if (idx == 0) return idx != 0 ? m_nodes[idx - 1] : null;
return null;
int bucketIndex = GetBucketIndexByElementIndex(idx - 1);
if (m_buckets.Count <= bucketIndex)
throw new ArgumentOutOfRangeException();
int bucketStartIndex = GetBucketStartIndex(bucketIndex);
var node = EnsureBucket(bucketIndex)[idx - bucketStartIndex - 1];
return node;
} }
public DtNode GetNode(long refs) public DtNode GetNode(long refs)
@ -182,9 +109,9 @@ namespace DotRecast.Detour
return GetNode(refs, 0); return GetNode(refs, 0);
} }
public IEnumerable<DtNode> AsEnumerable() public Dictionary<long, List<DtNode>> GetNodeMap()
{ {
return m_buckets.SelectMany(x => x); return m_map;
} }
} }
} }

View File

@ -1,7 +1,7 @@
/* /*
Copyright (c) 2009-2010 Mikko Mononen memon@inside.org Copyright (c) 2009-2010 Mikko Mononen memon@inside.org
recast4j copyright (c) 2015-2019 Piotr Piastucki piotr@jtilia.org recast4j copyright (c) 2015-2019 Piotr Piastucki piotr@jtilia.org
DotRecast Copyright (c) 2023-2024 Choi Ikpil ikpil@naver.com DotRecast Copyright (c) 2023 Choi Ikpil ikpil@naver.com
This software is provided 'as-is', without any express or implied This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages warranty. In no event will the authors be held liable for any damages
@ -24,12 +24,7 @@ namespace DotRecast.Detour
{ {
public class DtNodeQueue public class DtNodeQueue
{ {
private readonly RcSortedQueue<DtNode> m_heap; private readonly RcSortedQueue<DtNode> m_heap = new RcSortedQueue<DtNode>((n1, n2) => n1.total.CompareTo(n2.total));
public DtNodeQueue()
{
m_heap = new RcSortedQueue<DtNode>(DtNode.ComparisonNodeTotal);
}
public int Count() public int Count()
{ {
@ -48,7 +43,9 @@ namespace DotRecast.Detour
public DtNode Pop() public DtNode Pop()
{ {
return m_heap.Dequeue(); var node = Peek();
m_heap.Remove(node);
return node;
} }
public void Push(DtNode node) public void Push(DtNode node)
@ -64,7 +61,7 @@ namespace DotRecast.Detour
public bool IsEmpty() public bool IsEmpty()
{ {
return m_heap.IsEmpty(); return 0 == m_heap.Count();
} }
} }
} }

View File

@ -1,7 +1,7 @@
/* /*
Copyright (c) 2009-2010 Mikko Mononen memon@inside.org Copyright (c) 2009-2010 Mikko Mononen memon@inside.org
recast4j copyright (c) 2015-2019 Piotr Piastucki piotr@jtilia.org recast4j copyright (c) 2015-2019 Piotr Piastucki piotr@jtilia.org
DotRecast Copyright (c) 2023-2024 Choi Ikpil ikpil@naver.com DotRecast Copyright (c) 2023 Choi Ikpil ikpil@naver.com
This software is provided 'as-is', without any express or implied This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages warranty. In no event will the authors be held liable for any damages
@ -18,18 +18,14 @@ freely, subject to the following restrictions:
3. This notice may not be removed or altered from any source distribution. 3. This notice may not be removed or altered from any source distribution.
*/ */
using System;
using DotRecast.Core.Numerics;
namespace DotRecast.Detour namespace DotRecast.Detour
{ {
/// Defines an navigation mesh off-mesh connection within a dtMeshTile object. /// Defines an navigation mesh off-mesh connection within a dtMeshTile object.
/// An off-mesh connection is a user defined traversable connection made up to two vertices. /// An off-mesh connection is a user defined traversable connection made up to two vertices.
[Serializable]
public class DtOffMeshConnection public class DtOffMeshConnection
{ {
/// The endpoints of the connection. [(ax, ay, az, bx, by, bz)] /// The endpoints of the connection. [(ax, ay, az, bx, by, bz)]
public RcVec3f[] pos = new RcVec3f[2]; public float[] pos = new float[6];
/// The radius of the endpoints. [Limit: >= 0] /// The radius of the endpoints. [Limit: >= 0]
public float rad; public float rad;

View File

@ -1,7 +1,7 @@
/* /*
Copyright (c) 2009-2010 Mikko Mononen memon@inside.org Copyright (c) 2009-2010 Mikko Mononen memon@inside.org
recast4j copyright (c) 2015-2019 Piotr Piastucki piotr@jtilia.org recast4j copyright (c) 2015-2019 Piotr Piastucki piotr@jtilia.org
DotRecast Copyright (c) 2023-2024 Choi Ikpil ikpil@naver.com DotRecast Copyright (c) 2023 Choi Ikpil ikpil@naver.com
This software is provided 'as-is', without any express or implied This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages warranty. In no event will the authors be held liable for any damages
@ -20,31 +20,26 @@ freely, subject to the following restrictions:
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using DotRecast.Core.Buffers;
using DotRecast.Core.Collections;
using DotRecast.Core.Numerics; using DotRecast.Core.Numerics;
using CollectionExtensions = DotRecast.Core.Collections.CollectionExtensions;
namespace DotRecast.Detour namespace DotRecast.Detour
{ {
using static DtDetour;
public static class DtPathUtils public static class DtPathUtils
{ {
private const int MAX_STEER_POINTS = 3;
public static bool GetSteerTarget(DtNavMeshQuery navQuery, RcVec3f startPos, RcVec3f endPos, public static bool GetSteerTarget(DtNavMeshQuery navQuery, RcVec3f startPos, RcVec3f endPos,
float minTargetDist, float minTargetDist,
List<long> path, int pathSize, List<long> path,
out RcVec3f steerPos, out int steerPosFlag, out long steerPosRef) out RcVec3f steerPos, out int steerPosFlag, out long steerPosRef)
{ {
const int MAX_STEER_POINTS = 3;
steerPos = RcVec3f.Zero; steerPos = RcVec3f.Zero;
steerPosFlag = 0; steerPosFlag = 0;
steerPosRef = 0; steerPosRef = 0;
// Find steer target. // Find steer target.
Span<DtStraightPath> straightPath = stackalloc DtStraightPath[MAX_STEER_POINTS]; var straightPath = new List<DtStraightPath>(MAX_STEER_POINTS);
var result = navQuery.FindStraightPath(startPos, endPos, path, pathSize, straightPath, out var nsteerPath, MAX_STEER_POINTS, 0); var result = navQuery.FindStraightPath(startPos, endPos, path, ref straightPath, MAX_STEER_POINTS, 0);
if (result.Failed()) if (result.Failed())
{ {
return false; return false;
@ -52,7 +47,7 @@ namespace DotRecast.Detour
// Find vertex far enough to steer to. // Find vertex far enough to steer to.
int ns = 0; int ns = 0;
while (ns < nsteerPath) while (ns < straightPath.Count)
{ {
// Stop at Off-Mesh link or when point is further than slop away. // Stop at Off-Mesh link or when point is further than slop away.
if (((straightPath[ns].flags & DtStraightPathFlags.DT_STRAIGHTPATH_OFFMESH_CONNECTION) != 0) if (((straightPath[ns].flags & DtStraightPathFlags.DT_STRAIGHTPATH_OFFMESH_CONNECTION) != 0)
@ -62,7 +57,7 @@ namespace DotRecast.Detour
} }
// Failed to find good point to steer to. // Failed to find good point to steer to.
if (ns >= nsteerPath) if (ns >= straightPath.Count)
return false; return false;
steerPos = straightPath[ns].pos; steerPos = straightPath[ns].pos;
@ -93,32 +88,29 @@ namespace DotRecast.Detour
// +-S-+-T-+ // +-S-+-T-+
// |:::| | <-- the step can end up in here, resulting U-turn path. // |:::| | <-- the step can end up in here, resulting U-turn path.
// +---+---+ // +---+---+
public static int FixupShortcuts(ref List<long> path, int npath, DtNavMeshQuery navQuery) public static List<long> FixupShortcuts(List<long> path, DtNavMeshQuery navQuery)
{ {
if (npath < 3) if (path.Count < 3)
{ {
return npath; return path;
} }
// Get connected polygons // Get connected polygons
const int maxNeis = 16; List<long> neis = new List<long>();
Span<long> neis = stackalloc long[maxNeis];
int nneis = 0;
var status = navQuery.GetAttachedNavMesh().GetTileAndPolyByRef(path[0], out var tile, out var poly); var status = navQuery.GetAttachedNavMesh().GetTileAndPolyByRef(path[0], out var tile, out var poly);
if (status.Failed()) if (status.Failed())
{ {
return npath; return path;
} }
for (int k = poly.firstLink; k != DT_NULL_LINK; k = tile.links[k].next) for (int k = tile.polyLinks[poly.index]; k != DtNavMesh.DT_NULL_LINK; k = tile.links[k].next)
{ {
DtLink link = tile.links[k]; DtLink link = tile.links[k];
if (link.refs != 0) if (link.refs != 0)
{ {
if (nneis < maxNeis) neis.Add(link.refs);
neis[nneis++] = link.refs;
} }
} }
@ -126,9 +118,9 @@ namespace DotRecast.Detour
// in the path, short cut to that polygon directly. // in the path, short cut to that polygon directly.
const int maxLookAhead = 6; const int maxLookAhead = 6;
int cut = 0; int cut = 0;
for (int i = Math.Min(maxLookAhead, npath) - 1; i > 1 && cut == 0; i--) for (int i = Math.Min(maxLookAhead, path.Count) - 1; i > 1 && cut == 0; i--)
{ {
for (int j = 0; j < nneis; j++) for (int j = 0; j < neis.Count; j++)
{ {
if (path[i] == neis[j]) if (path[i] == neis[j])
{ {
@ -142,25 +134,23 @@ namespace DotRecast.Detour
{ {
List<long> shortcut = new List<long>(); List<long> shortcut = new List<long>();
shortcut.Add(path[0]); shortcut.Add(path[0]);
shortcut.AddRange(path.GetRange(cut, npath - cut)); shortcut.AddRange(path.GetRange(cut, path.Count - cut));
return shortcut;
path = shortcut;
return shortcut.Count;
} }
return npath; return path;
} }
public static int MergeCorridorStartMoved(List<long> path, int npath, int maxPath, Span<long> visited, int nvisited) public static List<long> MergeCorridorStartMoved(List<long> path, List<long> visited)
{ {
int furthestPath = -1; int furthestPath = -1;
int furthestVisited = -1; int furthestVisited = -1;
// Find furthest common polygon. // Find furthest common polygon.
for (int i = npath - 1; i >= 0; --i) for (int i = path.Count - 1; i >= 0; --i)
{ {
bool found = false; bool found = false;
for (int j = nvisited - 1; j >= 0; --j) for (int j = visited.Count - 1; j >= 0; --j)
{ {
if (path[i] == visited[j]) if (path[i] == visited[j])
{ {
@ -179,36 +169,33 @@ namespace DotRecast.Detour
// If no intersection found just return current path. // If no intersection found just return current path.
if (furthestPath == -1 || furthestVisited == -1) if (furthestPath == -1 || furthestVisited == -1)
{ {
return npath; return path;
} }
// Concatenate paths. // Concatenate paths.
var endIndex = nvisited - 1;
var length1 = endIndex - furthestVisited;
var length2 = npath - furthestPath;
using var result = RcRentedArray.Rent<long>(length1 + length2);
// Adjust beginning of the buffer to include the visited. // Adjust beginning of the buffer to include the visited.
List<long> result = new List<long>();
// Store visited // Store visited
for (int i = 0; i < length1; ++i) for (int i = visited.Count - 1; i > furthestVisited; --i)
result[i] = visited[endIndex - i]; {
result.Add(visited[i]);
path.CopyTo(furthestPath, result.AsArray(), length1, length2);
path.Clear();
CollectionExtensions.AddRange(path, result.AsSpan());
return result.Length;
} }
public static int MergeCorridorEndMoved(List<long> path, int npath, int maxPath, Span<long> visited, int nvisited) result.AddRange(path.GetRange(furthestPath, path.Count - furthestPath));
return result;
}
public static List<long> MergeCorridorEndMoved(List<long> path, List<long> visited)
{ {
int furthestPath = -1; int furthestPath = -1;
int furthestVisited = -1; int furthestVisited = -1;
// Find furthest common polygon. // Find furthest common polygon.
for (int i = 0; i < npath; ++i) for (int i = 0; i < path.Count; ++i)
{ {
bool found = false; bool found = false;
for (int j = nvisited - 1; j >= 0; --j) for (int j = visited.Count - 1; j >= 0; --j)
{ {
if (path[i] == visited[j]) if (path[i] == visited[j])
{ {
@ -227,31 +214,25 @@ namespace DotRecast.Detour
// If no intersection found just return current path. // If no intersection found just return current path.
if (furthestPath == -1 || furthestVisited == -1) if (furthestPath == -1 || furthestVisited == -1)
{ {
return npath; return path;
} }
// Concatenate paths. // Concatenate paths.
var length1 = furthestPath; List<long> result = path.GetRange(0, furthestPath);
var length2 = nvisited - furthestVisited; result.AddRange(visited.GetRange(furthestVisited, visited.Count - furthestVisited));
using var result = RcRentedArray.Rent<long>(length1 + length2); return result;
path.CopyTo(0, result.AsArray(), 0, length1);
visited.Slice(furthestVisited, nvisited - furthestVisited).CopyTo(result.AsSpan().Slice(length1, length2));
path.Clear();
CollectionExtensions.AddRange(path, result.AsSpan());
return path.Count;
} }
public static int MergeCorridorStartShortcut(List<long> path, int npath, int maxPath, List<long> visited, int nvisited) public static List<long> MergeCorridorStartShortcut(List<long> path, List<long> visited)
{ {
int furthestPath = -1; int furthestPath = -1;
int furthestVisited = -1; int furthestVisited = -1;
// Find furthest common polygon. // Find furthest common polygon.
for (int i = npath - 1; i >= 0; --i) for (int i = path.Count - 1; i >= 0; --i)
{ {
bool found = false; bool found = false;
for (int j = nvisited - 1; j >= 0; --j) for (int j = visited.Count - 1; j >= 0; --j)
{ {
if (path[i] == visited[j]) if (path[i] == visited[j])
{ {
@ -270,21 +251,15 @@ namespace DotRecast.Detour
// If no intersection found just return current path. // If no intersection found just return current path.
if (furthestPath == -1 || furthestVisited <= 0) if (furthestPath == -1 || furthestVisited <= 0)
{ {
return npath; return path;
} }
// Concatenate paths. // Concatenate paths.
// Adjust beginning of the buffer to include the visited. // Adjust beginning of the buffer to include the visited.
var length1 = furthestVisited; List<long> result = visited.GetRange(0, furthestVisited);
var length2 = npath - furthestPath; result.AddRange(path.GetRange(furthestPath, path.Count - furthestPath));
using var result = RcRentedArray.Rent<long>(length1 + length2); return result;
visited.CopyTo(0, result.AsArray(), 0, length1);
path.CopyTo(furthestPath, result.AsArray(), length1, length2);
path.Clear();
CollectionExtensions.AddRange(path, result.AsSpan());
return path.Count;
} }
} }
} }

View File

@ -1,7 +1,7 @@
/* /*
Copyright (c) 2009-2010 Mikko Mononen memon@inside.org Copyright (c) 2009-2010 Mikko Mononen memon@inside.org
recast4j copyright (c) 2015-2019 Piotr Piastucki piotr@jtilia.org recast4j copyright (c) 2015-2019 Piotr Piastucki piotr@jtilia.org
DotRecast Copyright (c) 2023-2024 Choi Ikpil ikpil@naver.com DotRecast Copyright (c) 2023 Choi Ikpil ikpil@naver.com
This software is provided 'as-is', without any express or implied This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages warranty. In no event will the authors be held liable for any damages
@ -20,26 +20,21 @@ freely, subject to the following restrictions:
namespace DotRecast.Detour namespace DotRecast.Detour
{ {
/// Defines a polygon within a dtMeshTile object. /** Defines a polygon within a MeshTile object. */
/// @ingroup detour
public class DtPoly public class DtPoly
{ {
public readonly int index; public readonly int index;
/// Index to first link in linked list. (Or #DT_NULL_LINK if there is no link.) /** The indices of the polygon's vertices. The actual vertices are located in MeshTile::verts. */
public int firstLink;
/// The indices of the polygon's vertices.
/// The actual vertices are located in dtMeshTile::verts.
public readonly int[] verts; public readonly int[] verts;
/// Packed data representing neighbor polygons references and flags for each edge. /** Packed data representing neighbor polygons references and flags for each edge. */
public readonly int[] neis; public readonly int[] neis;
/// The user defined polygon flags. /** The user defined polygon flags. */
public int flags; public int flags;
/// The number of vertices in the polygon. /** The number of vertices in the polygon. */
public int vertCount; public int vertCount;
/// The bit packed area id and polygon type. /// The bit packed area id and polygon type.
@ -53,25 +48,25 @@ namespace DotRecast.Detour
neis = new int[maxVertsPerPoly]; neis = new int[maxVertsPerPoly];
} }
/// Sets the user defined area id. [Limit: < #DT_MAX_AREAS] /** Sets the user defined area id. [Limit: &lt; {@link org.recast4j.detour.NavMesh#DT_MAX_AREAS}] */
public void SetArea(int a) public void SetArea(int a)
{ {
areaAndtype = (areaAndtype & 0xc0) | (a & 0x3f); areaAndtype = (areaAndtype & 0xc0) | (a & 0x3f);
} }
/// Sets the polygon type. (See: #dtPolyTypes.) /** Sets the polygon type. (See: #dtPolyTypes.) */
public void SetPolyType(int t) public void SetPolyType(int t)
{ {
areaAndtype = (areaAndtype & 0x3f) | (t << 6); areaAndtype = (areaAndtype & 0x3f) | (t << 6);
} }
/// Gets the user defined area id. /** Gets the user defined area id. */
public int GetArea() public int GetArea()
{ {
return areaAndtype & 0x3f; return areaAndtype & 0x3f;
} }
/// Gets the polygon type. (See: #dtPolyTypes) /** Gets the polygon type. (See: #dtPolyTypes) */
public int GetPolyType() public int GetPolyType()
{ {
return areaAndtype >> 6; return areaAndtype >> 6;

View File

@ -1,7 +1,7 @@
/* /*
Copyright (c) 2009-2010 Mikko Mononen memon@inside.org Copyright (c) 2009-2010 Mikko Mononen memon@inside.org
recast4j copyright (c) 2015-2019 Piotr Piastucki piotr@jtilia.org recast4j copyright (c) 2015-2019 Piotr Piastucki piotr@jtilia.org
DotRecast Copyright (c) 2023-2024 Choi Ikpil ikpil@naver.com DotRecast Copyright (c) 2023 Choi Ikpil ikpil@naver.com
This software is provided 'as-is', without any express or implied This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages warranty. In no event will the authors be held liable for any damages
@ -20,20 +20,19 @@ freely, subject to the following restrictions:
namespace DotRecast.Detour namespace DotRecast.Detour
{ {
/// Defines the location of detail sub-mesh data within a dtMeshTile. /** Defines the location of detail sub-mesh data within a dtMeshTile. */
public readonly struct DtPolyDetail public class DtPolyDetail
{ {
public readonly int vertBase; //< The offset of the vertices in the dtMeshTile::detailVerts array. /** The offset of the vertices in the MeshTile::detailVerts array. */
public readonly int triBase; //< The offset of the triangles in the dtMeshTile::detailTris array. public int vertBase;
public readonly byte vertCount; //< The number of vertices in the sub-mesh.
public readonly byte triCount; //< The number of triangles in the sub-mesh.
public DtPolyDetail(int vertBase, int triBase, byte vertCount, byte triCount) /** The offset of the triangles in the MeshTile::detailTris array. */
{ public int triBase;
this.vertBase = vertBase;
this.triBase = triBase; /** The number of vertices in the sub-mesh. */
this.vertCount = vertCount; public int vertCount;
this.triCount = triCount;
} /** The number of triangles in the sub-mesh. */
public int triCount;
} }
} }

View File

@ -1,4 +1,4 @@
using DotRecast.Core.Numerics; using DotRecast.Core.Numerics;
namespace DotRecast.Detour namespace DotRecast.Detour
{ {

View File

@ -1,4 +1,4 @@
namespace DotRecast.Detour namespace DotRecast.Detour
{ {
/// Flags representing the type of a navigation mesh polygon. /// Flags representing the type of a navigation mesh polygon.
public static class DtPolyTypes public static class DtPolyTypes

View File

@ -1,7 +1,7 @@
/* /*
Copyright (c) 2009-2010 Mikko Mononen memon@inside.org Copyright (c) 2009-2010 Mikko Mononen memon@inside.org
recast4j copyright (c) 2015-2019 Piotr Piastucki piotr@jtilia.org recast4j copyright (c) 2015-2019 Piotr Piastucki piotr@jtilia.org
DotRecast Copyright (c) 2023-2024 Choi Ikpil ikpil@naver.com DotRecast Copyright (c) 2023 Choi Ikpil ikpil@naver.com
This software is provided 'as-is', without any express or implied This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages warranty. In no event will the authors be held liable for any damages

View File

@ -1,7 +1,7 @@
/* /*
Copyright (c) 2009-2010 Mikko Mononen memon@inside.org Copyright (c) 2009-2010 Mikko Mononen memon@inside.org
recast4j copyright (c) 2015-2019 Piotr Piastucki piotr@jtilia.org recast4j copyright (c) 2015-2019 Piotr Piastucki piotr@jtilia.org
DotRecast Copyright (c) 2023-2024 Choi Ikpil ikpil@naver.com DotRecast Copyright (c) 2023 Choi Ikpil ikpil@naver.com
This software is provided 'as-is', without any express or implied This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages warranty. In no event will the authors be held liable for any damages
@ -23,8 +23,6 @@ using DotRecast.Core.Numerics;
namespace DotRecast.Detour namespace DotRecast.Detour
{ {
using static DtDetour;
/** /**
* <b>The Default Implementation</b> * <b>The Default Implementation</b>
* *
@ -52,7 +50,7 @@ namespace DotRecast.Detour
*/ */
public class DtQueryDefaultFilter : IDtQueryFilter public class DtQueryDefaultFilter : IDtQueryFilter
{ {
private readonly float[] m_areaCost = new float[DT_MAX_AREAS]; //< Cost per area type. (Used by default implementation.) private readonly float[] m_areaCost = new float[DtNavMesh.DT_MAX_AREAS]; //< Cost per area type. (Used by default implementation.)
private int m_includeFlags; //< Flags for polygons that can be visited. (Used by default implementation.) private int m_includeFlags; //< Flags for polygons that can be visited. (Used by default implementation.)
private int m_excludeFlags; //< Flags for polygons that should not be visited. (Used by default implementation.) private int m_excludeFlags; //< Flags for polygons that should not be visited. (Used by default implementation.)
@ -60,7 +58,7 @@ namespace DotRecast.Detour
{ {
m_includeFlags = 0xffff; m_includeFlags = 0xffff;
m_excludeFlags = 0; m_excludeFlags = 0;
for (int i = 0; i < DT_MAX_AREAS; ++i) for (int i = 0; i < DtNavMesh.DT_MAX_AREAS; ++i)
{ {
m_areaCost[i] = 1.0f; m_areaCost[i] = 1.0f;
} }
@ -70,12 +68,12 @@ namespace DotRecast.Detour
{ {
m_includeFlags = includeFlags; m_includeFlags = includeFlags;
m_excludeFlags = excludeFlags; m_excludeFlags = excludeFlags;
for (int i = 0; i < Math.Min(DT_MAX_AREAS, areaCost.Length); ++i) for (int i = 0; i < Math.Min(DtNavMesh.DT_MAX_AREAS, areaCost.Length); ++i)
{ {
m_areaCost[i] = areaCost[i]; m_areaCost[i] = areaCost[i];
} }
for (int i = areaCost.Length; i < DT_MAX_AREAS; ++i) for (int i = areaCost.Length; i < DtNavMesh.DT_MAX_AREAS; ++i)
{ {
m_areaCost[i] = 1.0f; m_areaCost[i] = 1.0f;
} }

View File

@ -1,4 +1,4 @@
using DotRecast.Core.Numerics; using DotRecast.Core.Numerics;
namespace DotRecast.Detour namespace DotRecast.Detour
{ {

View File

@ -1,4 +1,4 @@
using DotRecast.Core.Numerics; using DotRecast.Core.Numerics;
namespace DotRecast.Detour namespace DotRecast.Detour
{ {

View File

@ -1,7 +1,7 @@
/* /*
Copyright (c) 2009-2010 Mikko Mononen memon@inside.org Copyright (c) 2009-2010 Mikko Mononen memon@inside.org
recast4j copyright (c) 2015-2019 Piotr Piastucki piotr@jtilia.org recast4j copyright (c) 2015-2019 Piotr Piastucki piotr@jtilia.org
DotRecast Copyright (c) 2023-2024 Choi Ikpil ikpil@naver.com DotRecast Copyright (c) 2023 Choi Ikpil ikpil@naver.com
This software is provided 'as-is', without any express or implied This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages warranty. In no event will the authors be held liable for any damages
@ -23,31 +23,24 @@ using DotRecast.Core.Numerics;
namespace DotRecast.Detour namespace DotRecast.Detour
{ {
/// Provides information about raycast hit /**
/// filled by dtNavMeshQuery::raycast * Provides information about raycast hit. Filled by NavMeshQuery::raycast
/// @ingroup detour */
public struct DtRaycastHit public class DtRaycastHit
{ {
/// The hit parameter. (FLT_MAX if no wall hit.) /** The hit parameter. (float.MaxValue if no wall hit.) */
public float t; public float t;
/// hitNormal The normal of the nearest wall hit. [(x, y, z)] /** hitNormal The normal of the nearest wall hit. [(x, y, z)] */
public RcVec3f hitNormal; public RcVec3f hitNormal = new RcVec3f();
/// The index of the edge on the final polygon where the wall was hit. /** Visited polygons. */
public int hitEdgeIndex; public readonly List<long> path = new List<long>();
/// Pointer to an array of reference ids of the visited polygons. [opt] /** The cost of the path until hit. */
public long[] path;
public int pathCount;
/// The cost of the path until hit.
public float pathCost; public float pathCost;
public void AddPathNode(long nodeRef) /** The index of the edge on the readonly polygon where the wall was hit. */
{ public int hitEdgeIndex;
path[pathCount++] = nodeRef;
}
} }
} }

View File

@ -1,4 +1,4 @@
namespace DotRecast.Detour namespace DotRecast.Detour
{ {
/// Options for dtNavMeshQuery::raycast /// Options for dtNavMeshQuery::raycast
public static class DtRaycastOptions public static class DtRaycastOptions

View File

@ -1,4 +1,4 @@
namespace DotRecast.Detour namespace DotRecast.Detour
{ {
public readonly struct DtSegInterval public readonly struct DtSegInterval
{ {

View File

@ -1,7 +1,7 @@
/* /*
Copyright (c) 2009-2010 Mikko Mononen memon@inside.org Copyright (c) 2009-2010 Mikko Mononen memon@inside.org
recast4j copyright (c) 2015-2019 Piotr Piastucki piotr@jtilia.org recast4j copyright (c) 2015-2019 Piotr Piastucki piotr@jtilia.org
DotRecast Copyright (c) 2023-2024 Choi Ikpil ikpil@naver.com DotRecast Copyright (c) 2023 Choi Ikpil ikpil@naver.com
This software is provided 'as-is', without any express or implied This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages warranty. In no event will the authors be held liable for any damages
@ -26,7 +26,7 @@ namespace DotRecast.Detour
{ {
// High level status. // High level status.
public static readonly DtStatus DT_FAILURE = new DtStatus(1u << 31); // Operation failed. public static readonly DtStatus DT_FAILURE = new DtStatus(1u << 31); // Operation failed.
public static readonly DtStatus DT_SUCCESS = new DtStatus(1u << 30); // Operation succeed. public static readonly DtStatus DT_SUCCSESS = new DtStatus(1u << 30); // Operation succeed.
public static readonly DtStatus DT_IN_PROGRESS = new DtStatus(1u << 29); // Operation still in progress. public static readonly DtStatus DT_IN_PROGRESS = new DtStatus(1u << 29); // Operation still in progress.
// Detail information for status. // Detail information for status.
@ -57,7 +57,7 @@ namespace DotRecast.Detour
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool Succeeded() public bool Succeeded()
{ {
return 0 != (Value & (DT_SUCCESS.Value | DT_PARTIAL_RESULT.Value)); return 0 != (Value & (DT_SUCCSESS.Value | DT_PARTIAL_RESULT.Value));
} }
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]

View File

@ -1,7 +1,7 @@
/* /*
Copyright (c) 2009-2010 Mikko Mononen memon@inside.org Copyright (c) 2009-2010 Mikko Mononen memon@inside.org
recast4j copyright (c) 2015-2019 Piotr Piastucki piotr@jtilia.org recast4j copyright (c) 2015-2019 Piotr Piastucki piotr@jtilia.org
DotRecast Copyright (c) 2023-2024 Choi Ikpil ikpil@naver.com DotRecast Copyright (c) 2023 Choi Ikpil ikpil@naver.com
This software is provided 'as-is', without any express or implied This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages warranty. In no event will the authors be held liable for any damages
@ -25,16 +25,11 @@ namespace DotRecast.Detour
//TODO: (PP) Add comments //TODO: (PP) Add comments
public readonly struct DtStraightPath public readonly struct DtStraightPath
{ {
/// The local path corridor corners for the agent. (Staight path.) [(x, y, z) * #ncorners]
public readonly RcVec3f pos; public readonly RcVec3f pos;
public readonly int flags;
/// The local path corridor corner flags. (See: #dtStraightPathFlags) [(flags) * #ncorners]
public readonly byte flags;
/// The reference id of the polygon being entered at the corner. [(polyRef) * #ncorners]
public readonly long refs; public readonly long refs;
public DtStraightPath(RcVec3f pos, byte flags, long refs) public DtStraightPath(RcVec3f pos, int flags, long refs)
{ {
this.pos = pos; this.pos = pos;
this.flags = flags; this.flags = flags;

View File

@ -1,10 +1,10 @@
namespace DotRecast.Detour namespace DotRecast.Detour
{ {
/// Vertex flags returned by dtNavMeshQuery::findStraightPath. /// Vertex flags returned by dtNavMeshQuery::findStraightPath.
public static class DtStraightPathFlags public static class DtStraightPathFlags
{ {
public const byte DT_STRAIGHTPATH_START = 0x01; //< The vertex is the start position in the path. public const int DT_STRAIGHTPATH_START = 0x01; //< The vertex is the start position in the path.
public const byte DT_STRAIGHTPATH_END = 0x02; //< The vertex is the end position in the path. public const int DT_STRAIGHTPATH_END = 0x02; //< The vertex is the end position in the path.
public const byte DT_STRAIGHTPATH_OFFMESH_CONNECTION = 0x04; //< The vertex is the start of an off-mesh connection. public const int DT_STRAIGHTPATH_OFFMESH_CONNECTION = 0x04; //< The vertex is the start of an off-mesh connection.
} }
} }

View File

@ -1,4 +1,4 @@
using DotRecast.Core; using DotRecast.Core;
using DotRecast.Core.Collections; using DotRecast.Core.Collections;
namespace DotRecast.Detour namespace DotRecast.Detour

View File

@ -1,4 +1,4 @@
namespace DotRecast.Detour namespace DotRecast.Detour
{ {
/// Options for dtNavMeshQuery::findStraightPath. /// Options for dtNavMeshQuery::findStraightPath.
public static class DtStraightPathOptions public static class DtStraightPathOptions

View File

@ -1,13 +1,15 @@
using System; using System;
using DotRecast.Core.Numerics; using DotRecast.Core.Numerics;
namespace DotRecast.Detour namespace DotRecast.Detour
{ {
// Calculate the intersection between a polygon and a circle. A dodecagon is used as an approximation of the circle. /**
* Calculate the intersection between a polygon and a circle. A dodecagon is used as an approximation of the circle.
*/
public class DtStrictDtPolygonByCircleConstraint : IDtPolygonByCircleConstraint public class DtStrictDtPolygonByCircleConstraint : IDtPolygonByCircleConstraint
{ {
private const int CIRCLE_SEGMENTS = 12; private const int CIRCLE_SEGMENTS = 12;
private static readonly float[] UnitCircle = CreateCircle(); private static readonly float[] UnitCircle = MakeUnitCircle();
public static readonly IDtPolygonByCircleConstraint Shared = new DtStrictDtPolygonByCircleConstraint(); public static readonly IDtPolygonByCircleConstraint Shared = new DtStrictDtPolygonByCircleConstraint();
@ -15,7 +17,7 @@ namespace DotRecast.Detour
{ {
} }
public static float[] CreateCircle() private static float[] MakeUnitCircle()
{ {
var temp = new float[CIRCLE_SEGMENTS * 3]; var temp = new float[CIRCLE_SEGMENTS * 3];
for (int i = 0; i < CIRCLE_SEGMENTS; i++) for (int i = 0; i < CIRCLE_SEGMENTS; i++)
@ -29,23 +31,13 @@ namespace DotRecast.Detour
return temp; return temp;
} }
public static void ScaleCircle(Span<float> src, RcVec3f center, float radius, Span<float> dst) public float[] Apply(float[] verts, RcVec3f center, float radius)
{
for (int i = 0; i < CIRCLE_SEGMENTS; i++)
{
dst[3 * i] = src[3 * i] * radius + center.X;
dst[3 * i + 1] = center.Y;
dst[3 * i + 2] = src[3 * i + 2] * radius + center.Z;
}
}
public Span<float> Apply(Span<float> verts, RcVec3f center, float radius, Span<float> resultBuffer)
{ {
float radiusSqr = radius * radius; float radiusSqr = radius * radius;
int outsideVertex = -1; int outsideVertex = -1;
for (int pv = 0; pv < verts.Length; pv += 3) for (int pv = 0; pv < verts.Length; pv += 3)
{ {
if (RcVec.Dist2DSqr(center, verts, pv) > radiusSqr) if (RcVecUtils.Dist2DSqr(center, verts, pv) > radiusSqr)
{ {
outsideVertex = pv; outsideVertex = pv;
break; break;
@ -55,30 +47,32 @@ namespace DotRecast.Detour
if (outsideVertex == -1) if (outsideVertex == -1)
{ {
// polygon inside circle // polygon inside circle
var result = resultBuffer.Slice(0, verts.Length); return verts;
verts.CopyTo(result);
return result;
} }
Span<float> qCircle = stackalloc float[UnitCircle.Length]; float[] qCircle = Circle(center, radius);
ScaleCircle(UnitCircle, center, radius, qCircle); float[] intersection = DtConvexConvexIntersections.Intersect(verts, qCircle);
Span<float> intersection = DtConvexConvexIntersections.Intersect(verts, qCircle, resultBuffer); if (intersection == null && DtUtils.PointInPolygon(center, verts, verts.Length / 3))
if (intersection.IsEmpty && DtUtils.PointInPolygon(center, verts, verts.Length / 3))
{ {
// circle inside polygon // circle inside polygon
var result = resultBuffer.Slice(0, qCircle.Length); return qCircle;
qCircle.CopyTo(result);
return result;
} }
if(!intersection.IsEmpty) return intersection;
}
private float[] Circle(RcVec3f center, float radius)
{ {
var result = resultBuffer.Slice(0, intersection.Length); float[] circle = new float[12 * 3];
// No need to copy, data is already in buffer for (int i = 0; i < CIRCLE_SEGMENTS * 3; i += 3)
return result; {
circle[i] = UnitCircle[i] * radius + center.X;
circle[i + 1] = center.Y;
circle[i + 2] = UnitCircle[i + 2] * radius + center.Z;
} }
return Span<float>.Empty; return circle;
} }
} }
} }

View File

@ -1,10 +1,13 @@
using System; using System;
using DotRecast.Core;
using DotRecast.Core.Numerics; using DotRecast.Core.Numerics;
namespace DotRecast.Detour namespace DotRecast.Detour
{ {
public static class DtUtils public static class DtUtils
{ {
private static readonly float EQUAL_THRESHOLD = RcMath.Sqr(1.0f / 16384.0f);
public static int NextPow2(int v) public static int NextPow2(int v)
{ {
v--; v--;
@ -36,6 +39,24 @@ namespace DotRecast.Detour
return r; return r;
} }
/// Performs a 'sloppy' colocation check of the specified points.
/// @param[in] p0 A point. [(x, y, z)]
/// @param[in] p1 A point. [(x, y, z)]
/// @return True if the points are considered to be at the same location.
///
/// Basically, this function will return true if the specified points are
/// close enough to eachother to be considered colocated.
public static bool VEqual(RcVec3f p0, RcVec3f p1)
{
return VEqual(p0, p1, EQUAL_THRESHOLD);
}
public static bool VEqual(RcVec3f p0, RcVec3f p1, float thresholdSqr)
{
float d = RcVec3f.DistanceSquared(p0, p1);
return d < thresholdSqr;
}
/// Determines if two axis-aligned bounding boxes overlap. /// Determines if two axis-aligned bounding boxes overlap.
/// @param[in] amin Minimum bounds of box A. [(x, y, z)] /// @param[in] amin Minimum bounds of box A. [(x, y, z)]
/// @param[in] amax Maximum bounds of box A. [(x, y, z)] /// @param[in] amax Maximum bounds of box A. [(x, y, z)]
@ -43,12 +64,12 @@ namespace DotRecast.Detour
/// @param[in] bmax Maximum bounds of box B. [(x, y, z)] /// @param[in] bmax Maximum bounds of box B. [(x, y, z)]
/// @return True if the two AABB's overlap. /// @return True if the two AABB's overlap.
/// @see dtOverlapBounds /// @see dtOverlapBounds
public static bool OverlapQuantBounds(ref RcVec3i amin, ref RcVec3i amax, ref RcVec3i bmin, ref RcVec3i bmax) public static bool OverlapQuantBounds(int[] amin, int[] amax, int[] bmin, int[] bmax)
{ {
bool overlap = true; bool overlap = true;
overlap = (amin.X > bmax.X || amax.X < bmin.X) ? false : overlap; overlap = (amin[0] > bmax[0] || amax[0] < bmin[0]) ? false : overlap;
overlap = (amin.Y > bmax.Y || amax.Y < bmin.Y) ? false : overlap; overlap = (amin[1] > bmax[1] || amax[1] < bmin[1]) ? false : overlap;
overlap = (amin.Z > bmax.Z || amax.Z < bmin.Z) ? false : overlap; overlap = (amin[2] > bmax[2] || amax[2] < bmin[2]) ? false : overlap;
return overlap; return overlap;
} }
@ -76,7 +97,7 @@ namespace DotRecast.Detour
/// @par /// @par
/// ///
/// All vertices are projected onto the xz-plane, so the y-values are ignored. /// All vertices are projected onto the xz-plane, so the y-values are ignored.
public static bool OverlapPolyPoly2D(Span<float> polya, int npolya, Span<float> polyb, int npolyb) public static bool OverlapPolyPoly2D(float[] polya, int npolya, float[] polyb, int npolyb)
{ {
const float eps = 1e-4f; const float eps = 1e-4f;
for (int i = 0, j = npolya - 1; i < npolya; j = i++) for (int i = 0, j = npolya - 1; i < npolya; j = i++)
@ -124,7 +145,7 @@ namespace DotRecast.Detour
/// @param[in] b Vertex B. [(x, y, z)] /// @param[in] b Vertex B. [(x, y, z)]
/// @param[in] c Vertex C. [(x, y, z)] /// @param[in] c Vertex C. [(x, y, z)]
/// @return The signed xz-plane area of the triangle. /// @return The signed xz-plane area of the triangle.
public static float TriArea2D(Span<float> verts, int a, int b, int c) public static float TriArea2D(float[] verts, int a, int b, int c)
{ {
float abx = verts[b] - verts[a]; float abx = verts[b] - verts[a];
float abz = verts[b + 2] - verts[a + 2]; float abz = verts[b + 2] - verts[a + 2];
@ -144,7 +165,7 @@ namespace DotRecast.Detour
// Returns a random point in a convex polygon. // Returns a random point in a convex polygon.
// Adapted from Graphics Gems article. // Adapted from Graphics Gems article.
public static void RandomPointInConvexPoly(Span<float> pts, int npts, Span<float> areas, float s, float t, out RcVec3f @out) public static RcVec3f RandomPointInConvexPoly(float[] pts, int npts, float[] areas, float s, float t)
{ {
// Calc triangle araes // Calc triangle araes
float areasum = 0.0f; float areasum = 0.0f;
@ -181,7 +202,7 @@ namespace DotRecast.Detour
int pb = (tri - 1) * 3; int pb = (tri - 1) * 3;
int pc = tri * 3; int pc = tri * 3;
@out = new RcVec3f() return new RcVec3f()
{ {
X = a * pts[pa] + b * pts[pb] + c * pts[pc], X = a * pts[pa] + b * pts[pb] + c * pts[pc],
Y = a * pts[pa + 1] + b * pts[pb + 1] + c * pts[pc + 1], Y = a * pts[pa + 1] + b * pts[pb + 1] + c * pts[pc + 1],
@ -225,13 +246,13 @@ namespace DotRecast.Detour
return false; return false;
} }
public static RcVec2f ProjectPoly(RcVec3f axis, Span<float> poly, int npoly) public static RcVec2f ProjectPoly(RcVec3f axis, float[] poly, int npoly)
{ {
float rmin, rmax; float rmin, rmax;
rmin = rmax = axis.Dot2D(new RcVec3f(poly)); rmin = rmax = axis.Dot2D(poly, 0);
for (int i = 1; i < npoly; ++i) for (int i = 1; i < npoly; ++i)
{ {
float d = axis.Dot2D(RcVec.Create(poly, i * 3)); float d = axis.Dot2D(poly, i * 3);
rmin = Math.Min(rmin, d); rmin = Math.Min(rmin, d);
rmax = Math.Max(rmax, d); rmax = Math.Max(rmax, d);
} }
@ -246,7 +267,7 @@ namespace DotRecast.Detour
/// @par /// @par
/// ///
/// All points are projected onto the xz-plane, so the y-values are ignored. /// All points are projected onto the xz-plane, so the y-values are ignored.
public static bool PointInPolygon(RcVec3f pt, Span<float> verts, int nverts) public static bool PointInPolygon(RcVec3f pt, float[] verts, int nverts)
{ {
// TODO: Replace pnpoly with triArea2D tests? // TODO: Replace pnpoly with triArea2D tests?
int i, j; int i, j;
@ -265,7 +286,7 @@ namespace DotRecast.Detour
return c; return c;
} }
public static bool DistancePtPolyEdgesSqr(RcVec3f pt, Span<float> verts, int nverts, Span<float> ed, Span<float> et) public static bool DistancePtPolyEdgesSqr(RcVec3f pt, float[] verts, int nverts, float[] ed, float[] et)
{ {
// TODO: Replace pnpoly with triArea2D tests? // TODO: Replace pnpoly with triArea2D tests?
int i, j; int i, j;
@ -286,10 +307,10 @@ namespace DotRecast.Detour
return c; return c;
} }
public static float DistancePtSegSqr2D(RcVec3f pt, Span<float> verts, int p, int q, out float t) public static float DistancePtSegSqr2D(RcVec3f pt, float[] verts, int p, int q, out float t)
{ {
var vp = RcVec.Create(verts, p); var vp = RcVecUtils.Create(verts, p);
var vq = RcVec.Create(verts, q); var vq = RcVecUtils.Create(verts, q);
return DistancePtSegSqr2D(pt, vp, vq, out t); return DistancePtSegSqr2D(pt, vp, vq, out t);
} }
@ -321,7 +342,7 @@ namespace DotRecast.Detour
} }
public static bool IntersectSegmentPoly2D(RcVec3f p0, RcVec3f p1, public static bool IntersectSegmentPoly2D(RcVec3f p0, RcVec3f p1,
Span<RcVec3f> verts, int nverts, RcVec3f[] verts, int nverts,
out float tmin, out float tmax, out float tmin, out float tmax,
out int segMin, out int segMax) out int segMin, out int segMax)
{ {
@ -341,8 +362,8 @@ namespace DotRecast.Detour
RcVec3f vpi = verts[i]; RcVec3f vpi = verts[i];
var edge = RcVec3f.Subtract(vpi, vpj); var edge = RcVec3f.Subtract(vpi, vpj);
var diff = RcVec3f.Subtract(p0v, vpj); var diff = RcVec3f.Subtract(p0v, vpj);
float n = RcVec.Perp2D(edge, diff); float n = RcVecUtils.Perp2D(edge, diff);
float d = RcVec.Perp2D(dir, edge); float d = RcVecUtils.Perp2D(dir, edge);
if (MathF.Abs(d) < EPS) if (MathF.Abs(d) < EPS)
{ {
// S is nearly parallel to this edge // S is nearly parallel to this edge
@ -404,14 +425,14 @@ namespace DotRecast.Detour
RcVec3f u = RcVec3f.Subtract(aq, ap); RcVec3f u = RcVec3f.Subtract(aq, ap);
RcVec3f v = RcVec3f.Subtract(bq, bp); RcVec3f v = RcVec3f.Subtract(bq, bp);
RcVec3f w = RcVec3f.Subtract(ap, bp); RcVec3f w = RcVec3f.Subtract(ap, bp);
float d = RcVec.PerpXZ(u, v); float d = RcVecUtils.PerpXZ(u, v);
if (MathF.Abs(d) < 1e-6f) if (MathF.Abs(d) < 1e-6f)
{ {
return false; return false;
} }
s = RcVec.PerpXZ(v, w) / d; s = RcVecUtils.PerpXZ(v, w) / d;
t = RcVec.PerpXZ(u, w) / d; t = RcVecUtils.PerpXZ(u, w) / d;
return true; return true;
} }

View File

@ -1,14 +1,7 @@
using System;
namespace DotRecast.Detour namespace DotRecast.Detour
{ {
/// Provides custom polygon query behavior.
/// Used by dtNavMeshQuery::queryPolygons.
/// @ingroup detour
public interface IDtPolyQuery public interface IDtPolyQuery
{ {
/// Called for each batch of unique polygons touched by the search area in dtNavMeshQuery::queryPolygons. void Process(DtMeshTile tile, DtPoly poly, long refs);
/// This can be called multiple times for a single query.
void Process(DtMeshTile tile, Span<DtPoly> poly, Span<long> refs, int count);
} }
} }

View File

@ -1,6 +1,6 @@
/* /*
recast4j copyright (c) 2021 Piotr Piastucki piotr@jtilia.org recast4j copyright (c) 2021 Piotr Piastucki piotr@jtilia.org
DotRecast Copyright (c) 2023-2024 Choi Ikpil ikpil@naver.com DotRecast Copyright (c) 2023 Choi Ikpil ikpil@naver.com
This software is provided 'as-is', without any express or implied This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages warranty. In no event will the authors be held liable for any damages
@ -24,6 +24,6 @@ namespace DotRecast.Detour
{ {
public interface IDtPolygonByCircleConstraint public interface IDtPolygonByCircleConstraint
{ {
Span<float> Apply(Span<float> polyVerts, RcVec3f circleCenter, float radius, Span<float> resultBuffer); float[] Apply(float[] polyVerts, RcVec3f circleCenter, float radius);
} }
} }

View File

@ -1,7 +1,7 @@
/* /*
Copyright (c) 2009-2010 Mikko Mononen memon@inside.org Copyright (c) 2009-2010 Mikko Mononen memon@inside.org
recast4j copyright (c) 2015-2019 Piotr Piastucki piotr@jtilia.org recast4j copyright (c) 2015-2019 Piotr Piastucki piotr@jtilia.org
DotRecast Copyright (c) 2023-2024 Choi Ikpil ikpil@naver.com DotRecast Copyright (c) 2023 Choi Ikpil ikpil@naver.com
This software is provided 'as-is', without any express or implied This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages warranty. In no event will the authors be held liable for any damages

View File

@ -1,6 +1,6 @@
/* /*
recast4j copyright (c) 2021 Piotr Piastucki piotr@jtilia.org recast4j copyright (c) 2021 Piotr Piastucki piotr@jtilia.org
DotRecast Copyright (c) 2023-2024 Choi Ikpil ikpil@naver.com DotRecast Copyright (c) 2023 Choi Ikpil ikpil@naver.com
This software is provided 'as-is', without any express or implied This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages warranty. In no event will the authors be held liable for any damages

View File

@ -21,17 +21,13 @@ using DotRecast.Core;
namespace DotRecast.Detour.Io namespace DotRecast.Detour.Io
{ {
using static DtDetour;
public class DtMeshDataReader public class DtMeshDataReader
{ {
public const int DT_POLY_DETAIL_SIZE = 10; public const int DT_POLY_DETAIL_SIZE = 10;
public const int LINK_SIZEOF = 16;
public const int LINK_SIZEOF32BIT = 12;
public DtMeshData Read(BinaryReader stream, int maxVertPerPoly) public DtMeshData Read(BinaryReader stream, int maxVertPerPoly)
{ {
RcByteBuffer buf = RcIO.ToByteBuffer(stream); RcByteBuffer buf = IOUtils.ToByteBuffer(stream);
return Read(buf, maxVertPerPoly, false); return Read(buf, maxVertPerPoly, false);
} }
@ -42,7 +38,7 @@ namespace DotRecast.Detour.Io
public DtMeshData Read32Bit(BinaryReader stream, int maxVertPerPoly) public DtMeshData Read32Bit(BinaryReader stream, int maxVertPerPoly)
{ {
RcByteBuffer buf = RcIO.ToByteBuffer(stream); RcByteBuffer buf = IOUtils.ToByteBuffer(stream);
return Read(buf, maxVertPerPoly, true); return Read(buf, maxVertPerPoly, true);
} }
@ -57,10 +53,10 @@ namespace DotRecast.Detour.Io
DtMeshHeader header = new DtMeshHeader(); DtMeshHeader header = new DtMeshHeader();
data.header = header; data.header = header;
header.magic = buf.GetInt(); header.magic = buf.GetInt();
if (header.magic != DT_NAVMESH_MAGIC) if (header.magic != DtMeshHeader.DT_NAVMESH_MAGIC)
{ {
header.magic = RcIO.SwapEndianness(header.magic); header.magic = IOUtils.SwapEndianness(header.magic);
if (header.magic != DT_NAVMESH_MAGIC) if (header.magic != DtMeshHeader.DT_NAVMESH_MAGIC)
{ {
throw new IOException("Invalid magic"); throw new IOException("Invalid magic");
} }
@ -69,16 +65,16 @@ namespace DotRecast.Detour.Io
} }
header.version = buf.GetInt(); header.version = buf.GetInt();
if (header.version != DT_NAVMESH_VERSION) if (header.version != DtMeshHeader.DT_NAVMESH_VERSION)
{ {
if (header.version < DT_NAVMESH_VERSION_RECAST4J_FIRST if (header.version < DtMeshHeader.DT_NAVMESH_VERSION_RECAST4J_FIRST
|| header.version > DT_NAVMESH_VERSION_RECAST4J_LAST) || header.version > DtMeshHeader.DT_NAVMESH_VERSION_RECAST4J_LAST)
{ {
throw new IOException("Invalid version " + header.version); throw new IOException("Invalid version " + header.version);
} }
} }
bool cCompatibility = header.version == DT_NAVMESH_VERSION; bool cCompatibility = header.version == DtMeshHeader.DT_NAVMESH_VERSION;
header.x = buf.GetInt(); header.x = buf.GetInt();
header.y = buf.GetInt(); header.y = buf.GetInt();
header.layer = buf.GetInt(); header.layer = buf.GetInt();
@ -120,6 +116,8 @@ namespace DotRecast.Detour.Io
return data; return data;
} }
public const int LINK_SIZEOF = 16;
public const int LINK_SIZEOF32BIT = 12;
public static int GetSizeofLink(bool is32Bit) public static int GetSizeofLink(bool is32Bit)
{ {
@ -143,7 +141,7 @@ namespace DotRecast.Detour.Io
for (int i = 0; i < polys.Length; i++) for (int i = 0; i < polys.Length; i++)
{ {
polys[i] = new DtPoly(i, maxVertPerPoly); polys[i] = new DtPoly(i, maxVertPerPoly);
if (header.version < DT_NAVMESH_VERSION_RECAST4J_NO_POLY_FIRSTLINK) if (header.version < DtMeshHeader.DT_NAVMESH_VERSION_RECAST4J_NO_POLY_FIRSTLINK)
{ {
buf.GetInt(); // polys[i].firstLink buf.GetInt(); // polys[i].firstLink
} }
@ -171,11 +169,11 @@ namespace DotRecast.Detour.Io
DtPolyDetail[] polys = new DtPolyDetail[header.detailMeshCount]; DtPolyDetail[] polys = new DtPolyDetail[header.detailMeshCount];
for (int i = 0; i < polys.Length; i++) for (int i = 0; i < polys.Length; i++)
{ {
int vertBase = buf.GetInt(); polys[i] = new DtPolyDetail();
int triBase = buf.GetInt(); polys[i].vertBase = buf.GetInt();
byte vertCount = (byte)(buf.Get() & 0xFF); polys[i].triBase = buf.GetInt();
byte triCount = (byte)(buf.Get() & 0xFF); polys[i].vertCount = buf.Get() & 0xFF;
polys[i] = new DtPolyDetail(vertBase, triBase, vertCount, triCount); polys[i].triCount = buf.Get() & 0xFF;
if (cCompatibility) if (cCompatibility)
{ {
buf.GetShort(); // C struct padding buf.GetShort(); // C struct padding
@ -202,25 +200,29 @@ namespace DotRecast.Detour.Io
for (int i = 0; i < nodes.Length; i++) for (int i = 0; i < nodes.Length; i++)
{ {
nodes[i] = new DtBVNode(); nodes[i] = new DtBVNode();
if (header.version < DT_NAVMESH_VERSION_RECAST4J_32BIT_BVTREE) if (header.version < DtMeshHeader.DT_NAVMESH_VERSION_RECAST4J_32BIT_BVTREE)
{ {
nodes[i].bmin.X = buf.GetShort() & 0xFFFF; for (int j = 0; j < 3; j++)
nodes[i].bmin.Y = buf.GetShort() & 0xFFFF; {
nodes[i].bmin.Z = buf.GetShort() & 0xFFFF; nodes[i].bmin[j] = buf.GetShort() & 0xFFFF;
}
nodes[i].bmax.X = buf.GetShort() & 0xFFFF; for (int j = 0; j < 3; j++)
nodes[i].bmax.Y = buf.GetShort() & 0xFFFF; {
nodes[i].bmax.Z = buf.GetShort() & 0xFFFF; nodes[i].bmax[j] = buf.GetShort() & 0xFFFF;
}
} }
else else
{ {
nodes[i].bmin.X = buf.GetInt(); for (int j = 0; j < 3; j++)
nodes[i].bmin.Y = buf.GetInt(); {
nodes[i].bmin.Z = buf.GetInt(); nodes[i].bmin[j] = buf.GetInt();
}
nodes[i].bmax.X = buf.GetInt(); for (int j = 0; j < 3; j++)
nodes[i].bmax.Y = buf.GetInt(); {
nodes[i].bmax.Z = buf.GetInt(); nodes[i].bmax[j] = buf.GetInt();
}
} }
nodes[i].i = buf.GetInt(); nodes[i].i = buf.GetInt();
@ -235,11 +237,9 @@ namespace DotRecast.Detour.Io
for (int i = 0; i < cons.Length; i++) for (int i = 0; i < cons.Length; i++)
{ {
cons[i] = new DtOffMeshConnection(); cons[i] = new DtOffMeshConnection();
for (int j = 0; j < 2; j++) for (int j = 0; j < 6; j++)
{ {
cons[i].pos[j].X = buf.GetFloat(); cons[i].pos[j] = buf.GetFloat();
cons[i].pos[j].Y = buf.GetFloat();
cons[i].pos[j].Z = buf.GetFloat();
} }
cons[i].rad = buf.GetFloat(); cons[i].rad = buf.GetFloat();

View File

@ -21,38 +21,36 @@ using DotRecast.Core;
namespace DotRecast.Detour.Io namespace DotRecast.Detour.Io
{ {
using static DtDetour; public class DtMeshDataWriter : DtWriter
public class DtMeshDataWriter
{ {
public void Write(BinaryWriter stream, DtMeshData data, RcByteOrder order, bool cCompatibility) public void Write(BinaryWriter stream, DtMeshData data, RcByteOrder order, bool cCompatibility)
{ {
DtMeshHeader header = data.header; DtMeshHeader header = data.header;
RcIO.Write(stream, header.magic, order); Write(stream, header.magic, order);
RcIO.Write(stream, cCompatibility ? DT_NAVMESH_VERSION : DT_NAVMESH_VERSION_RECAST4J_LAST, order); Write(stream, cCompatibility ? DtMeshHeader.DT_NAVMESH_VERSION : DtMeshHeader.DT_NAVMESH_VERSION_RECAST4J_LAST, order);
RcIO.Write(stream, header.x, order); Write(stream, header.x, order);
RcIO.Write(stream, header.y, order); Write(stream, header.y, order);
RcIO.Write(stream, header.layer, order); Write(stream, header.layer, order);
RcIO.Write(stream, header.userId, order); Write(stream, header.userId, order);
RcIO.Write(stream, header.polyCount, order); Write(stream, header.polyCount, order);
RcIO.Write(stream, header.vertCount, order); Write(stream, header.vertCount, order);
RcIO.Write(stream, header.maxLinkCount, order); Write(stream, header.maxLinkCount, order);
RcIO.Write(stream, header.detailMeshCount, order); Write(stream, header.detailMeshCount, order);
RcIO.Write(stream, header.detailVertCount, order); Write(stream, header.detailVertCount, order);
RcIO.Write(stream, header.detailTriCount, order); Write(stream, header.detailTriCount, order);
RcIO.Write(stream, header.bvNodeCount, order); Write(stream, header.bvNodeCount, order);
RcIO.Write(stream, header.offMeshConCount, order); Write(stream, header.offMeshConCount, order);
RcIO.Write(stream, header.offMeshBase, order); Write(stream, header.offMeshBase, order);
RcIO.Write(stream, header.walkableHeight, order); Write(stream, header.walkableHeight, order);
RcIO.Write(stream, header.walkableRadius, order); Write(stream, header.walkableRadius, order);
RcIO.Write(stream, header.walkableClimb, order); Write(stream, header.walkableClimb, order);
RcIO.Write(stream, header.bmin.X, order); Write(stream, header.bmin.X, order);
RcIO.Write(stream, header.bmin.Y, order); Write(stream, header.bmin.Y, order);
RcIO.Write(stream, header.bmin.Z, order); Write(stream, header.bmin.Z, order);
RcIO.Write(stream, header.bmax.X, order); Write(stream, header.bmax.X, order);
RcIO.Write(stream, header.bmax.Y, order); Write(stream, header.bmax.Y, order);
RcIO.Write(stream, header.bmax.Z, order); Write(stream, header.bmax.Z, order);
RcIO.Write(stream, header.bvQuantFactor, order); Write(stream, header.bvQuantFactor, order);
WriteVerts(stream, data.verts, header.vertCount, order); WriteVerts(stream, data.verts, header.vertCount, order);
WritePolys(stream, data, order, cCompatibility); WritePolys(stream, data, order, cCompatibility);
if (cCompatibility) if (cCompatibility)
@ -72,7 +70,7 @@ namespace DotRecast.Detour.Io
{ {
for (int i = 0; i < count * 3; i++) for (int i = 0; i < count * 3; i++)
{ {
RcIO.Write(stream, verts[i], order); Write(stream, verts[i], order);
} }
} }
@ -82,22 +80,22 @@ namespace DotRecast.Detour.Io
{ {
if (cCompatibility) if (cCompatibility)
{ {
RcIO.Write(stream, 0xFFFF, order); Write(stream, 0xFFFF, order);
} }
for (int j = 0; j < data.polys[i].verts.Length; j++) for (int j = 0; j < data.polys[i].verts.Length; j++)
{ {
RcIO.Write(stream, (short)data.polys[i].verts[j], order); Write(stream, (short)data.polys[i].verts[j], order);
} }
for (int j = 0; j < data.polys[i].neis.Length; j++) for (int j = 0; j < data.polys[i].neis.Length; j++)
{ {
RcIO.Write(stream, (short)data.polys[i].neis[j], order); Write(stream, (short)data.polys[i].neis[j], order);
} }
RcIO.Write(stream, (short)data.polys[i].flags, order); Write(stream, (short)data.polys[i].flags, order);
RcIO.Write(stream, (byte)data.polys[i].vertCount); Write(stream, (byte)data.polys[i].vertCount);
RcIO.Write(stream, (byte)data.polys[i].areaAndtype); Write(stream, (byte)data.polys[i].areaAndtype);
} }
} }
@ -105,13 +103,13 @@ namespace DotRecast.Detour.Io
{ {
for (int i = 0; i < data.header.detailMeshCount; i++) for (int i = 0; i < data.header.detailMeshCount; i++)
{ {
RcIO.Write(stream, data.detailMeshes[i].vertBase, order); Write(stream, data.detailMeshes[i].vertBase, order);
RcIO.Write(stream, data.detailMeshes[i].triBase, order); Write(stream, data.detailMeshes[i].triBase, order);
RcIO.Write(stream, (byte)data.detailMeshes[i].vertCount); Write(stream, (byte)data.detailMeshes[i].vertCount);
RcIO.Write(stream, (byte)data.detailMeshes[i].triCount); Write(stream, (byte)data.detailMeshes[i].triCount);
if (cCompatibility) if (cCompatibility)
{ {
RcIO.Write(stream, (short)0, order); Write(stream, (short)0, order);
} }
} }
} }
@ -120,7 +118,7 @@ namespace DotRecast.Detour.Io
{ {
for (int i = 0; i < data.header.detailTriCount * 4; i++) for (int i = 0; i < data.header.detailTriCount * 4; i++)
{ {
RcIO.Write(stream, (byte)data.detailTris[i]); Write(stream, (byte)data.detailTris[i]);
} }
} }
@ -130,26 +128,30 @@ namespace DotRecast.Detour.Io
{ {
if (cCompatibility) if (cCompatibility)
{ {
RcIO.Write(stream, (short)data.bvTree[i].bmin.X, order); for (int j = 0; j < 3; j++)
RcIO.Write(stream, (short)data.bvTree[i].bmin.Y, order); {
RcIO.Write(stream, (short)data.bvTree[i].bmin.Z, order); Write(stream, (short)data.bvTree[i].bmin[j], order);
}
RcIO.Write(stream, (short)data.bvTree[i].bmax.X, order); for (int j = 0; j < 3; j++)
RcIO.Write(stream, (short)data.bvTree[i].bmax.Y, order); {
RcIO.Write(stream, (short)data.bvTree[i].bmax.Z, order); Write(stream, (short)data.bvTree[i].bmax[j], order);
}
} }
else else
{ {
RcIO.Write(stream, data.bvTree[i].bmin.X, order); for (int j = 0; j < 3; j++)
RcIO.Write(stream, data.bvTree[i].bmin.Y, order); {
RcIO.Write(stream, data.bvTree[i].bmin.Z, order); Write(stream, data.bvTree[i].bmin[j], order);
RcIO.Write(stream, data.bvTree[i].bmax.X, order);
RcIO.Write(stream, data.bvTree[i].bmax.Y, order);
RcIO.Write(stream, data.bvTree[i].bmax.Z, order);
} }
RcIO.Write(stream, data.bvTree[i].i, order); for (int j = 0; j < 3; j++)
{
Write(stream, data.bvTree[i].bmax[j], order);
}
}
Write(stream, data.bvTree[i].i, order);
} }
} }
@ -157,18 +159,16 @@ namespace DotRecast.Detour.Io
{ {
for (int i = 0; i < data.header.offMeshConCount; i++) for (int i = 0; i < data.header.offMeshConCount; i++)
{ {
for (int j = 0; j < 2; j++) for (int j = 0; j < 6; j++)
{ {
RcIO.Write(stream, data.offMeshCons[i].pos[j].X, order); Write(stream, data.offMeshCons[i].pos[j], order);
RcIO.Write(stream, data.offMeshCons[i].pos[j].Y, order);
RcIO.Write(stream, data.offMeshCons[i].pos[j].Z, order);
} }
RcIO.Write(stream, data.offMeshCons[i].rad, order); Write(stream, data.offMeshCons[i].rad, order);
RcIO.Write(stream, (short)data.offMeshCons[i].poly, order); Write(stream, (short)data.offMeshCons[i].poly, order);
RcIO.Write(stream, (byte)data.offMeshCons[i].flags); Write(stream, (byte)data.offMeshCons[i].flags);
RcIO.Write(stream, (byte)data.offMeshCons[i].side); Write(stream, (byte)data.offMeshCons[i].side);
RcIO.Write(stream, data.offMeshCons[i].userId, order); Write(stream, data.offMeshCons[i].userId, order);
} }
} }
} }

View File

@ -22,8 +22,6 @@ using DotRecast.Core;
namespace DotRecast.Detour.Io namespace DotRecast.Detour.Io
{ {
using static DtDetour;
public class DtMeshSetReader public class DtMeshSetReader
{ {
private readonly DtMeshDataReader meshReader = new DtMeshDataReader(); private readonly DtMeshDataReader meshReader = new DtMeshDataReader();
@ -31,7 +29,7 @@ namespace DotRecast.Detour.Io
public DtNavMesh Read(BinaryReader @is, int maxVertPerPoly) public DtNavMesh Read(BinaryReader @is, int maxVertPerPoly)
{ {
return Read(RcIO.ToByteBuffer(@is), maxVertPerPoly, false); return Read(IOUtils.ToByteBuffer(@is), maxVertPerPoly, false);
} }
public DtNavMesh Read(RcByteBuffer bb, int maxVertPerPoly) public DtNavMesh Read(RcByteBuffer bb, int maxVertPerPoly)
@ -41,7 +39,7 @@ namespace DotRecast.Detour.Io
public DtNavMesh Read32Bit(BinaryReader @is, int maxVertPerPoly) public DtNavMesh Read32Bit(BinaryReader @is, int maxVertPerPoly)
{ {
return Read(RcIO.ToByteBuffer(@is), maxVertPerPoly, true); return Read(IOUtils.ToByteBuffer(@is), maxVertPerPoly, true);
} }
public DtNavMesh Read32Bit(RcByteBuffer bb, int maxVertPerPoly) public DtNavMesh Read32Bit(RcByteBuffer bb, int maxVertPerPoly)
@ -51,7 +49,7 @@ namespace DotRecast.Detour.Io
public DtNavMesh Read(BinaryReader @is) public DtNavMesh Read(BinaryReader @is)
{ {
return Read(RcIO.ToByteBuffer(@is)); return Read(IOUtils.ToByteBuffer(@is));
} }
public DtNavMesh Read(RcByteBuffer bb) public DtNavMesh Read(RcByteBuffer bb)
@ -68,9 +66,8 @@ namespace DotRecast.Detour.Io
} }
bool cCompatibility = header.version == NavMeshSetHeader.NAVMESHSET_VERSION; bool cCompatibility = header.version == NavMeshSetHeader.NAVMESHSET_VERSION;
DtNavMesh mesh = new DtNavMesh(); DtNavMesh mesh = new DtNavMesh(header.option, header.maxVertsPerPoly);
mesh.Init(header.option, header.maxVertsPerPoly); ReadTiles(bb, is32Bit, header, cCompatibility, mesh);
ReadTiles(bb, is32Bit, ref header, cCompatibility, mesh);
return mesh; return mesh;
} }
@ -80,7 +77,7 @@ namespace DotRecast.Detour.Io
header.magic = bb.GetInt(); header.magic = bb.GetInt();
if (header.magic != NavMeshSetHeader.NAVMESHSET_MAGIC) if (header.magic != NavMeshSetHeader.NAVMESHSET_MAGIC)
{ {
header.magic = RcIO.SwapEndianness(header.magic); header.magic = IOUtils.SwapEndianness(header.magic);
if (header.magic != NavMeshSetHeader.NAVMESHSET_MAGIC) if (header.magic != NavMeshSetHeader.NAVMESHSET_MAGIC)
{ {
throw new IOException("Invalid magic " + header.magic); throw new IOException("Invalid magic " + header.magic);
@ -107,7 +104,7 @@ namespace DotRecast.Detour.Io
return header; return header;
} }
private void ReadTiles(RcByteBuffer bb, bool is32Bit, ref NavMeshSetHeader header, bool cCompatibility, DtNavMesh mesh) private void ReadTiles(RcByteBuffer bb, bool is32Bit, NavMeshSetHeader header, bool cCompatibility, DtNavMesh mesh)
{ {
// Read tiles. // Read tiles.
for (int i = 0; i < header.numTiles; ++i) for (int i = 0; i < header.numTiles; ++i)
@ -134,7 +131,7 @@ namespace DotRecast.Detour.Io
} }
DtMeshData data = meshReader.Read(bb, mesh.GetMaxVertsPerPoly(), is32Bit); DtMeshData data = meshReader.Read(bb, mesh.GetMaxVertsPerPoly(), is32Bit);
mesh.AddTile(data, i, tileHeader.tileRef, out _); mesh.AddTile(data, i, tileHeader.tileRef);
} }
} }
@ -150,7 +147,7 @@ namespace DotRecast.Detour.Io
int salt = ((refs >> (m_polyBits + m_tileBits)) & saltMask); int salt = ((refs >> (m_polyBits + m_tileBits)) & saltMask);
int it = ((refs >> m_polyBits) & tileMask); int it = ((refs >> m_polyBits) & tileMask);
int ip = refs & polyMask; int ip = refs & polyMask;
return EncodePolyId(salt, it, ip); return DtNavMesh.EncodePolyId(salt, it, ip);
} }
} }
} }

View File

@ -22,7 +22,7 @@ using DotRecast.Core.Numerics;
namespace DotRecast.Detour.Io namespace DotRecast.Detour.Io
{ {
public class DtMeshSetWriter public class DtMeshSetWriter : DtWriter
{ {
private readonly DtMeshDataWriter writer = new DtMeshDataWriter(); private readonly DtMeshDataWriter writer = new DtMeshDataWriter();
private readonly DtNavMeshParamWriter paramWriter = new DtNavMeshParamWriter(); private readonly DtNavMeshParamWriter paramWriter = new DtNavMeshParamWriter();
@ -35,8 +35,8 @@ namespace DotRecast.Detour.Io
private void WriteHeader(BinaryWriter stream, DtNavMesh mesh, RcByteOrder order, bool cCompatibility) private void WriteHeader(BinaryWriter stream, DtNavMesh mesh, RcByteOrder order, bool cCompatibility)
{ {
RcIO.Write(stream, NavMeshSetHeader.NAVMESHSET_MAGIC, order); Write(stream, NavMeshSetHeader.NAVMESHSET_MAGIC, order);
RcIO.Write(stream, cCompatibility ? NavMeshSetHeader.NAVMESHSET_VERSION : NavMeshSetHeader.NAVMESHSET_VERSION_RECAST4J, order); Write(stream, cCompatibility ? NavMeshSetHeader.NAVMESHSET_VERSION : NavMeshSetHeader.NAVMESHSET_VERSION_RECAST4J, order);
int numTiles = 0; int numTiles = 0;
for (int i = 0; i < mesh.GetMaxTiles(); ++i) for (int i = 0; i < mesh.GetMaxTiles(); ++i)
{ {
@ -49,11 +49,11 @@ namespace DotRecast.Detour.Io
numTiles++; numTiles++;
} }
RcIO.Write(stream, numTiles, order); Write(stream, numTiles, order);
paramWriter.Write(stream, mesh.GetParams(), order); paramWriter.Write(stream, mesh.GetParams(), order);
if (!cCompatibility) if (!cCompatibility)
{ {
RcIO.Write(stream, mesh.GetMaxVertsPerPoly(), order); Write(stream, mesh.GetMaxVertsPerPoly(), order);
} }
} }
@ -77,11 +77,11 @@ namespace DotRecast.Detour.Io
byte[] ba = msw.ToArray(); byte[] ba = msw.ToArray();
tileHeader.dataSize = ba.Length; tileHeader.dataSize = ba.Length;
RcIO.Write(stream, tileHeader.tileRef, order); Write(stream, tileHeader.tileRef, order);
RcIO.Write(stream, tileHeader.dataSize, order); Write(stream, tileHeader.dataSize, order);
if (cCompatibility) if (cCompatibility)
{ {
RcIO.Write(stream, 0, order); // C struct padding Write(stream, 0, order); // C struct padding
} }
stream.Write(ba); stream.Write(ba);

View File

@ -4,17 +4,17 @@ using DotRecast.Core.Numerics;
namespace DotRecast.Detour.Io namespace DotRecast.Detour.Io
{ {
public class DtNavMeshParamWriter public class DtNavMeshParamWriter : DtWriter
{ {
public void Write(BinaryWriter stream, DtNavMeshParams option, RcByteOrder order) public void Write(BinaryWriter stream, DtNavMeshParams option, RcByteOrder order)
{ {
RcIO.Write(stream, option.orig.X, order); Write(stream, option.orig.X, order);
RcIO.Write(stream, option.orig.Y, order); Write(stream, option.orig.Y, order);
RcIO.Write(stream, option.orig.Z, order); Write(stream, option.orig.Z, order);
RcIO.Write(stream, option.tileWidth, order); Write(stream, option.tileWidth, order);
RcIO.Write(stream, option.tileHeight, order); Write(stream, option.tileHeight, order);
RcIO.Write(stream, option.maxTiles, order); Write(stream, option.maxTiles, order);
RcIO.Write(stream, option.maxPolys, order); Write(stream, option.maxPolys, order);
} }
} }
} }

View File

@ -0,0 +1,98 @@
/*
Recast4J Copyright (c) 2015 Piotr Piastucki piotr@jtilia.org
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
*/
using System;
using System.IO;
using DotRecast.Core;
namespace DotRecast.Detour.Io
{
public abstract class DtWriter
{
protected void Write(BinaryWriter stream, float value, RcByteOrder order)
{
byte[] bytes = BitConverter.GetBytes(value);
int i = BitConverter.ToInt32(bytes, 0);
Write(stream, i, order);
}
protected void Write(BinaryWriter stream, short value, RcByteOrder order)
{
if (order == RcByteOrder.BIG_ENDIAN)
{
stream.Write((byte)((value >> 8) & 0xFF));
stream.Write((byte)(value & 0xFF));
}
else
{
stream.Write((byte)(value & 0xFF));
stream.Write((byte)((value >> 8) & 0xFF));
}
}
protected void Write(BinaryWriter stream, long value, RcByteOrder order)
{
if (order == RcByteOrder.BIG_ENDIAN)
{
Write(stream, (int)((ulong)value >> 32), order);
Write(stream, (int)(value & 0xFFFFFFFF), order);
}
else
{
Write(stream, (int)(value & 0xFFFFFFFF), order);
Write(stream, (int)((ulong)value >> 32), order);
}
}
protected void Write(BinaryWriter stream, int value, RcByteOrder order)
{
if (order == RcByteOrder.BIG_ENDIAN)
{
stream.Write((byte)((value >> 24) & 0xFF));
stream.Write((byte)((value >> 16) & 0xFF));
stream.Write((byte)((value >> 8) & 0xFF));
stream.Write((byte)(value & 0xFF));
}
else
{
stream.Write((byte)(value & 0xFF));
stream.Write((byte)((value >> 8) & 0xFF));
stream.Write((byte)((value >> 16) & 0xFF));
stream.Write((byte)((value >> 24) & 0xFF));
}
}
protected void Write(BinaryWriter stream, bool @bool)
{
Write(stream, (byte)(@bool ? 1 : 0));
}
protected void Write(BinaryWriter stream, byte value)
{
stream.Write(value);
}
protected void Write(BinaryWriter stream, MemoryStream data)
{
data.Position = 0;
byte[] buffer = new byte[data.Length];
data.Read(buffer, 0, buffer.Length);
stream.Write(buffer);
}
}
}

View File

@ -0,0 +1,64 @@
/*
Recast4J Copyright (c) 2015 Piotr Piastucki piotr@jtilia.org
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
*/
using System;
using System.IO;
using DotRecast.Core;
namespace DotRecast.Detour.Io
{
public static class IOUtils
{
public static RcByteBuffer ToByteBuffer(BinaryReader @is, bool direct)
{
byte[] data = ToByteArray(@is);
if (direct)
{
Array.Reverse(data);
}
return new RcByteBuffer(data);
}
public static byte[] ToByteArray(BinaryReader inputStream)
{
using var msw = new MemoryStream();
byte[] buffer = new byte[4096];
int l;
while ((l = inputStream.Read(buffer)) > 0)
{
msw.Write(buffer, 0, l);
}
return msw.ToArray();
}
public static RcByteBuffer ToByteBuffer(BinaryReader inputStream)
{
var bytes = ToByteArray(inputStream);
return new RcByteBuffer(bytes);
}
public static int SwapEndianness(int i)
{
var s = (((uint)i >> 24) & 0xFF) | (((uint)i >> 8) & 0xFF00) | (((uint)i << 8) & 0xFF0000) | ((i << 24) & 0xFF000000);
return (int)s;
}
}
}

View File

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

View File

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

View File

@ -1,7 +1,7 @@
/* /*
Copyright (c) 2009-2010 Mikko Mononen memon@inside.org Copyright (c) 2009-2010 Mikko Mononen memon@inside.org
recast4j copyright (c) 2015-2019 Piotr Piastucki piotr@jtilia.org recast4j copyright (c) 2015-2019 Piotr Piastucki piotr@jtilia.org
DotRecast Copyright (c) 2023-2024 Choi Ikpil ikpil@naver.com DotRecast Copyright (c) 2023 Choi Ikpil ikpil@naver.com
This software is provided 'as-is', without any express or implied This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages warranty. In no event will the authors be held liable for any damages
@ -32,7 +32,6 @@ namespace DotRecast.Recast.Demo
private DtNavMesh _navMesh; private DtNavMesh _navMesh;
private DtNavMeshQuery _navMeshQuery; private DtNavMeshQuery _navMeshQuery;
private readonly RcNavMeshBuildSettings _settings; private readonly RcNavMeshBuildSettings _settings;
private RcConfig _cfg;
private IList<RcBuilderResult> _recastResults; private IList<RcBuilderResult> _recastResults;
private bool _changed; private bool _changed;
@ -57,11 +56,6 @@ namespace DotRecast.Recast.Demo
return _geom; return _geom;
} }
public RcConfig GetRecastConfig()
{
return _cfg;
}
public IList<RcBuilderResult> GetRecastResults() public IList<RcBuilderResult> GetRecastResults()
{ {
return _recastResults; return _recastResults;
@ -92,10 +86,9 @@ namespace DotRecast.Recast.Demo
_changed = changed; _changed = changed;
} }
public void Update(DemoInputGeomProvider geom, RcConfig cfg, IList<RcBuilderResult> recastResults, DtNavMesh navMesh) public void Update(DemoInputGeomProvider geom, IList<RcBuilderResult> recastResults, DtNavMesh navMesh)
{ {
_geom = geom; _geom = geom;
_cfg = cfg;
_recastResults = recastResults; _recastResults = recastResults;
_navMesh = navMesh; _navMesh = navMesh;
SetQuery(navMesh); SetQuery(navMesh);

View File

@ -1,44 +0,0 @@
{
"name": "DotRecast.Recast.Demo",
"rootNamespace": "DotRecast.Recast.Demo",
"references": [
"DotRecast.Core"
],
"includePlatforms": [],
"excludePlatforms": [
"Android",
"Editor",
"EmbeddedLinux",
"GameCoreScarlett",
"GameCoreXboxOne",
"iOS",
"LinuxStandalone64",
"CloudRendering",
"macOSStandalone",
"PS4",
"PS5",
"QNX",
"Stadia",
"Switch",
"tvOS",
"WSA",
"VisionOS",
"WebGL",
"WindowsStandalone32",
"WindowsStandalone64",
"XboxOne"
],
"allowUnsafeCode": false,
"overrideReferences": false,
"precompiledReferences": [],
"autoReferenced": true,
"defineConstraints": [],
"versionDefines": [
{
"name": "Select...",
"expression": "",
"define": "USE_RECAST_DEMO"
}
],
"noEngineReferences": true
}

View File

@ -2,17 +2,16 @@
<PropertyGroup> <PropertyGroup>
<OutputType>Exe</OutputType> <OutputType>Exe</OutputType>
<TargetFrameworks>net6.0;net7.0;net8.0</TargetFrameworks> <TargetFrameworks>net6.0;net7.0</TargetFrameworks>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks> <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<PackageId>DotRecast.Recast.Demo</PackageId> <PackageId>DotRecast.Recast.Demo</PackageId>
<PackageReadmeFile>README.md</PackageReadmeFile> <PackageReadmeFile>README.md</PackageReadmeFile>
<Authors>ikpil</Authors> <Authors>ikpil</Authors>
<Description>DotRecast - a port of Recast Detour, Industry-standard navigation mesh toolset for .NET, C#, Unity3D, games, servers</Description> <Description>DotRecast - a port of Recast Detour, navigation mesh toolset for games, Unity3D, servers, C#</Description>
<RepositoryType>git</RepositoryType> <RepositoryType>git</RepositoryType>
<PackageProjectUrl>https://github.com/ikpil/DotRecast</PackageProjectUrl> <PackageProjectUrl>https://github.com/ikpil/DotRecast</PackageProjectUrl>
<RepositoryUrl>https://github.com/ikpil/DotRecast</RepositoryUrl> <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> <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> </PropertyGroup>
<ItemGroup> <ItemGroup>
@ -20,15 +19,15 @@
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Serilog" Version="4.1.0" /> <PackageReference Include="Serilog" Version="3.0.1"/>
<PackageReference Include="Serilog.Settings.Configuration" Version="8.0.4" /> <PackageReference Include="Serilog.Settings.Configuration" Version="7.0.1"/>
<PackageReference Include="Serilog.Enrichers.Thread" Version="4.0.0" /> <PackageReference Include="Serilog.Enrichers.Thread" Version="3.1.0"/>
<PackageReference Include="Serilog.Sinks.Async" Version="2.1.0" /> <PackageReference Include="Serilog.Sinks.Async" Version="1.5.0"/>
<PackageReference Include="Serilog.Sinks.Console" Version="6.0.0" /> <PackageReference Include="Serilog.Sinks.Console" Version="4.1.0"/>
<PackageReference Include="Serilog.Sinks.File" Version="6.0.0" /> <PackageReference Include="Serilog.Sinks.File" Version="5.0.0"/>
<PackageReference Include="K4os.Compression.LZ4" Version="1.3.8" /> <PackageReference Include="K4os.Compression.LZ4" Version="1.3.6"/>
<PackageReference Include="Silk.NET" Version="2.22.0" /> <PackageReference Include="Silk.NET" Version="2.17.1"/>
<PackageReference Include="Silk.NET.OpenGL.Extensions.ImGui" Version="2.22.0" /> <PackageReference Include="Silk.NET.OpenGL.Extensions.ImGui" Version="2.17.1"/>
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>

View File

@ -1,5 +1,4 @@
using System; using System;
using DotRecast.Core;
namespace DotRecast.Recast.Demo.Draw; namespace DotRecast.Recast.Demo.Draw;
@ -9,23 +8,23 @@ public class ArrayBuffer<T>
private T[] _items; private T[] _items;
public int Count => _size; public int Count => _size;
public ArrayBuffer() : this(512) { } public ArrayBuffer()
public ArrayBuffer(int capacity)
{ {
if (capacity <= 0)
throw new ArgumentOutOfRangeException();
_size = 0; _size = 0;
_items = new T[capacity]; _items = Array.Empty<T>();
} }
public void Add(T item) public void Add(T item)
{ {
if (0 >= _items.Length)
{
_items = new T[256];
}
if (_items.Length <= _size) if (_items.Length <= _size)
{ {
var temp = new T[(int)(_size * 1.5)]; var temp = new T[(int)(_size * 1.5)];
RcArrays.Copy(_items, 0, temp, 0, _items.Length); Array.Copy(_items, 0, temp, 0, _items.Length);
_items = temp; _items = temp;
} }
@ -37,8 +36,8 @@ public class ArrayBuffer<T>
_size = 0; _size = 0;
} }
public Span<T> AsArray() public T[] AsArray()
{ {
return _items.AsSpan(0, _size); return _items;
} }
} }

View File

@ -1,7 +1,7 @@
/* /*
Copyright (c) 2009-2010 Mikko Mononen memon@inside.org Copyright (c) 2009-2010 Mikko Mononen memon@inside.org
recast4j copyright (c) 2015-2019 Piotr Piastucki piotr@jtilia.org recast4j copyright (c) 2015-2019 Piotr Piastucki piotr@jtilia.org
DotRecast Copyright (c) 2023-2024 Choi Ikpil ikpil@naver.com DotRecast Copyright (c) 2023 Choi Ikpil ikpil@naver.com
This software is provided 'as-is', without any express or implied This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages warranty. In no event will the authors be held liable for any damages
@ -326,9 +326,9 @@ public class DebugDraw
} }
} }
private const int NUM_ARC_PTS = 8; private static readonly int NUM_ARC_PTS = 8;
private const float PAD = 0.05f; private static readonly float PAD = 0.05f;
private const float ARC_PTS_SCALE = (1.0f - PAD * 2) / NUM_ARC_PTS; private static readonly float ARC_PTS_SCALE = (1.0f - PAD * 2) / NUM_ARC_PTS;
public void AppendArc(float x0, float y0, float z0, float x1, float y1, float z1, float h, float as0, float as1, int col) public void AppendArc(float x0, float y0, float z0, float x1, float y1, float z1, float h, float as0, float as1, int col)
{ {
@ -694,7 +694,7 @@ public class DebugDraw
plane[3] = pw; plane[3] = pw;
} }
public bool FrustumTest(Span<float> bounds) public bool FrustumTest(float[] bounds)
{ {
foreach (float[] plane in frustumPlanes) foreach (float[] plane in frustumPlanes)
{ {
@ -748,6 +748,6 @@ public class DebugDraw
public bool FrustumTest(RcVec3f bmin, RcVec3f bmax) public bool FrustumTest(RcVec3f bmin, RcVec3f bmax)
{ {
return FrustumTest(stackalloc float[] { bmin.X, bmin.Y, bmin.Z, bmax.X, bmax.Y, bmax.Z }); return FrustumTest(new float[] { bmin.X, bmin.Y, bmin.Z, bmax.X, bmax.Y, bmax.Z });
} }
} }

View File

@ -1,7 +1,7 @@
/* /*
Copyright (c) 2009-2010 Mikko Mononen memon@inside.org Copyright (c) 2009-2010 Mikko Mononen memon@inside.org
recast4j copyright (c) 2015-2019 Piotr Piastucki piotr@jtilia.org recast4j copyright (c) 2015-2019 Piotr Piastucki piotr@jtilia.org
DotRecast Copyright (c) 2023-2024 Choi Ikpil ikpil@naver.com DotRecast Copyright (c) 2023 Choi Ikpil ikpil@naver.com
This software is provided 'as-is', without any express or implied This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages warranty. In no event will the authors be held liable for any damages

View File

@ -1,7 +1,7 @@
/* /*
Copyright (c) 2009-2010 Mikko Mononen memon@inside.org Copyright (c) 2009-2010 Mikko Mononen memon@inside.org
recast4j copyright (c) 2015-2019 Piotr Piastucki piotr@jtilia.org recast4j copyright (c) 2015-2019 Piotr Piastucki piotr@jtilia.org
DotRecast Copyright (c) 2023-2024 Choi Ikpil ikpil@naver.com DotRecast Copyright (c) 2023 Choi Ikpil ikpil@naver.com
This software is provided 'as-is', without any express or implied This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages warranty. In no event will the authors be held liable for any damages
@ -64,8 +64,8 @@ public class DrawMode
DRAWMODE_POLYMESH_DETAIL DRAWMODE_POLYMESH_DETAIL
); );
public readonly int Idx; public int Idx { get; }
public readonly string Text; public string Text { get; }
private DrawMode(int idx, string text) private DrawMode(int idx, string text)
{ {

View File

@ -1,7 +1,7 @@
/* /*
Copyright (c) 2009-2010 Mikko Mononen memon@inside.org Copyright (c) 2009-2010 Mikko Mononen memon@inside.org
recast4j copyright (c) 2015-2019 Piotr Piastucki piotr@jtilia.org recast4j copyright (c) 2015-2019 Piotr Piastucki piotr@jtilia.org
DotRecast Copyright (c) 2023-2024 Choi Ikpil ikpil@naver.com DotRecast Copyright (c) 2023 Choi Ikpil ikpil@naver.com
This software is provided 'as-is', without any express or implied This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages warranty. In no event will the authors be held liable for any damages
@ -32,18 +32,15 @@ public class GLCheckerTexture
_gl = gl; _gl = gl;
} }
public unsafe void Release() public void Release()
{ {
if (m_texId != 0) if (m_texId != 0)
{ {
fixed (uint* p = &m_texId) _gl.DeleteTextures(1, m_texId);
{
_gl.DeleteTextures(1, p);
}
} }
} }
public unsafe void Bind() public void Bind()
{ {
if (m_texId == 0) if (m_texId == 0)
{ {
@ -53,11 +50,7 @@ public class GLCheckerTexture
uint TSIZE = 64; uint TSIZE = 64;
int[] data = new int[TSIZE * TSIZE]; int[] data = new int[TSIZE * TSIZE];
fixed (uint* p = &m_texId) _gl.GenTextures(1, out m_texId);
{
_gl.GenTextures(1, p);
}
_gl.BindTexture(GLEnum.Texture2D, m_texId); _gl.BindTexture(GLEnum.Texture2D, m_texId);
int level = 0; int level = 0;
@ -77,10 +70,8 @@ public class GLCheckerTexture
level++; level++;
} }
uint linearMipmapNearest = (uint)GLEnum.LinearMipmapNearest; _gl.TexParameterI(GLEnum.Texture2D, GLEnum.TextureMinFilter, (uint)GLEnum.LinearMipmapNearest);
uint linear = (uint)GLEnum.Linear; _gl.TexParameterI(GLEnum.Texture2D, GLEnum.TextureMagFilter, (uint)GLEnum.Linear);
_gl.TexParameterI(GLEnum.Texture2D, GLEnum.TextureMinFilter, &linearMipmapNearest);
_gl.TexParameterI(GLEnum.Texture2D, GLEnum.TextureMagFilter, &linear);
} }
else else
{ {

View File

@ -1,6 +1,5 @@
/* /*
recast4j Copyright (c) 2015-2019 Piotr Piastucki piotr@jtilia.org recast4j copyright (c) 2015-2019 Piotr Piastucki piotr@jtilia.org
DotRecast Copyright (c) 2023-2024 Choi Ikpil ikpil@naver.com
This software is provided 'as-is', without any express or implied This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages warranty. In no event will the authors be held liable for any damages
@ -69,8 +68,8 @@ public static class GLU
public static int GlhUnProjectf(float winx, float winy, float winz, float[] modelview, float[] projection, int[] viewport, ref RcVec3f objectCoordinate) public static int GlhUnProjectf(float winx, float winy, float winz, float[] modelview, float[] projection, int[] viewport, ref RcVec3f objectCoordinate)
{ {
// Transformation matrices // Transformation matrices
Span<float> m = stackalloc float[16], A = stackalloc float[16]; float[] m = new float[16], A = new float[16];
Span<float> @in = stackalloc float[4], @out = stackalloc float[4]; float[] @in = new float[4], @out = new float[4];
// Calculation for inverting a matrix, compute projection x modelview // Calculation for inverting a matrix, compute projection x modelview
// and store in A[16] // and store in A[16]
MultiplyMatrices4by4OpenGL_FLOAT(A, projection, modelview); MultiplyMatrices4by4OpenGL_FLOAT(A, projection, modelview);
@ -93,7 +92,7 @@ public static class GLU
return 1; return 1;
} }
static void MultiplyMatrices4by4OpenGL_FLOAT(Span<float> result, float[] matrix1, float[] matrix2) static void MultiplyMatrices4by4OpenGL_FLOAT(float[] result, float[] matrix1, float[] matrix2)
{ {
result[0] = matrix1[0] * matrix2[0] + matrix1[4] * matrix2[1] + matrix1[8] * matrix2[2] + matrix1[12] * matrix2[3]; result[0] = matrix1[0] * matrix2[0] + matrix1[4] * matrix2[1] + matrix1[8] * matrix2[2] + matrix1[12] * matrix2[3];
result[4] = matrix1[0] * matrix2[4] + matrix1[4] * matrix2[5] + matrix1[8] * matrix2[6] + matrix1[12] * matrix2[7]; result[4] = matrix1[0] * matrix2[4] + matrix1[4] * matrix2[5] + matrix1[8] * matrix2[6] + matrix1[12] * matrix2[7];
@ -116,7 +115,7 @@ public static class GLU
result[15] = matrix1[3] * matrix2[12] + matrix1[7] * matrix2[13] + matrix1[11] * matrix2[14] + matrix1[15] * matrix2[15]; result[15] = matrix1[3] * matrix2[12] + matrix1[7] * matrix2[13] + matrix1[11] * matrix2[14] + matrix1[15] * matrix2[15];
} }
static void MultiplyMatrixByVector4by4OpenGL_FLOAT(Span<float> resultvector, Span<float> matrix, Span<float> pvector) static void MultiplyMatrixByVector4by4OpenGL_FLOAT(float[] resultvector, float[] matrix, float[] pvector)
{ {
resultvector[0] = matrix[0] * pvector[0] + matrix[4] * pvector[1] + matrix[8] * pvector[2] + matrix[12] * pvector[3]; resultvector[0] = matrix[0] * pvector[0] + matrix[4] * pvector[1] + matrix[8] * pvector[2] + matrix[12] * pvector[3];
resultvector[1] = matrix[1] * pvector[0] + matrix[5] * pvector[1] + matrix[9] * pvector[2] + matrix[13] * pvector[3]; resultvector[1] = matrix[1] * pvector[0] + matrix[5] * pvector[1] + matrix[9] * pvector[2] + matrix[13] * pvector[3];
@ -125,13 +124,15 @@ public static class GLU
} }
// This code comes directly from GLU except that it is for float // This code comes directly from GLU except that it is for float
static int GlhInvertMatrixf2(Span<float> m, Span<float> @out) static int GlhInvertMatrixf2(float[] m, float[] @out)
{ {
float[][] wtmp = RcArrayUtils.Of<float>(4, 8);
float m0, m1, m2, m3, s; float m0, m1, m2, m3, s;
Span<float> r0 = stackalloc float[8]; float[] r0, r1, r2, r3;
Span<float> r1 = stackalloc float[8]; r0 = wtmp[0];
Span<float> r2 = stackalloc float[8]; r1 = wtmp[1];
Span<float> r3 = stackalloc float[8]; r2 = wtmp[2];
r3 = wtmp[3];
r0[0] = MAT(m, 0, 0); r0[0] = MAT(m, 0, 0);
r0[1] = MAT(m, 0, 1); r0[1] = MAT(m, 0, 1);
r0[2] = MAT(m, 0, 2); r0[2] = MAT(m, 0, 2);
@ -159,28 +160,27 @@ public static class GLU
/* choose pivot - or die */ /* choose pivot - or die */
if (MathF.Abs(r3[0]) > MathF.Abs(r2[0])) if (MathF.Abs(r3[0]) > MathF.Abs(r2[0]))
{ {
Span<float> r = r2; float[] r = r2;
r2 = r3; r2 = r3;
r3 = r; r3 = r;
} }
if (MathF.Abs(r2[0]) > MathF.Abs(r1[0])) if (MathF.Abs(r2[0]) > MathF.Abs(r1[0]))
{ {
Span<float> r = r2; float[] r = r2;
r2 = r1; r2 = r1;
r1 = r; r1 = r;
} }
if (MathF.Abs(r1[0]) > MathF.Abs(r0[0])) if (MathF.Abs(r1[0]) > MathF.Abs(r0[0]))
{ {
Span<float> r = r1; float[] r = r1;
r1 = r0; r1 = r0;
r0 = r; r0 = r;
} }
if (0.0 == r0[0]) if (0.0 == r0[0])
return 0; return 0;
/* eliminate first variable */ /* eliminate first variable */
m1 = r1[0] / r0[0]; m1 = r1[0] / r0[0];
m2 = r2[0] / r0[0]; m2 = r2[0] / r0[0];
@ -232,14 +232,14 @@ public static class GLU
/* choose pivot - or die */ /* choose pivot - or die */
if (MathF.Abs(r3[1]) > MathF.Abs(r2[1])) if (MathF.Abs(r3[1]) > MathF.Abs(r2[1]))
{ {
Span<float> r = r2; float[] r = r2;
r2 = r3; r2 = r3;
r3 = r; r3 = r;
} }
if (MathF.Abs(r2[1]) > MathF.Abs(r1[1])) if (MathF.Abs(r2[1]) > MathF.Abs(r1[1]))
{ {
Span<float> r = r2; float[] r = r2;
r2 = r1; r2 = r1;
r1 = r; r1 = r;
} }
@ -284,7 +284,7 @@ public static class GLU
/* choose pivot - or die */ /* choose pivot - or die */
if (MathF.Abs(r3[2]) > MathF.Abs(r2[2])) if (MathF.Abs(r3[2]) > MathF.Abs(r2[2]))
{ {
Span<float> r = r2; float[] r = r2;
r2 = r3; r2 = r3;
r3 = r; r3 = r;
} }
@ -358,12 +358,12 @@ public static class GLU
return 1; return 1;
} }
static float MAT(Span<float> m, int r, int c) static float MAT(float[] m, int r, int c)
{ {
return m[(c) * 4 + (r)]; return m[(c) * 4 + (r)];
} }
static void MAT(Span<float> m, int r, int c, float v) static void MAT(float[] m, int r, int c, float v)
{ {
m[(c) * 4 + (r)] = v; m[(c) * 4 + (r)] = v;
} }

View File

@ -1,4 +1,3 @@
using System.Numerics;
using DotRecast.Core.Numerics; using DotRecast.Core.Numerics;
namespace DotRecast.Recast.Demo.Draw; namespace DotRecast.Recast.Demo.Draw;

View File

@ -1,11 +1,10 @@
using System; using System;
using System.IO;
using Silk.NET.OpenGL; using Silk.NET.OpenGL;
using DotRecast.Core.Numerics; using DotRecast.Core.Numerics;
namespace DotRecast.Recast.Demo.Draw; namespace DotRecast.Recast.Demo.Draw;
// TODO use a lot of Memory, 2GB+
public class ModernOpenGLDraw : IOpenGLDraw public class ModernOpenGLDraw : IOpenGLDraw
{ {
private GL _gl; private GL _gl;
@ -20,8 +19,8 @@ public class ModernOpenGLDraw : IOpenGLDraw
private float fogEnd; private float fogEnd;
private bool fogEnabled; private bool fogEnabled;
private int uniformViewMatrix; private int uniformViewMatrix;
private readonly ArrayBuffer<OpenGLVertex> vertices = new(512); private readonly ArrayBuffer<OpenGLVertex> vertices = new();
private readonly ArrayBuffer<int> elements = new(512); private readonly ArrayBuffer<int> elements = new();
private GLCheckerTexture _texture; private GLCheckerTexture _texture;
private readonly float[] _viewMatrix = new float[16]; private readonly float[] _viewMatrix = new float[16];
private readonly float[] _projectionMatrix = new float[16]; private readonly float[] _projectionMatrix = new float[16];
@ -37,42 +36,36 @@ public class ModernOpenGLDraw : IOpenGLDraw
public unsafe void Init() public unsafe void Init()
{ {
const string SHADER_VERSION = "#version 400\n"; string SHADER_VERSION = "#version 400\n";
const string vertex_shader = $@" string vertex_shader = SHADER_VERSION + "uniform mat4 ProjMtx;\n" //
{SHADER_VERSION} + "uniform mat4 ViewMtx;\n" //
uniform mat4 ProjMtx; + "in vec3 Position;\n" //
uniform mat4 ViewMtx; + "in vec2 TexCoord;\n" //
in vec3 Position; + "in vec4 Color;\n" //
in vec2 TexCoord; + "out vec2 Frag_UV;\n" //
in vec4 Color; + "out vec4 Frag_Color;\n" //
out vec2 Frag_UV; + "out float Frag_Depth;\n" //
out vec4 Frag_Color; + "void main() {\n" //
out float Frag_Depth; + " Frag_UV = TexCoord;\n" //
void main() {{ + " Frag_Color = Color;\n" //
Frag_UV = TexCoord; + " vec4 VSPosition = ViewMtx * vec4(Position, 1);\n" //
Frag_Color = Color; + " Frag_Depth = -VSPosition.z;\n" //
vec4 VSPosition = ViewMtx * vec4(Position, 1); + " gl_Position = ProjMtx * VSPosition;\n" //
Frag_Depth = -VSPosition.z; + "}\n";
gl_Position = ProjMtx * VSPosition; string fragment_shader = SHADER_VERSION + "precision mediump float;\n" //
}} + "uniform sampler2D Texture;\n" //
"; + "uniform float UseTexture;\n" //
const string fragment_shader = $@" + "uniform float EnableFog;\n" //
{SHADER_VERSION} + "uniform float FogStart;\n" //
precision mediump float; + "uniform float FogEnd;\n" //
uniform sampler2D Texture; + "const vec4 FogColor = vec4(0.3f, 0.3f, 0.32f, 1.0f);\n" //
uniform float UseTexture; + "in vec2 Frag_UV;\n" //
uniform float EnableFog; + "in vec4 Frag_Color;\n" //
uniform float FogStart; + "in float Frag_Depth;\n" //
uniform float FogEnd; + "out vec4 Out_Color;\n" //
const vec4 FogColor = vec4(0.3f, 0.3f, 0.32f, 1.0f); + "void main(){\n" //
in vec2 Frag_UV; + " Out_Color = mix(FogColor, Frag_Color * mix(vec4(1), texture(Texture, Frag_UV.st), UseTexture), 1.0 - EnableFog * clamp( (Frag_Depth - FogStart) / (FogEnd - FogStart), 0.0, 1.0) );\n" //
in vec4 Frag_Color; + "}\n";
in float Frag_Depth;
out vec4 Out_Color;
void main(){{
Out_Color = mix(FogColor, Frag_Color * mix(vec4(1), texture(Texture, Frag_UV.st), UseTexture), 1.0 - EnableFog * clamp( (Frag_Depth - FogStart) / (FogEnd - FogStart), 0.0, 1.0) );
}}
";
program = _gl.CreateProgram(); program = _gl.CreateProgram();
uint vert_shdr = _gl.CreateShader(GLEnum.VertexShader); uint vert_shdr = _gl.CreateShader(GLEnum.VertexShader);
@ -157,10 +150,6 @@ void main(){{
_gl.BindVertexArray(0); _gl.BindVertexArray(0);
_gl.BindBuffer(GLEnum.ArrayBuffer, 0); _gl.BindBuffer(GLEnum.ArrayBuffer, 0);
_gl.BindBuffer(GLEnum.ElementArrayBuffer, 0); _gl.BindBuffer(GLEnum.ElementArrayBuffer, 0);
//int* range = stackalloc int[2];
//_gl.GetInteger(GetPName.LineWidthRange, range);
//Console.WriteLine($"\nLineWidthRange: {range[0]} {range[1]}");
} }
public void Clear() public void Clear()
@ -202,18 +191,35 @@ void main(){{
// GlBufferData(GL_ARRAY_BUFFER, MAX_VERTEX_BUFFER, GL_STREAM_DRAW); // GlBufferData(GL_ARRAY_BUFFER, MAX_VERTEX_BUFFER, GL_STREAM_DRAW);
// GlBufferData(GL_ELEMENT_ARRAY_BUFFER, MAX_ELEMENT_BUFFER, GL_STREAM_DRAW); // GlBufferData(GL_ELEMENT_ARRAY_BUFFER, MAX_ELEMENT_BUFFER, GL_STREAM_DRAW);
_gl.BufferData<OpenGLVertex>(GLEnum.ArrayBuffer, vertices.AsArray(), GLEnum.DynamicDraw); uint vboSize = (uint)vertices.Count * 24;
uint eboSize = currentPrim == DebugDrawPrimitives.QUADS ? (uint)vertices.Count * 6 : (uint)vertices.Count * 4;
_gl.BufferData(GLEnum.ArrayBuffer, vboSize, null, GLEnum.StreamDraw);
_gl.BufferData(GLEnum.ElementArrayBuffer, eboSize, null, GLEnum.StreamDraw);
// load draw vertices & elements directly into vertex + element buffer
{
byte* pVerts = (byte*)_gl.MapBuffer(GLEnum.ArrayBuffer, GLEnum.WriteOnly);
byte* pElems = (byte*)_gl.MapBuffer(GLEnum.ElementArrayBuffer, GLEnum.WriteOnly);
//vertices.ForEach(v => v.Store(verts));
fixed (void* v = vertices.AsArray())
{
System.Buffer.MemoryCopy(v, pVerts, vboSize, vboSize);
}
if (currentPrim == DebugDrawPrimitives.QUADS) if (currentPrim == DebugDrawPrimitives.QUADS)
{ {
using var unmanagedElems = new UnmanagedMemoryStream(pElems, eboSize, eboSize, FileAccess.Write);
using var bw = new BinaryWriter(unmanagedElems);
for (int i = 0; i < vertices.Count; i += 4) for (int i = 0; i < vertices.Count; i += 4)
{ {
elements.Add(i); bw.Write(i);
elements.Add(i + 1); bw.Write(i + 1);
elements.Add(i + 2); bw.Write(i + 2);
elements.Add(i); bw.Write(i);
elements.Add(i + 2); bw.Write(i + 2);
elements.Add(i + 3); bw.Write(i + 3);
} }
} }
else else
@ -222,10 +228,16 @@ void main(){{
{ {
elements.Add(i); elements.Add(i);
} }
fixed (void* e = elements.AsArray())
{
System.Buffer.MemoryCopy(e, pElems, eboSize, eboSize);
}
} }
_gl.BufferData<int>(GLEnum.ElementArrayBuffer, elements.AsArray(), GLEnum.DynamicDraw); _gl.UnmapBuffer(GLEnum.ElementArrayBuffer);
_gl.UnmapBuffer(GLEnum.ArrayBuffer);
}
if (_texture != null) if (_texture != null)
{ {
_texture.Bind(); _texture.Bind();
@ -259,22 +271,15 @@ void main(){{
_gl.BindBuffer(GLEnum.ArrayBuffer, 0); _gl.BindBuffer(GLEnum.ArrayBuffer, 0);
_gl.BindBuffer(GLEnum.ElementArrayBuffer, 0); _gl.BindBuffer(GLEnum.ElementArrayBuffer, 0);
vertices.Clear(); vertices.Clear();
elements.Clear();
_gl.LineWidth(1.0f); _gl.LineWidth(1.0f);
_gl.PointSize(1.0f); _gl.PointSize(1.0f);
} }
public void Vertex(float x, float y, float z, int color) public void Vertex(float x, float y, float z, int color)
{ {
vertices.Add(new OpenGLVertex(x, y, z, color)); vertices.Add(new OpenGLVertex(x, y, z, color));
} }
public unsafe void Vertex(float* pos, int color)
{
vertices.Add(new OpenGLVertex(pos[0], pos[1], pos[2], color));
}
public void Vertex(float[] pos, int color) public void Vertex(float[] pos, int color)
{ {
vertices.Add(new OpenGLVertex(pos, color)); vertices.Add(new OpenGLVertex(pos, color));

View File

@ -1,7 +1,7 @@
/* /*
Copyright (c) 2009-2010 Mikko Mononen memon@inside.org Copyright (c) 2009-2010 Mikko Mononen memon@inside.org
recast4j copyright (c) 2015-2019 Piotr Piastucki piotr@jtilia.org recast4j copyright (c) 2015-2019 Piotr Piastucki piotr@jtilia.org
DotRecast Copyright (c) 2023-2024 Choi Ikpil ikpil@naver.com DotRecast Copyright (c) 2023 Choi Ikpil ikpil@naver.com
This software is provided 'as-is', without any express or implied This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages warranty. In no event will the authors be held liable for any damages
@ -23,14 +23,14 @@ using DotRecast.Core.Numerics;
using DotRecast.Detour; using DotRecast.Detour;
using DotRecast.Recast.Toolset.Builder; using DotRecast.Recast.Toolset.Builder;
using DotRecast.Recast.Toolset.Geom; using DotRecast.Recast.Toolset.Geom;
using static DotRecast.Recast.RcRecast; using static DotRecast.Recast.RcCommons;
namespace DotRecast.Recast.Demo.Draw; namespace DotRecast.Recast.Demo.Draw;
public class NavMeshRenderer public class NavMeshRenderer
{ {
private readonly RecastDebugDraw _debugDraw; private readonly RecastDebugDraw _debugDraw;
private readonly int _navMeshDrawFlags = RecastDebugDraw.DU_DRAWNAVMESH_OFFMESHCONS | RecastDebugDraw.DU_DRAWNAVMESH_CLOSEDLIST; private readonly int _navMeshDrawFlags = RecastDebugDraw.DRAWNAVMESH_OFFMESHCONS | RecastDebugDraw.DRAWNAVMESH_CLOSEDLIST;
public NavMeshRenderer(RecastDebugDraw debugDraw) public NavMeshRenderer(RecastDebugDraw debugDraw)
{ {
@ -123,80 +123,80 @@ public class NavMeshRenderer
foreach (RcBuilderResult rcBuilderResult in rcBuilderResults) foreach (RcBuilderResult rcBuilderResult in rcBuilderResults)
{ {
if (rcBuilderResult.CompactHeightfield != null && drawMode == DrawMode.DRAWMODE_COMPACT) if (rcBuilderResult.GetCompactHeightfield() != null && drawMode == DrawMode.DRAWMODE_COMPACT)
{ {
_debugDraw.DebugDrawCompactHeightfieldSolid(rcBuilderResult.CompactHeightfield); _debugDraw.DebugDrawCompactHeightfieldSolid(rcBuilderResult.GetCompactHeightfield());
} }
if (rcBuilderResult.CompactHeightfield != null && drawMode == DrawMode.DRAWMODE_COMPACT_DISTANCE) if (rcBuilderResult.GetCompactHeightfield() != null && drawMode == DrawMode.DRAWMODE_COMPACT_DISTANCE)
{ {
_debugDraw.DebugDrawCompactHeightfieldDistance(rcBuilderResult.CompactHeightfield); _debugDraw.DebugDrawCompactHeightfieldDistance(rcBuilderResult.GetCompactHeightfield());
} }
if (rcBuilderResult.CompactHeightfield != null && drawMode == DrawMode.DRAWMODE_COMPACT_REGIONS) if (rcBuilderResult.GetCompactHeightfield() != null && drawMode == DrawMode.DRAWMODE_COMPACT_REGIONS)
{ {
_debugDraw.DebugDrawCompactHeightfieldRegions(rcBuilderResult.CompactHeightfield); _debugDraw.DebugDrawCompactHeightfieldRegions(rcBuilderResult.GetCompactHeightfield());
} }
if (rcBuilderResult.SolidHeightfiled != null && drawMode == DrawMode.DRAWMODE_VOXELS) if (rcBuilderResult.GetSolidHeightfield() != null && drawMode == DrawMode.DRAWMODE_VOXELS)
{ {
_debugDraw.Fog(true); _debugDraw.Fog(true);
_debugDraw.DebugDrawHeightfieldSolid(rcBuilderResult.SolidHeightfiled); _debugDraw.DebugDrawHeightfieldSolid(rcBuilderResult.GetSolidHeightfield());
_debugDraw.Fog(false); _debugDraw.Fog(false);
} }
if (rcBuilderResult.SolidHeightfiled != null && drawMode == DrawMode.DRAWMODE_VOXELS_WALKABLE) if (rcBuilderResult.GetSolidHeightfield() != null && drawMode == DrawMode.DRAWMODE_VOXELS_WALKABLE)
{ {
_debugDraw.Fog(true); _debugDraw.Fog(true);
_debugDraw.DebugDrawHeightfieldWalkable(rcBuilderResult.SolidHeightfiled); _debugDraw.DebugDrawHeightfieldWalkable(rcBuilderResult.GetSolidHeightfield());
_debugDraw.Fog(false); _debugDraw.Fog(false);
} }
if (rcBuilderResult.ContourSet != null && drawMode == DrawMode.DRAWMODE_RAW_CONTOURS) if (rcBuilderResult.GetContourSet() != null && drawMode == DrawMode.DRAWMODE_RAW_CONTOURS)
{ {
_debugDraw.DepthMask(false); _debugDraw.DepthMask(false);
_debugDraw.DebugDrawRawContours(rcBuilderResult.ContourSet, 1f); _debugDraw.DebugDrawRawContours(rcBuilderResult.GetContourSet(), 1f);
_debugDraw.DepthMask(true); _debugDraw.DepthMask(true);
} }
if (rcBuilderResult.ContourSet != null && drawMode == DrawMode.DRAWMODE_BOTH_CONTOURS) if (rcBuilderResult.GetContourSet() != null && drawMode == DrawMode.DRAWMODE_BOTH_CONTOURS)
{ {
_debugDraw.DepthMask(false); _debugDraw.DepthMask(false);
_debugDraw.DebugDrawRawContours(rcBuilderResult.ContourSet, 0.5f); _debugDraw.DebugDrawRawContours(rcBuilderResult.GetContourSet(), 0.5f);
_debugDraw.DebugDrawContours(rcBuilderResult.ContourSet); _debugDraw.DebugDrawContours(rcBuilderResult.GetContourSet());
_debugDraw.DepthMask(true); _debugDraw.DepthMask(true);
} }
if (rcBuilderResult.ContourSet != null && drawMode == DrawMode.DRAWMODE_CONTOURS) if (rcBuilderResult.GetContourSet() != null && drawMode == DrawMode.DRAWMODE_CONTOURS)
{ {
_debugDraw.DepthMask(false); _debugDraw.DepthMask(false);
_debugDraw.DebugDrawContours(rcBuilderResult.ContourSet); _debugDraw.DebugDrawContours(rcBuilderResult.GetContourSet());
_debugDraw.DepthMask(true); _debugDraw.DepthMask(true);
} }
if (rcBuilderResult.CompactHeightfield != null && drawMode == DrawMode.DRAWMODE_REGION_CONNECTIONS) if (rcBuilderResult.GetCompactHeightfield() != null && drawMode == DrawMode.DRAWMODE_REGION_CONNECTIONS)
{ {
_debugDraw.DebugDrawCompactHeightfieldRegions(rcBuilderResult.CompactHeightfield); _debugDraw.DebugDrawCompactHeightfieldRegions(rcBuilderResult.GetCompactHeightfield());
_debugDraw.DepthMask(false); _debugDraw.DepthMask(false);
if (rcBuilderResult.ContourSet != null) if (rcBuilderResult.GetContourSet() != null)
{ {
_debugDraw.DebugDrawRegionConnections(rcBuilderResult.ContourSet); _debugDraw.DebugDrawRegionConnections(rcBuilderResult.GetContourSet());
} }
_debugDraw.DepthMask(true); _debugDraw.DepthMask(true);
} }
if (rcBuilderResult.Mesh != null && drawMode == DrawMode.DRAWMODE_POLYMESH) if (rcBuilderResult.GetMesh() != null && drawMode == DrawMode.DRAWMODE_POLYMESH)
{ {
_debugDraw.DepthMask(false); _debugDraw.DepthMask(false);
_debugDraw.DebugDrawPolyMesh(rcBuilderResult.Mesh); _debugDraw.DebugDrawPolyMesh(rcBuilderResult.GetMesh());
_debugDraw.DepthMask(true); _debugDraw.DepthMask(true);
} }
if (rcBuilderResult.MeshDetail != null && drawMode == DrawMode.DRAWMODE_POLYMESH_DETAIL) if (rcBuilderResult.GetMeshDetail() != null && drawMode == DrawMode.DRAWMODE_POLYMESH_DETAIL)
{ {
_debugDraw.DepthMask(false); _debugDraw.DepthMask(false);
_debugDraw.DebugDrawPolyMeshDetail(rcBuilderResult.MeshDetail); _debugDraw.DebugDrawPolyMeshDetail(rcBuilderResult.GetMeshDetail());
_debugDraw.DepthMask(true); _debugDraw.DepthMask(true);
} }
} }

View File

@ -1,7 +1,7 @@
/* /*
Copyright (c) 2009-2010 Mikko Mononen memon@inside.org Copyright (c) 2009-2010 Mikko Mononen memon@inside.org
recast4j copyright (c) 2015-2019 Piotr Piastucki piotr@jtilia.org recast4j copyright (c) 2015-2019 Piotr Piastucki piotr@jtilia.org
DotRecast Copyright (c) 2023-2024 Choi Ikpil ikpil@naver.com DotRecast Copyright (c) 2023 Choi Ikpil ikpil@naver.com
This software is provided 'as-is', without any express or implied This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages warranty. In no event will the authors be held liable for any damages
@ -19,6 +19,7 @@ freely, subject to the following restrictions:
*/ */
using System; using System;
using System.Collections.Generic;
using DotRecast.Core.Numerics; using DotRecast.Core.Numerics;
using DotRecast.Detour; using DotRecast.Detour;
using DotRecast.Recast.Toolset.Builder; using DotRecast.Recast.Toolset.Builder;
@ -26,13 +27,11 @@ using Silk.NET.OpenGL;
namespace DotRecast.Recast.Demo.Draw; namespace DotRecast.Recast.Demo.Draw;
using static DtDetour;
public class RecastDebugDraw : DebugDraw public class RecastDebugDraw : DebugDraw
{ {
public const int DU_DRAWNAVMESH_OFFMESHCONS = 0x01; public static readonly int DRAWNAVMESH_OFFMESHCONS = 0x01;
public const int DU_DRAWNAVMESH_CLOSEDLIST = 0x02; public static readonly int DRAWNAVMESH_CLOSEDLIST = 0x02;
public const int DU_DRAWNAVMESH_COLOR_TILES = 0x04; public static readonly int DRAWNAVMESH_COLOR_TILES = 0x04;
public RecastDebugDraw(GL gl) : base(gl) public RecastDebugDraw(GL gl) : base(gl)
{ {
@ -102,7 +101,7 @@ public class RecastDebugDraw : DebugDraw
public void DebugDrawNavMeshWithClosedList(DtNavMesh mesh, DtNavMeshQuery query, int flags) public void DebugDrawNavMeshWithClosedList(DtNavMesh mesh, DtNavMeshQuery query, int flags)
{ {
DtNavMeshQuery q = (flags & DU_DRAWNAVMESH_CLOSEDLIST) != 0 ? query : null; DtNavMeshQuery q = (flags & DRAWNAVMESH_CLOSEDLIST) != 0 ? query : null;
for (int i = 0; i < mesh.GetMaxTiles(); ++i) for (int i = 0; i < mesh.GetMaxTiles(); ++i)
{ {
DtMeshTile tile = mesh.GetTile(i); DtMeshTile tile = mesh.GetTile(i);
@ -117,7 +116,7 @@ public class RecastDebugDraw : DebugDraw
{ {
long @base = mesh.GetPolyRefBase(tile); long @base = mesh.GetPolyRefBase(tile);
int tileNum = DecodePolyIdTile(@base); int tileNum = DtNavMesh.DecodePolyIdTile(@base);
int tileColor = DuIntToCol(tileNum, 128); int tileColor = DuIntToCol(tileNum, 128);
DepthMask(false); DepthMask(false);
Begin(DebugDrawPrimitives.TRIS); Begin(DebugDrawPrimitives.TRIS);
@ -136,7 +135,7 @@ public class RecastDebugDraw : DebugDraw
} }
else else
{ {
if ((flags & DU_DRAWNAVMESH_COLOR_TILES) != 0) if ((flags & DRAWNAVMESH_COLOR_TILES) != 0)
{ {
col = tileColor; col = tileColor;
} }
@ -164,7 +163,7 @@ public class RecastDebugDraw : DebugDraw
// Draw outer poly boundaries // Draw outer poly boundaries
DrawPolyBoundaries(tile, DuRGBA(0, 48, 64, 220), 2.5f, false); DrawPolyBoundaries(tile, DuRGBA(0, 48, 64, 220), 2.5f, false);
if ((flags & DU_DRAWNAVMESH_OFFMESHCONS) != 0) if ((flags & DRAWNAVMESH_OFFMESHCONS) != 0)
{ {
Begin(DebugDrawPrimitives.LINES, 2.0f); Begin(DebugDrawPrimitives.LINES, 2.0f);
for (int i = 0; i < tile.data.header.polyCount; ++i) for (int i = 0; i < tile.data.header.polyCount; ++i)
@ -199,7 +198,7 @@ public class RecastDebugDraw : DebugDraw
// Check to see if start and end end-points have links. // Check to see if start and end end-points have links.
bool startSet = false; bool startSet = false;
bool endSet = false; bool endSet = false;
for (int k = p.firstLink; k != DT_NULL_LINK; k = tile.links[k].next) for (int k = tile.polyLinks[p.index]; k != DtNavMesh.DT_NULL_LINK; k = tile.links[k].next)
{ {
if (tile.links[k].edge == 0) if (tile.links[k].edge == 0)
{ {
@ -214,27 +213,25 @@ public class RecastDebugDraw : DebugDraw
// End points and their on-mesh locations. // End points and their on-mesh locations.
Vertex(va.X, va.Y, va.Z, col); Vertex(va.X, va.Y, va.Z, col);
Vertex(con.pos[0], col); Vertex(con.pos[0], con.pos[1], con.pos[2], col);
col2 = startSet ? col : DuRGBA(220, 32, 16, 196); col2 = startSet ? col : DuRGBA(220, 32, 16, 196);
AppendCircle(con.pos[0].X, con.pos[0].Y + 0.1f, con.pos[0].Z, con.rad, col2); AppendCircle(con.pos[0], con.pos[1] + 0.1f, con.pos[2], con.rad, col2);
Vertex(vb.X, vb.Y, vb.Z, col); Vertex(vb.X, vb.Y, vb.Z, col);
Vertex(con.pos[1], col); Vertex(con.pos[3], con.pos[4], con.pos[5], col);
col2 = endSet ? col : DuRGBA(220, 32, 16, 196); col2 = endSet ? col : DuRGBA(220, 32, 16, 196);
AppendCircle(con.pos[1].X, con.pos[1].Y + 0.1f, con.pos[1].Z, con.rad, col2); AppendCircle(con.pos[3], con.pos[4] + 0.1f, con.pos[5], con.rad, col2);
// End point vertices. // End point vertices.
Vertex(con.pos[0], DuRGBA(0, 48, 64, 196)); Vertex(con.pos[0], con.pos[1], con.pos[2], 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[0], con.pos[1] + 0.2f, con.pos[2], DuRGBA(0, 48, 64, 196));
Vertex(con.pos[1], DuRGBA(0, 48, 64, 196)); Vertex(con.pos[3], con.pos[4], con.pos[5], 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)); Vertex(con.pos[3], con.pos[4] + 0.2f, con.pos[5], DuRGBA(0, 48, 64, 196));
// Connection arc. // Connection arc.
AppendArc( AppendArc(con.pos[0], con.pos[1], con.pos[2], con.pos[3], con.pos[4], con.pos[5], 0.25f,
con.pos[0].X, con.pos[0].Y, con.pos[0].Z, (con.flags & 1) != 0 ? 0.6f : 0, 0.6f, col);
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(); End();
@ -258,7 +255,9 @@ public class RecastDebugDraw : DebugDraw
DtPoly p = tile.data.polys[index]; DtPoly p = tile.data.polys[index];
if (tile.data.detailMeshes != null) if (tile.data.detailMeshes != null)
{ {
ref DtPolyDetail pd = ref tile.data.detailMeshes[index]; DtPolyDetail pd = tile.data.detailMeshes[index];
if (pd != null)
{
for (int j = 0; j < pd.triCount; ++j) for (int j = 0; j < pd.triCount; ++j)
{ {
int t = (pd.triBase + j) * 4; int t = (pd.triBase + j) * 4;
@ -279,6 +278,7 @@ public class RecastDebugDraw : DebugDraw
} }
} }
} }
}
else else
{ {
for (int j = 1; j < p.vertCount - 1; ++j) for (int j = 1; j < p.vertCount - 1; ++j)
@ -300,7 +300,6 @@ public class RecastDebugDraw : DebugDraw
Begin(DebugDrawPrimitives.LINES, linew); Begin(DebugDrawPrimitives.LINES, linew);
Span<RcVec3f> tv = stackalloc RcVec3f[3];
for (int i = 0; i < tile.data.header.polyCount; ++i) for (int i = 0; i < tile.data.header.polyCount; ++i)
{ {
DtPoly p = tile.data.polys[i]; DtPoly p = tile.data.polys[i];
@ -320,10 +319,10 @@ public class RecastDebugDraw : DebugDraw
continue; continue;
} }
if ((p.neis[j] & DT_EXT_LINK) != 0) if ((p.neis[j] & DtNavMesh.DT_EXT_LINK) != 0)
{ {
bool con = false; bool con = false;
for (int k = p.firstLink; k != DT_NULL_LINK; k = tile.links[k].next) for (int k = tile.polyLinks[p.index]; k != DtNavMesh.DT_NULL_LINK; k = tile.links[k].next)
{ {
if (tile.links[k].edge == j) if (tile.links[k].edge == j)
{ {
@ -368,18 +367,18 @@ public class RecastDebugDraw : DebugDraw
// This is really slow. // This is really slow.
if (tile.data.detailMeshes != null) if (tile.data.detailMeshes != null)
{ {
ref DtPolyDetail pd = ref tile.data.detailMeshes[i]; DtPolyDetail pd = tile.data.detailMeshes[i];
for (int k = 0; k < pd.triCount; ++k) for (int k = 0; k < pd.triCount; ++k)
{ {
int t = (pd.triBase + k) * 4; int t = (pd.triBase + k) * 4;
RcVec3f[] tv = new RcVec3f[3];
for (int m = 0; m < 3; ++m) for (int m = 0; m < 3; ++m)
{ {
int v = tile.data.detailTris[t + m]; int v = tile.data.detailTris[t + m];
if (v < p.vertCount) if (v < p.vertCount)
{ {
tv[m] = new RcVec3f( tv[m] = new RcVec3f(
tile.data.verts[p.verts[v] * 3], tile.data.verts[p.verts[v] * 3], tile.data.verts[p.verts[v] * 3 + 1],
tile.data.verts[p.verts[v] * 3 + 1],
tile.data.verts[p.verts[v] * 3 + 2] tile.data.verts[p.verts[v] * 3 + 2]
); );
} }
@ -395,7 +394,7 @@ public class RecastDebugDraw : DebugDraw
for (int m = 0, n = 2; m < 3; n = m++) for (int m = 0, n = 2; m < 3; n = m++)
{ {
if ((GetDetailTriEdgeFlags(tile.data.detailTris[t + 3], n) & DtDetailTriEdgeFlags.DT_DETAIL_EDGE_BOUNDARY) == 0) if ((DtNavMesh.GetDetailTriEdgeFlags(tile.data.detailTris[t + 3], n) & DtDetailTriEdgeFlags.DT_DETAIL_EDGE_BOUNDARY) == 0)
continue; continue;
if (((tile.data.detailTris[t + 3] >> (n * 2)) & 0x3) == 0) if (((tile.data.detailTris[t + 3] >> (n * 2)) & 0x3) == 0)
@ -465,13 +464,9 @@ public class RecastDebugDraw : DebugDraw
continue; continue;
} }
AppendBoxWire( AppendBoxWire(tile.data.header.bmin.X + n.bmin[0] * cs, tile.data.header.bmin.Y + n.bmin[1] * cs,
tile.data.header.bmin.X + n.bmin.X * cs, tile.data.header.bmin.Z + n.bmin[2] * cs, tile.data.header.bmin.X + n.bmax[0] * cs,
tile.data.header.bmin.Y + n.bmin.Y * cs, tile.data.header.bmin.Y + n.bmax[1] * cs, tile.data.header.bmin.Z + n.bmax[2] * cs,
tile.data.header.bmin.Z + n.bmin.Z * cs,
tile.data.header.bmin.X + n.bmax.X * cs,
tile.data.header.bmin.Y + n.bmax.Y * cs,
tile.data.header.bmin.Z + n.bmax.Z * cs,
DuRGBA(255, 255, 255, 128)); DuRGBA(255, 255, 255, 128));
} }
@ -491,11 +486,11 @@ public class RecastDebugDraw : DebugDraw
{ {
float fx = chf.bmin.X + x * cs; float fx = chf.bmin.X + x * cs;
float fz = chf.bmin.Z + y * cs; float fz = chf.bmin.Z + y * cs;
ref RcCompactCell c = ref chf.cells[x + y * chf.width]; RcCompactCell c = chf.cells[x + y * chf.width];
for (int i = c.index, ni = c.index + c.count; i < ni; ++i) for (int i = c.index, ni = c.index + c.count; i < ni; ++i)
{ {
ref RcCompactSpan s = ref chf.spans[i]; RcCompactSpan s = chf.spans[i];
int area = chf.areas[i]; int area = chf.areas[i];
int color; int color;
@ -503,7 +498,7 @@ public class RecastDebugDraw : DebugDraw
{ {
color = DuRGBA(0, 192, 255, 64); color = DuRGBA(0, 192, 255, 64);
} }
else if (area == RcRecast.RC_NULL_AREA) else if (area == RcConstants.RC_NULL_AREA)
{ {
color = DuRGBA(0, 0, 0, 64); color = DuRGBA(0, 0, 0, 64);
} }
@ -675,7 +670,7 @@ public class RecastDebugDraw : DebugDraw
int v3 = c.rverts[j * 4 + 3]; int v3 = c.rverts[j * 4 + 3];
float off = 0; float off = 0;
int colv = color; int colv = color;
if ((v3 & RcRecast.RC_BORDER_VERTEX) != 0) if ((v3 & RcConstants.RC_BORDER_VERTEX) != 0)
{ {
colv = DuRGBA(255, 255, 255, a); colv = DuRGBA(255, 255, 255, a);
off = ch * 2; off = ch * 2;
@ -722,7 +717,7 @@ public class RecastDebugDraw : DebugDraw
int vb0 = c.verts[j * 4]; int vb0 = c.verts[j * 4];
int vb1 = c.verts[j * 4 + 1]; int vb1 = c.verts[j * 4 + 1];
int vb2 = c.verts[j * 4 + 2]; int vb2 = c.verts[j * 4 + 2];
int col = (va3 & RcRecast.RC_AREA_BORDER) != 0 ? bcolor : color; int col = (va3 & RcConstants.RC_AREA_BORDER) != 0 ? bcolor : color;
float fx = orig.X + va0 * cs; float fx = orig.X + va0 * cs;
float fy = orig.Y + (va1 + 1 + (i & 1)) * ch; float fy = orig.Y + (va1 + 1 + (i & 1)) * ch;
@ -753,7 +748,7 @@ public class RecastDebugDraw : DebugDraw
int v3 = c.verts[j * 4 + 3]; int v3 = c.verts[j * 4 + 3];
float off = 0; float off = 0;
int colv = color; int colv = color;
if ((v3 & RcRecast.RC_BORDER_VERTEX) != 0) if ((v3 & RcConstants.RC_BORDER_VERTEX) != 0)
{ {
colv = DuRGBA(255, 255, 255, a); colv = DuRGBA(255, 255, 255, a);
off = ch * 2; off = ch * 2;
@ -833,7 +828,7 @@ public class RecastDebugDraw : DebugDraw
{ {
fcol[0] = DuRGBA(64, 128, 160, 255); fcol[0] = DuRGBA(64, 128, 160, 255);
} }
else if (s.area == RcRecast.RC_NULL_AREA) else if (s.area == RcConstants.RC_NULL_AREA)
{ {
fcol[0] = DuRGBA(64, 64, 64, 255); fcol[0] = DuRGBA(64, 64, 64, 255);
} }
@ -864,11 +859,11 @@ public class RecastDebugDraw : DebugDraw
{ {
float fx = chf.bmin.X + x * cs; float fx = chf.bmin.X + x * cs;
float fz = chf.bmin.Z + y * cs; float fz = chf.bmin.Z + y * cs;
ref RcCompactCell c = ref chf.cells[x + y * chf.width]; RcCompactCell c = chf.cells[x + y * chf.width];
for (int i = c.index, ni = c.index + c.count; i < ni; ++i) for (int i = c.index, ni = c.index + c.count; i < ni; ++i)
{ {
ref RcCompactSpan s = ref chf.spans[i]; RcCompactSpan s = chf.spans[i];
float fy = chf.bmin.Y + (s.y) * ch; float fy = chf.bmin.Y + (s.y) * ch;
int color; int color;
if (s.reg != 0) if (s.reg != 0)
@ -917,11 +912,11 @@ public class RecastDebugDraw : DebugDraw
{ {
float fx = chf.bmin.X + x * cs; float fx = chf.bmin.X + x * cs;
float fz = chf.bmin.Z + y * cs; float fz = chf.bmin.Z + y * cs;
ref RcCompactCell c = ref chf.cells[x + y * chf.width]; RcCompactCell c = chf.cells[x + y * chf.width];
for (int i = c.index, ni = c.index + c.count; i < ni; ++i) for (int i = c.index, ni = c.index + c.count; i < ni; ++i)
{ {
ref RcCompactSpan s = ref chf.spans[i]; RcCompactSpan s = chf.spans[i];
float fy = chf.bmin.Y + (s.y + 1) * ch; float fy = chf.bmin.Y + (s.y + 1) * ch;
char cd = (char)(chf.dist[i] * dscale); char cd = (char)(chf.dist[i] * dscale);
int color = DuRGBA(cd, cd, cd, 255); int color = DuRGBA(cd, cd, cd, 255);
@ -955,7 +950,7 @@ public class RecastDebugDraw : DebugDraw
{ {
color = DuRGBA(0, 192, 255, 64); color = DuRGBA(0, 192, 255, 64);
} }
else if (area == RcRecast.RC_NULL_AREA) else if (area == RcConstants.RC_NULL_AREA)
{ {
color = DuRGBA(0, 0, 0, 64); color = DuRGBA(0, 0, 0, 64);
} }
@ -967,7 +962,7 @@ public class RecastDebugDraw : DebugDraw
int[] vi = new int[3]; int[] vi = new int[3];
for (int j = 2; j < nvp; ++j) for (int j = 2; j < nvp; ++j)
{ {
if (mesh.polys[p + j] == RcRecast.RC_MESH_NULL_IDX) if (mesh.polys[p + j] == RcConstants.RC_MESH_NULL_IDX)
{ {
break; break;
} }
@ -998,7 +993,7 @@ public class RecastDebugDraw : DebugDraw
int p = i * nvp * 2; int p = i * nvp * 2;
for (int j = 0; j < nvp; ++j) for (int j = 0; j < nvp; ++j)
{ {
if (mesh.polys[p + j] == RcRecast.RC_MESH_NULL_IDX) if (mesh.polys[p + j] == RcConstants.RC_MESH_NULL_IDX)
{ {
break; break;
} }
@ -1008,7 +1003,7 @@ public class RecastDebugDraw : DebugDraw
continue; continue;
} }
int nj = (j + 1 >= nvp || mesh.polys[p + j + 1] == RcRecast.RC_MESH_NULL_IDX) ? 0 : j + 1; int nj = (j + 1 >= nvp || mesh.polys[p + j + 1] == RcConstants.RC_MESH_NULL_IDX) ? 0 : j + 1;
int[] vi = { mesh.polys[p + j], mesh.polys[p + nj] }; int[] vi = { mesh.polys[p + j], mesh.polys[p + nj] };
for (int k = 0; k < 2; ++k) for (int k = 0; k < 2; ++k)
@ -1032,7 +1027,7 @@ public class RecastDebugDraw : DebugDraw
int p = i * nvp * 2; int p = i * nvp * 2;
for (int j = 0; j < nvp; ++j) for (int j = 0; j < nvp; ++j)
{ {
if (mesh.polys[p + j] == RcRecast.RC_MESH_NULL_IDX) if (mesh.polys[p + j] == RcConstants.RC_MESH_NULL_IDX)
{ {
break; break;
} }
@ -1042,7 +1037,7 @@ public class RecastDebugDraw : DebugDraw
continue; continue;
} }
int nj = (j + 1 >= nvp || mesh.polys[p + j + 1] == RcRecast.RC_MESH_NULL_IDX) ? 0 : j + 1; int nj = (j + 1 >= nvp || mesh.polys[p + j + 1] == RcConstants.RC_MESH_NULL_IDX) ? 0 : j + 1;
int[] vi = { mesh.polys[p + j], mesh.polys[p + nj] }; int[] vi = { mesh.polys[p + j], mesh.polys[p + nj] };
int col = colb; int col = colb;
@ -1206,7 +1201,9 @@ public class RecastDebugDraw : DebugDraw
float off = 0.5f; float off = 0.5f;
Begin(DebugDrawPrimitives.POINTS, 4.0f); Begin(DebugDrawPrimitives.POINTS, 4.0f);
foreach (DtNode node in pool.AsEnumerable()) foreach (List<DtNode> nodes in pool.GetNodeMap().Values)
{
foreach (DtNode node in nodes)
{ {
if (node == null) if (node == null)
{ {
@ -1215,11 +1212,14 @@ public class RecastDebugDraw : DebugDraw
Vertex(node.pos.X, node.pos.Y + off, node.pos.Z, DuRGBA(255, 192, 0, 255)); Vertex(node.pos.X, node.pos.Y + off, node.pos.Z, DuRGBA(255, 192, 0, 255));
} }
}
End(); End();
Begin(DebugDrawPrimitives.LINES, 2.0f); Begin(DebugDrawPrimitives.LINES, 2.0f);
foreach (DtNode node in pool.AsEnumerable()) foreach (List<DtNode> nodes in pool.GetNodeMap().Values)
{
foreach (DtNode node in nodes)
{ {
if (node == null) if (node == null)
{ {
@ -1240,6 +1240,7 @@ public class RecastDebugDraw : DebugDraw
Vertex(node.pos.X, node.pos.Y + off, node.pos.Z, DuRGBA(255, 192, 0, 128)); 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)); Vertex(parent.pos.X, parent.pos.Y + off, parent.pos.Z, DuRGBA(255, 192, 0, 128));
} }
}
End(); End();
} }
@ -1295,10 +1296,8 @@ public class RecastDebugDraw : DebugDraw
Begin(DebugDrawPrimitives.LINES, 2.0f); Begin(DebugDrawPrimitives.LINES, 2.0f);
// Connection arc. // Connection arc.
AppendArc( AppendArc(con.pos[0], con.pos[1], con.pos[2], con.pos[3], con.pos[4], con.pos[5], 0.25f,
con.pos[0].X, con.pos[0].Y, con.pos[0].Z, (con.flags & 1) != 0 ? 0.6f : 0.0f, 0.6f, c);
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(); End();
} }
@ -1333,7 +1332,7 @@ public class RecastDebugDraw : DebugDraw
for (int side = 0; side < 8; ++side) for (int side = 0; side < 8; ++side)
{ {
int m = DT_EXT_LINK | (short)side; int m = DtNavMesh.DT_EXT_LINK | (short)side;
for (int i = 0; i < tile.data.header.polyCount; ++i) for (int i = 0; i < tile.data.header.polyCount; ++i)
{ {

View File

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

View File

@ -1,4 +1,4 @@
namespace DotRecast.Recast.Demo; namespace DotRecast.Recast.Demo;
public static class KeyModState public static class KeyModState
{ {

View File

@ -1,4 +1,4 @@
namespace DotRecast.Recast.Demo.Messages; namespace DotRecast.Recast.Demo.Messages;
public class GeomLoadBeganEvent : IRecastDemoMessage public class GeomLoadBeganEvent : IRecastDemoMessage
{ {

View File

@ -1,4 +1,4 @@
namespace DotRecast.Recast.Demo.Messages; namespace DotRecast.Recast.Demo.Messages;
public interface IRecastDemoChannel public interface IRecastDemoChannel
{ {

View File

@ -1,4 +1,4 @@
namespace DotRecast.Recast.Demo.Messages; namespace DotRecast.Recast.Demo.Messages;
public class IRecastDemoMessage public class IRecastDemoMessage
{ {

View File

@ -1,4 +1,4 @@
namespace DotRecast.Recast.Demo.Messages; namespace DotRecast.Recast.Demo.Messages;
public class NavMeshBuildBeganEvent : IRecastDemoMessage public class NavMeshBuildBeganEvent : IRecastDemoMessage
{ {

View File

@ -1,4 +1,4 @@
namespace DotRecast.Recast.Demo.Messages; namespace DotRecast.Recast.Demo.Messages;
public class NavMeshLoadBeganEvent : IRecastDemoMessage public class NavMeshLoadBeganEvent : IRecastDemoMessage
{ {

View File

@ -1,4 +1,4 @@
namespace DotRecast.Recast.Demo.Messages; namespace DotRecast.Recast.Demo.Messages;
public class NavMeshSaveBeganEvent : IRecastDemoMessage public class NavMeshSaveBeganEvent : IRecastDemoMessage
{ {

View File

@ -1,4 +1,4 @@
using DotRecast.Core.Numerics; using DotRecast.Core.Numerics;
namespace DotRecast.Recast.Demo.Messages; namespace DotRecast.Recast.Demo.Messages;

View File

@ -1,8 +1,8 @@
using System.IO; using System.IO;
using System.Threading;
using DotRecast.Core; using DotRecast.Core;
using DotRecast.Recast.Demo.Logging.Sinks; using DotRecast.Recast.Demo.Logging.Sinks;
using Serilog; using Serilog;
using Serilog.Enrichers;
namespace DotRecast.Recast.Demo; namespace DotRecast.Recast.Demo;
@ -10,8 +10,6 @@ public static class Program
{ {
public static void Main(string[] args) public static void Main(string[] args)
{ {
Thread.CurrentThread.Name ??= "main";
InitializeWorkingDirectory(); InitializeWorkingDirectory();
InitializeLogger(); InitializeLogger();
StartDemo(); StartDemo();
@ -24,6 +22,7 @@ public static class Program
.MinimumLevel.Verbose() .MinimumLevel.Verbose()
.Enrich.WithThreadId() .Enrich.WithThreadId()
.Enrich.WithThreadName() .Enrich.WithThreadName()
.Enrich.WithProperty(ThreadNameEnricher.ThreadNamePropertyName, "main")
.WriteTo.Async(c => c.LogMessageBroker(outputTemplate: format)) .WriteTo.Async(c => c.LogMessageBroker(outputTemplate: format))
.WriteTo.Async(c => c.Console(outputTemplate: format)) .WriteTo.Async(c => c.Console(outputTemplate: format))
.WriteTo.Async(c => c.File( .WriteTo.Async(c => c.File(

View File

@ -1,7 +1,7 @@
/* /*
Copyright (c) 2009-2010 Mikko Mononen memon@inside.org Copyright (c) 2009-2010 Mikko Mononen memon@inside.org
recast4j copyright (c) 2015-2019 Piotr Piastucki piotr@jtilia.org recast4j copyright (c) 2015-2019 Piotr Piastucki piotr@jtilia.org
DotRecast Copyright (c) 2023-2024 Choi Ikpil ikpil@naver.com DotRecast Copyright (c) 2023 Choi Ikpil ikpil@naver.com
This software is provided 'as-is', without any express or implied This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages warranty. In no event will the authors be held liable for any damages
@ -25,8 +25,6 @@ using System.Globalization;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Numerics; using System.Numerics;
using System.Runtime;
using System.Runtime.InteropServices;
using DotRecast.Core; using DotRecast.Core;
using Serilog; using Serilog;
using Silk.NET.Input; using Silk.NET.Input;
@ -45,6 +43,7 @@ using DotRecast.Recast.Demo.Messages;
using DotRecast.Recast.Toolset.Geom; using DotRecast.Recast.Toolset.Geom;
using DotRecast.Recast.Demo.Tools; using DotRecast.Recast.Demo.Tools;
using DotRecast.Recast.Demo.UI; using DotRecast.Recast.Demo.UI;
using DotRecast.Recast.Toolset;
using MouseButton = Silk.NET.Input.MouseButton; using MouseButton = Silk.NET.Input.MouseButton;
using Window = Silk.NET.Windowing.Window; using Window = Silk.NET.Windowing.Window;
@ -60,7 +59,6 @@ public class RecastDemo : IRecastDemoChannel
private ImGuiController _imgui; private ImGuiController _imgui;
private RcCanvas _canvas; private RcCanvas _canvas;
private Vector2D<int> _resolution;
private int width = 1000; private int width = 1000;
private int height = 900; private int height = 900;
@ -112,8 +110,7 @@ public class RecastDemo : IRecastDemoChannel
private bool markerPositionSet; private bool markerPositionSet;
private RcVec3f markerPosition = new RcVec3f(); private RcVec3f markerPosition = new RcVec3f();
private RcMenuView _menuView; private RcToolsetView toolset;
private RcToolsetView _toolsetView;
private RcSettingsView settingsView; private RcSettingsView settingsView;
private RcLogView logView; private RcLogView logView;
@ -253,17 +250,17 @@ public class RecastDemo : IRecastDemoChannel
private IWindow CreateWindow() private IWindow CreateWindow()
{ {
var monitor = Window.Platforms.First().GetMainMonitor(); var monitor = Window.Platforms.First().GetMainMonitor();
_resolution = monitor.VideoMode.Resolution.Value; var resolution = monitor.VideoMode.Resolution.Value;
float aspect = 16.0f / 9.0f; float aspect = 16.0f / 9.0f;
width = Math.Min(_resolution.X, (int)(_resolution.Y * aspect)) - 100; width = Math.Min(resolution.X, (int)(resolution.Y * aspect)) - 100;
height = _resolution.Y - 100; height = resolution.Y - 100;
viewport = new int[] { 0, 0, width, height }; viewport = new int[] { 0, 0, width, height };
var options = WindowOptions.Default; var options = WindowOptions.Default;
options.Title = title; options.Title = title;
options.Size = new Vector2D<int>(width, height); 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.VSync = true;
options.ShouldSwapAutomatically = false; options.ShouldSwapAutomatically = false;
options.PreferredDepthBufferBits = 24; options.PreferredDepthBufferBits = 24;
@ -320,8 +317,8 @@ public class RecastDemo : IRecastDemoChannel
if (null != mesh) if (null != mesh)
{ {
_sample.Update(_sample.GetInputGeom(), _sample.GetRecastConfig(), ImmutableArray<RcBuilderResult>.Empty, mesh); _sample.Update(_sample.GetInputGeom(), ImmutableArray<RcBuilderResult>.Empty, mesh);
_toolsetView.SetEnabled(true); toolset.SetEnabled(true);
} }
} }
catch (Exception e) catch (Exception e)
@ -367,26 +364,18 @@ public class RecastDemo : IRecastDemoChannel
dd.Init(camr); dd.Init(camr);
var scale = (float)_resolution.X / 1920;
int fontSize = Math.Max(10, (int)(16 * scale));
// for windows : Microsoft Visual C++ Redistributable Package // for windows : Microsoft Visual C++ Redistributable Package
// link - https://learn.microsoft.com/en-us/cpp/windows/latest-supported-vc-redist // link - https://learn.microsoft.com/en-us/cpp/windows/latest-supported-vc-redist
var imGuiFontConfig = new ImGuiFontConfig(Path.Combine("resources\\fonts", "DroidSans.ttf"), fontSize, null); ImGuiFontConfig imGuiFontConfig = new(Path.Combine("resources\\fonts", "DroidSans.ttf"), 16, null);
_imgui = new ImGuiController(_gl, window, _input, imGuiFontConfig); _imgui = new ImGuiController(_gl, window, _input, imGuiFontConfig);
ImGui.GetStyle().ScaleAllSizes(scale);
//ImGui.GetIO().FontGlobalScale = 2.0f;
DemoInputGeomProvider geom = LoadInputMesh("nav_test.obj"); DemoInputGeomProvider geom = LoadInputMesh("nav_test.obj");
_sample = new DemoSample(geom, ImmutableArray<RcBuilderResult>.Empty, null); _sample = new DemoSample(geom, ImmutableArray<RcBuilderResult>.Empty, null);
_menuView = new RcMenuView();
settingsView = new RcSettingsView(this); settingsView = new RcSettingsView(this);
settingsView.SetSample(_sample); settingsView.SetSample(_sample);
_toolsetView = new RcToolsetView( toolset = new RcToolsetView(
new TestNavmeshSampleTool(), new TestNavmeshSampleTool(),
new TileSampleTool(), new TileSampleTool(),
new ObstacleSampleTool(), new ObstacleSampleTool(),
@ -397,10 +386,10 @@ public class RecastDemo : IRecastDemoChannel
new JumpLinkBuilderSampleTool(), new JumpLinkBuilderSampleTool(),
new DynamicUpdateSampleTool() new DynamicUpdateSampleTool()
); );
_toolsetView.SetEnabled(true); toolset.SetEnabled(true);
logView = new RcLogView(); logView = new RcLogView();
_canvas = new RcCanvas(window, _menuView, settingsView, _toolsetView, logView); _canvas = new RcCanvas(window, settingsView, toolset, logView);
var vendor = _gl.GetStringS(GLEnum.Vendor); var vendor = _gl.GetStringS(GLEnum.Vendor);
var version = _gl.GetStringS(GLEnum.Version); var version = _gl.GetStringS(GLEnum.Version);
@ -411,24 +400,12 @@ public class RecastDemo : IRecastDemoChannel
var workingDirectory = Directory.GetCurrentDirectory(); var workingDirectory = Directory.GetCurrentDirectory();
Logger.Information($"Working directory - {workingDirectory}"); Logger.Information($"Working directory - {workingDirectory}");
Logger.Information($"OS Version - {Environment.OSVersion} {bitness}"); Logger.Information($"ImGui.Net version - {ImGui.GetVersion()}");
Logger.Information($"{RuntimeInformation.OSArchitecture} {RuntimeInformation.OSDescription}");
Logger.Information($"{RuntimeInformation.ProcessArchitecture} {RuntimeInformation.FrameworkDescription}");
Logger.Information($"Dotnet - {Environment.Version.ToString()} culture({currentCulture.Name})"); Logger.Information($"Dotnet - {Environment.Version.ToString()} culture({currentCulture.Name})");
Logger.Information($"Processor Count : {Environment.ProcessorCount}"); Logger.Information($"OS Version - {Environment.OSVersion} {bitness}");
Logger.Information($"Server garbage collection : {(GCSettings.IsServerGC ? "Enabled" : "Disabled")}");
Logger.Information($"Current latency mode for garbage collection: {GCSettings.LatencyMode}");
Logger.Information("");
Logger.Information($"ImGui.Net - version({ImGui.GetVersion()}) UI scale({scale}) fontSize({fontSize})");
Logger.Information($"{vendor} {rendererGl}"); Logger.Information($"{vendor} {rendererGl}");
Logger.Information($"gl version({version}) lang version({glslString})"); Logger.Information($"gl version - {version}");
} Logger.Information($"gl 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) private void UpdateKeyboard(float dt)
@ -438,17 +415,17 @@ public class RecastDemo : IRecastDemoChannel
// keyboard input // keyboard input
foreach (var keyboard in _input.Keyboards) foreach (var keyboard in _input.Keyboards)
{ {
var tempMoveFront = GetKeyValue(keyboard, Key.W, Key.Up); var tempMoveFront = keyboard.IsKeyPressed(Key.W) || keyboard.IsKeyPressed(Key.Up) ? 1.0f : -1f;
var tempMoveLeft = GetKeyValue(keyboard, Key.A, Key.Left); var tempMoveLeft = keyboard.IsKeyPressed(Key.A) || keyboard.IsKeyPressed(Key.Left) ? 1.0f : -1f;
var tempMoveBack = GetKeyValue(keyboard, Key.S, Key.Down); var tempMoveBack = keyboard.IsKeyPressed(Key.S) || keyboard.IsKeyPressed(Key.Down) ? 1.0f : -1f;
var tempMoveRight = GetKeyValue(keyboard, Key.D, Key.Right); var tempMoveRight = keyboard.IsKeyPressed(Key.D) || keyboard.IsKeyPressed(Key.Right) ? 1.0f : -1f;
var tempMoveUp = GetKeyValue(keyboard, Key.Q, Key.PageUp); var tempMoveUp = keyboard.IsKeyPressed(Key.Q) || keyboard.IsKeyPressed(Key.PageUp) ? 1.0f : -1f;
var tempMoveDown = GetKeyValue(keyboard, Key.E, Key.PageDown); var tempMoveDown = keyboard.IsKeyPressed(Key.E) || keyboard.IsKeyPressed(Key.PageDown) ? 1.0f : -1f;
var tempMoveAccel = GetKeyValue(keyboard, Key.ShiftLeft, Key.ShiftRight); var tempMoveAccel = keyboard.IsKeyPressed(Key.ShiftLeft) || keyboard.IsKeyPressed(Key.ShiftRight) ? 1.0f : -1f;
var tempControl = GetKeyValue(keyboard, Key.ControlLeft, Key.ControlRight); var tempControl = keyboard.IsKeyPressed(Key.ControlLeft) || keyboard.IsKeyPressed(Key.ControlRight);
_modState |= 0 < tempControl ? KeyModState.Control : KeyModState.None; _modState |= tempControl ? (int)KeyModState.Control : (int)KeyModState.None;
_modState |= 0 < tempMoveAccel ? KeyModState.Shift : KeyModState.None; _modState |= 0 < tempMoveAccel ? (int)KeyModState.Shift : (int)KeyModState.None;
//Logger.Information($"{_modState}"); //Logger.Information($"{_modState}");
_moveFront = Math.Clamp(_moveFront + tempMoveFront * dt * 4.0f, 0, 2.0f); _moveFront = Math.Clamp(_moveFront + tempMoveFront * dt * 4.0f, 0, 2.0f);
@ -472,7 +449,7 @@ public class RecastDemo : IRecastDemoChannel
var settings = _sample.GetSettings(); var settings = _sample.GetSettings();
RcVec3f bmin = _sample.GetInputGeom().GetMeshBoundsMin(); RcVec3f bmin = _sample.GetInputGeom().GetMeshBoundsMin();
RcVec3f bmax = _sample.GetInputGeom().GetMeshBoundsMax(); RcVec3f bmax = _sample.GetInputGeom().GetMeshBoundsMax();
RcRecast.CalcGridSize(bmin, bmax, settings.cellSize, out var gw, out var gh); RcCommons.CalcGridSize(bmin, bmax, settings.cellSize, out var gw, out var gh);
settingsView.SetVoxels(gw, gh); settingsView.SetVoxels(gw, gh);
settingsView.SetTiles(tileNavMeshBuilder.GetTiles(_sample.GetInputGeom(), settings.cellSize, settings.tileSize)); settingsView.SetTiles(tileNavMeshBuilder.GetTiles(_sample.GetInputGeom(), settings.cellSize, settings.tileSize));
settingsView.SetMaxTiles(tileNavMeshBuilder.GetMaxTiles(_sample.GetInputGeom(), settings.cellSize, settings.tileSize)); settingsView.SetMaxTiles(tileNavMeshBuilder.GetMaxTiles(_sample.GetInputGeom(), settings.cellSize, settings.tileSize));
@ -515,7 +492,7 @@ public class RecastDemo : IRecastDemoChannel
timeAcc -= DELTA_TIME; timeAcc -= DELTA_TIME;
if (simIter < 5 && _sample != null) if (simIter < 5 && _sample != null)
{ {
var tool = _toolsetView.GetTool(); var tool = toolset.GetTool();
if (null != tool) if (null != tool)
{ {
tool.HandleUpdate(DELTA_TIME); tool.HandleUpdate(DELTA_TIME);
@ -547,7 +524,6 @@ public class RecastDemo : IRecastDemoChannel
bool hasBound = false; bool hasBound = false;
RcVec3f bminN = RcVec3f.Zero; RcVec3f bminN = RcVec3f.Zero;
RcVec3f bmaxN = RcVec3f.Zero; RcVec3f bmaxN = RcVec3f.Zero;
if (_sample.GetInputGeom() != null) if (_sample.GetInputGeom() != null)
{ {
bminN = _sample.GetInputGeom().GetMeshBoundsMin(); bminN = _sample.GetInputGeom().GetMeshBoundsMin();
@ -563,7 +539,7 @@ public class RecastDemo : IRecastDemoChannel
{ {
foreach (RcBuilderResult result in _sample.GetRecastResults()) foreach (RcBuilderResult result in _sample.GetRecastResults())
{ {
if (result.CompactHeightfield != null) if (result.GetSolidHeightfield() != null)
{ {
if (!hasBound) if (!hasBound)
{ {
@ -572,15 +548,15 @@ public class RecastDemo : IRecastDemoChannel
} }
bminN = new RcVec3f( bminN = new RcVec3f(
Math.Min(bminN.X, result.CompactHeightfield.bmin.X), Math.Min(bminN.X, result.GetSolidHeightfield().bmin.X),
Math.Min(bminN.Y, result.CompactHeightfield.bmin.Y), Math.Min(bminN.Y, result.GetSolidHeightfield().bmin.Y),
Math.Min(bminN.Z, result.CompactHeightfield.bmin.Z) Math.Min(bminN.Z, result.GetSolidHeightfield().bmin.Z)
); );
bmaxN = new RcVec3f( bmaxN = new RcVec3f(
Math.Max(bmaxN.X, result.CompactHeightfield.bmax.X), Math.Max(bmaxN.X, result.GetSolidHeightfield().bmax.X),
Math.Max(bmaxN.Y, result.CompactHeightfield.bmax.Y), Math.Max(bmaxN.Y, result.GetSolidHeightfield().bmax.Y),
Math.Max(bmaxN.Z, result.CompactHeightfield.bmax.Z) Math.Max(bmaxN.Z, result.GetSolidHeightfield().bmax.Z)
); );
hasBound = true; hasBound = true;
@ -588,15 +564,12 @@ public class RecastDemo : IRecastDemoChannel
} }
} }
// Reset camera and fog to match the mesh bounds.
if (hasBound) if (hasBound)
{ {
RcVec3f bmin = bminN; RcVec3f bmin = bminN;
RcVec3f bmax = bmaxN; RcVec3f bmax = bmaxN;
camr = (float)(Math.Sqrt(RcMath.Sqr(bmax.X - bmin.X) + camr = (float)(Math.Sqrt(RcMath.Sqr(bmax.X - bmin.X) + RcMath.Sqr(bmax.Y - bmin.Y) + RcMath.Sqr(bmax.Z - bmin.Z)) / 2);
RcMath.Sqr(bmax.Y - bmin.Y) +
RcMath.Sqr(bmax.Z - bmin.Z)) / 2);
cameraPos.X = (bmax.X + bmin.X) / 2 + camr; cameraPos.X = (bmax.X + bmin.X) / 2 + camr;
cameraPos.Y = (bmax.Y + bmin.Y) / 2 + camr; cameraPos.Y = (bmax.Y + bmin.Y) / 2 + camr;
cameraPos.Z = (bmax.Z + bmin.Z) / 2 + camr; cameraPos.Z = (bmax.Z + bmin.Z) / 2 + camr;
@ -606,7 +579,7 @@ public class RecastDemo : IRecastDemoChannel
} }
_sample.SetChanged(false); _sample.SetChanged(false);
_toolsetView.SetSample(_sample); toolset.SetSample(_sample);
} }
if (_messages.TryDequeue(out var msg)) if (_messages.TryDequeue(out var msg))
@ -635,7 +608,7 @@ public class RecastDemo : IRecastDemoChannel
dd.Fog(camr * 0.1f, camr * 1.25f); dd.Fog(camr * 0.1f, camr * 1.25f);
renderer.Render(_sample, settingsView.GetDrawMode()); renderer.Render(_sample, settingsView.GetDrawMode());
ISampleTool sampleTool = _toolsetView.GetTool(); ISampleTool sampleTool = toolset.GetTool();
if (sampleTool != null) if (sampleTool != null)
{ {
sampleTool.HandleRender(renderer); sampleTool.HandleRender(renderer);
@ -684,7 +657,7 @@ public class RecastDemo : IRecastDemoChannel
{ {
var geom = LoadInputMesh(args.FilePath); var geom = LoadInputMesh(args.FilePath);
_sample.Update(geom, null, ImmutableArray<RcBuilderResult>.Empty, null); _sample.Update(geom, ImmutableArray<RcBuilderResult>.Empty, null);
} }
private void OnNavMeshBuildBegan(NavMeshBuildBeganEvent args) private void OnNavMeshBuildBegan(NavMeshBuildBeganEvent args)
@ -702,15 +675,14 @@ public class RecastDemo : IRecastDemoChannel
NavMeshBuildResult buildResult; NavMeshBuildResult buildResult;
var geom = _sample.GetInputGeom();
var settings = _sample.GetSettings(); var settings = _sample.GetSettings();
if (settings.tiled) if (settings.tiled)
{ {
buildResult = tileNavMeshBuilder.Build(geom, settings); buildResult = tileNavMeshBuilder.Build(_sample.GetInputGeom(), settings);
} }
else else
{ {
buildResult = soloNavMeshBuilder.Build(geom, settings); buildResult = soloNavMeshBuilder.Build(_sample.GetInputGeom(), settings);
} }
if (!buildResult.Success) if (!buildResult.Success)
@ -719,16 +691,16 @@ public class RecastDemo : IRecastDemoChannel
return; return;
} }
_sample.Update(_sample.GetInputGeom(), buildResult.Cfg, buildResult.RecastBuilderResults, buildResult.NavMesh); _sample.Update(_sample.GetInputGeom(), buildResult.RecastBuilderResults, buildResult.NavMesh);
_sample.SetChanged(false); _sample.SetChanged(false);
settingsView.SetBuildTime((RcFrequency.Ticks - t) / TimeSpan.TicksPerMillisecond); settingsView.SetBuildTime((RcFrequency.Ticks - t) / TimeSpan.TicksPerMillisecond);
//settingsUI.SetBuildTelemetry(buildResult.Item1.Select(x => x.GetTelemetry()).ToList()); //settingsUI.SetBuildTelemetry(buildResult.Item1.Select(x => x.GetTelemetry()).ToList());
_toolsetView.SetSample(_sample); toolset.SetSample(_sample);
Logger.Information($"build times"); Logger.Information($"build times");
Logger.Information($"-----------------------------------------"); Logger.Information($"-----------------------------------------");
var telemetries = buildResult.RecastBuilderResults var telemetries = buildResult.RecastBuilderResults
.Select(x => x.Context) .Select(x => x.GetTelemetry())
.SelectMany(x => x.ToList()) .SelectMany(x => x.ToList())
.GroupBy(x => x.Key) .GroupBy(x => x.Key)
.ToImmutableSortedDictionary(x => x.Key, x => x.Sum(y => y.Millis)); .ToImmutableSortedDictionary(x => x.Key, x => x.Sum(y => y.Millis));
@ -816,7 +788,7 @@ public class RecastDemo : IRecastDemoChannel
RcVec3f rayDir = new RcVec3f(rayEnd.X - rayStart.X, rayEnd.Y - rayStart.Y, rayEnd.Z - rayStart.Z); RcVec3f rayDir = new RcVec3f(rayEnd.X - rayStart.X, rayEnd.Y - rayStart.Y, rayEnd.Z - rayStart.Z);
rayDir = RcVec3f.Normalize(rayDir); rayDir = RcVec3f.Normalize(rayDir);
ISampleTool raySampleTool = _toolsetView.GetTool(); ISampleTool raySampleTool = toolset.GetTool();
if (raySampleTool != null) if (raySampleTool != null)
{ {

View File

@ -1,7 +1,7 @@
/* /*
Copyright (c) 2009-2010 Mikko Mononen memon@inside.org Copyright (c) 2009-2010 Mikko Mononen memon@inside.org
recast4j copyright (c) 2015-2019 Piotr Piastucki piotr@jtilia.org recast4j copyright (c) 2015-2019 Piotr Piastucki piotr@jtilia.org
DotRecast Copyright (c) 2023-2024 Choi Ikpil ikpil@naver.com DotRecast Copyright (c) 2023 Choi Ikpil ikpil@naver.com
This software is provided 'as-is', without any express or implied This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages warranty. In no event will the authors be held liable for any damages
@ -160,11 +160,15 @@ public class ConvexVolumeSampleTool : ISampleTool
var geom = _sample.GetInputGeom(); var geom = _sample.GetInputGeom();
if (shift) if (shift)
{ {
_tool.TryRemove(geom, p, out var volume); _tool.RemoveByPos(geom, p);
} }
else else
{ {
_tool.TryAdd(geom, p, _areaType, _boxDescent, _boxHeight, _polyOffset, out var volume); if (_tool.PlottingShape(p, out var pts, out var hull))
{
var vol = RcConvexVolumeTool.CreateConvexVolume(pts, hull, _areaType, _boxDescent, _boxHeight, _polyOffset);
_tool.Add(geom, vol);
}
} }
} }

View File

@ -1,6 +1,6 @@
/* /*
recast4j copyright (c) 2021 Piotr Piastucki piotr@jtilia.org recast4j copyright (c) 2021 Piotr Piastucki piotr@jtilia.org
DotRecast Copyright (c) 2023-2024 Choi Ikpil ikpil@naver.com DotRecast Copyright (c) 2023 Choi Ikpil ikpil@naver.com
This software is provided 'as-is', without any express or implied This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages warranty. In no event will the authors be held liable for any damages
@ -18,9 +18,12 @@ freely, subject to the following restrictions:
*/ */
using System; using System;
using System.Collections.Generic;
using System.Linq;
using DotRecast.Core.Numerics; using DotRecast.Core.Numerics;
using DotRecast.Detour; using DotRecast.Detour;
using DotRecast.Detour.Crowd; using DotRecast.Detour.Crowd;
using DotRecast.Recast.Toolset.Builder;
using DotRecast.Recast.Demo.Draw; using DotRecast.Recast.Demo.Draw;
using DotRecast.Recast.Toolset; using DotRecast.Recast.Toolset;
using DotRecast.Recast.Toolset.Tools; using DotRecast.Recast.Toolset.Tools;
@ -95,11 +98,6 @@ public class CrowdAgentProfilingSampleTool : ISampleTool
ImGui.SliderInt("Max Iterations", ref toolCfg.maxIterations, 0, 4000); ImGui.SliderInt("Max Iterations", ref toolCfg.maxIterations, 0, 4000);
ImGui.NewLine(); ImGui.NewLine();
ImGui.Text("Debug Draw");
ImGui.Separator();
ImGui.Checkbox("Show Agents", ref toolCfg.showAgents);
ImGui.NewLine();
if (ImGui.Button("Start Crowd Profiling")) if (ImGui.Button("Start Crowd Profiling"))
{ {
var settings = _sample.GetSettings(); var settings = _sample.GetSettings();
@ -120,11 +118,7 @@ public class CrowdAgentProfilingSampleTool : ISampleTool
ImGui.Text($"{rtt.Key}: {rtt.Micros} us"); ImGui.Text($"{rtt.Key}: {rtt.Micros} us");
} }
ImGui.Text($"Sampling Time: {_tool.GetCrowdUpdateSamplingTime():0.00} ms"); ImGui.Text($"Update Time: {_tool.GetCrowdUpdateTime()} ms");
ImGui.Text($"Current Update Time: {_tool.GetCrowdUpdateTime():0.00} ms");
ImGui.Text($"Avg Update Time: {_tool.GetCrowdUpdateAvgTime():0.00} ms");
ImGui.Text($"Max Update Time: {_tool.GetCrowdUpdateMaxTime():0.00} ms");
ImGui.Text($"Min Update Time: {_tool.GetCrowdUpdateMinTime():0.00} ms");
} }
} }
@ -134,7 +128,7 @@ public class CrowdAgentProfilingSampleTool : ISampleTool
dd.DepthMask(false); dd.DepthMask(false);
var crowd = _tool.GetCrowd(); var crowd = _tool.GetCrowd();
if (crowd != null && _tool.GetToolConfig().showAgents) if (crowd != null)
{ {
foreach (DtCrowdAgent ag in crowd.GetActiveAgents()) foreach (DtCrowdAgent ag in crowd.GetActiveAgents())
{ {

View File

@ -1,7 +1,7 @@
/* /*
Copyright (c) 2009-2010 Mikko Mononen memon@inside.org Copyright (c) 2009-2010 Mikko Mononen memon@inside.org
recast4j copyright (c) 2021 Piotr Piastucki piotr@jtilia.org recast4j copyright (c) 2021 Piotr Piastucki piotr@jtilia.org
DotRecast Copyright (c) 2023-2024 Choi Ikpil ikpil@naver.com DotRecast Copyright (c) 2023 Choi Ikpil ikpil@naver.com
This software is provided 'as-is', without any express or implied This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages warranty. In no event will the authors be held liable for any damages
@ -234,7 +234,7 @@ public class CrowdSampleTool : ISampleTool
dd.Vertex(prev.X, prev.Y + 0.1f, prev.Z, DuRGBA(0, 0, 0, (int)(128 * preva))); dd.Vertex(prev.X, prev.Y + 0.1f, prev.Z, DuRGBA(0, 0, 0, (int)(128 * preva)));
dd.Vertex(trail.trail[v], trail.trail[v + 1] + 0.1f, trail.trail[v + 2], DuRGBA(0, 0, 0, (int)(128 * a))); dd.Vertex(trail.trail[v], trail.trail[v + 1] + 0.1f, trail.trail[v + 2], DuRGBA(0, 0, 0, (int)(128 * a)));
preva = a; preva = a;
prev = RcVec.Create(trail.trail, v); prev = RcVecUtils.Create(trail.trail, v);
} }
dd.End(); dd.End();
@ -251,10 +251,10 @@ public class CrowdSampleTool : ISampleTool
if (_showCorners) if (_showCorners)
{ {
if (0 < ag.ncorners) if (0 < ag.corners.Count)
{ {
dd.Begin(LINES, 2.0f); dd.Begin(LINES, 2.0f);
for (int j = 0; j < ag.ncorners; ++j) for (int j = 0; j < ag.corners.Count; ++j)
{ {
RcVec3f va = j == 0 ? pos : ag.corners[j - 1].pos; RcVec3f va = j == 0 ? pos : ag.corners[j - 1].pos;
RcVec3f vb = ag.corners[j].pos; RcVec3f vb = ag.corners[j].pos;
@ -262,10 +262,10 @@ public class CrowdSampleTool : ISampleTool
dd.Vertex(vb.X, vb.Y + radius, vb.Z, DuRGBA(128, 0, 0, 192)); dd.Vertex(vb.X, vb.Y + radius, vb.Z, DuRGBA(128, 0, 0, 192));
} }
if ((ag.corners[ag.ncorners - 1].flags if ((ag.corners[ag.corners.Count - 1].flags
& DtStraightPathFlags.DT_STRAIGHTPATH_OFFMESH_CONNECTION) != 0) & DtStraightPathFlags.DT_STRAIGHTPATH_OFFMESH_CONNECTION) != 0)
{ {
RcVec3f v = ag.corners[ag.ncorners - 1].pos; RcVec3f v = ag.corners[ag.corners.Count - 1].pos;
dd.Vertex(v.X, v.Y, v.Z, DuRGBA(192, 0, 0, 192)); dd.Vertex(v.X, v.Y, v.Z, DuRGBA(192, 0, 0, 192));
dd.Vertex(v.X, v.Y + radius * 2, v.Z, DuRGBA(192, 0, 0, 192)); dd.Vertex(v.X, v.Y + radius * 2, v.Z, DuRGBA(192, 0, 0, 192));
} }
@ -325,7 +325,7 @@ public class CrowdSampleTool : ISampleTool
2.0f); 2.0f);
dd.Begin(LINES, 2.0f); dd.Begin(LINES, 2.0f);
for (int j = 0; j < ag.nneis; ++j) for (int j = 0; j < ag.neis.Count; ++j)
{ {
DtCrowdAgent nei = ag.neis[j].agent; DtCrowdAgent nei = ag.neis[j].agent;
if (nei != null) if (nei != null)

View File

@ -1,6 +1,6 @@
/* /*
recast4j copyright (c) 2021 Piotr Piastucki piotr@jtilia.org recast4j copyright (c) 2021 Piotr Piastucki piotr@jtilia.org
DotRecast Copyright (c) 2023-2024 Choi Ikpil ikpil@naver.com DotRecast Copyright (c) 2023 Choi Ikpil ikpil@naver.com
This software is provided 'as-is', without any express or implied This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages warranty. In no event will the authors be held liable for any damages
@ -25,7 +25,6 @@ using DotRecast.Core;
using DotRecast.Core.Collections; using DotRecast.Core.Collections;
using DotRecast.Core.Numerics; using DotRecast.Core.Numerics;
using DotRecast.Detour.Dynamic; using DotRecast.Detour.Dynamic;
using DotRecast.Detour.Dynamic.Io;
using DotRecast.Recast.Toolset; using DotRecast.Recast.Toolset;
using DotRecast.Recast.Toolset.Tools; using DotRecast.Recast.Toolset.Tools;
using DotRecast.Recast.Demo.Draw; using DotRecast.Recast.Demo.Draw;
@ -92,7 +91,7 @@ public class DynamicUpdateSampleTool : ISampleTool
var bridgeGeom = DemoInputGeomProvider.LoadFile("bridge.obj"); var bridgeGeom = DemoInputGeomProvider.LoadFile("bridge.obj");
var houseGeom = DemoInputGeomProvider.LoadFile("house.obj"); var houseGeom = DemoInputGeomProvider.LoadFile("house.obj");
var convexGeom = DemoInputGeomProvider.LoadFile("convex.obj"); var convexGeom = DemoInputGeomProvider.LoadFile("convex.obj");
_tool = new(new RcRand(Random.Shared), bridgeGeom, houseGeom, convexGeom); _tool = new(Random.Shared, bridgeGeom, houseGeom, convexGeom);
executor = Task.Factory; executor = Task.Factory;
} }
@ -117,17 +116,8 @@ public class DynamicUpdateSampleTool : ISampleTool
if (mode == RcDynamicUpdateToolMode.BUILD) if (mode == RcDynamicUpdateToolMode.BUILD)
{ {
const string loadVoxelPopupStrId = "Load Voxels Popup"; var loadVoxelPopupStrId = "Load Voxels Popup";
bool isLoadVoxelPopup = true; bool isLoadVoxelPopup = true;
if (_sample.GetRecastResults() != null && _sample.GetRecastConfig() != null)
{
if (ImGui.Button("Import Voxels"))
{
Copy();
}
}
if (ImGui.Button("Load Voxels...")) if (ImGui.Button("Load Voxels..."))
{ {
ImGui.OpenPopup(loadVoxelPopupStrId); ImGui.OpenPopup(loadVoxelPopupStrId);
@ -145,7 +135,7 @@ public class DynamicUpdateSampleTool : ISampleTool
ImGui.EndPopup(); ImGui.EndPopup();
} }
const string saveVoxelPopupStrId = "Save Voxels Popup"; var saveVoxelPopupStrId = "Save Voxels Popup";
bool isSaveVoxelPopup = true; bool isSaveVoxelPopup = true;
var dynaMesh = _tool.GetDynamicNavMesh(); var dynaMesh = _tool.GetDynamicNavMesh();
@ -154,7 +144,7 @@ public class DynamicUpdateSampleTool : ISampleTool
ImGui.Checkbox("Compression", ref compression); ImGui.Checkbox("Compression", ref compression);
if (ImGui.Button("Save Voxels...")) if (ImGui.Button("Save Voxels..."))
{ {
ImGui.OpenPopup(saveVoxelPopupStrId); ImGui.BeginPopup(saveVoxelPopupStrId);
} }
if (ImGui.BeginPopupModal(saveVoxelPopupStrId, ref isSaveVoxelPopup, ImGuiWindowFlags.NoTitleBar)) if (ImGui.BeginPopupModal(saveVoxelPopupStrId, ref isSaveVoxelPopup, ImGuiWindowFlags.NoTitleBar))
@ -162,7 +152,9 @@ public class DynamicUpdateSampleTool : ISampleTool
var picker = ImFilePicker.GetFilePicker(saveVoxelPopupStrId, Path.Combine(Environment.CurrentDirectory), ".voxels"); var picker = ImFilePicker.GetFilePicker(saveVoxelPopupStrId, Path.Combine(Environment.CurrentDirectory), ".voxels");
if (picker.Draw()) if (picker.Draw())
{ {
if (string.IsNullOrEmpty(picker.SelectedFile))
Save(picker.SelectedFile); Save(picker.SelectedFile);
ImFilePicker.RemoveFilePicker(saveVoxelPopupStrId); ImFilePicker.RemoveFilePicker(saveVoxelPopupStrId);
} }
@ -414,12 +406,12 @@ public class DynamicUpdateSampleTool : ISampleTool
long t = RcFrequency.Ticks; long t = RcFrequency.Ticks;
try try
{ {
bool updated = _tool.Update(executor); bool updated = _tool.UpdateDynaMesh(executor);
if (updated) if (updated)
{ {
buildTime = (RcFrequency.Ticks - t) / TimeSpan.TicksPerMillisecond; buildTime = (RcFrequency.Ticks - t) / TimeSpan.TicksPerMillisecond;
var dynaMesh = _tool.GetDynamicNavMesh(); var dynaMesh = _tool.GetDynamicNavMesh();
_sample.Update(null, null, dynaMesh.RecastResults(), dynaMesh.NavMesh()); _sample.Update(null, dynaMesh.RecastResults(), dynaMesh.NavMesh());
_sample.SetChanged(false); _sample.SetChanged(false);
} }
} }
@ -429,15 +421,6 @@ public class DynamicUpdateSampleTool : ISampleTool
} }
} }
private void Copy()
{
if (_sample.GetRecastResults() != null && _sample.GetRecastConfig() != null)
{
var dynaMesh = _tool.Copy(_sample.GetRecastConfig(), _sample.GetRecastResults());
UpdateFrom(dynaMesh.config);
BuildDynaMesh();
}
}
private void Load(string filename) private void Load(string filename)
{ {
@ -467,7 +450,7 @@ public class DynamicUpdateSampleTool : ISampleTool
long t = RcFrequency.Ticks; long t = RcFrequency.Ticks;
try try
{ {
var _ = dynaMesh.Build(executor); var _ = dynaMesh.Build(executor).Result;
} }
catch (Exception e) catch (Exception e)
{ {
@ -475,7 +458,7 @@ public class DynamicUpdateSampleTool : ISampleTool
} }
buildTime = (RcFrequency.Ticks - t) / TimeSpan.TicksPerMillisecond; buildTime = (RcFrequency.Ticks - t) / TimeSpan.TicksPerMillisecond;
_sample.Update(null, null, dynaMesh.RecastResults(), dynaMesh.NavMesh()); _sample.Update(null, dynaMesh.RecastResults(), dynaMesh.NavMesh());
} }
private void UpdateTo(DtDynamicNavMeshConfig config) private void UpdateTo(DtDynamicNavMeshConfig config)

View File

@ -1,4 +1,4 @@
using System; using System;
using DotRecast.Core.Collections; using DotRecast.Core.Collections;
using DotRecast.Core.Numerics; using DotRecast.Core.Numerics;
using DotRecast.Recast.Demo.Draw; using DotRecast.Recast.Demo.Draw;
@ -36,11 +36,16 @@ public static class GizmoRenderer
} }
} }
public static int GetColorByNormal(RcVec3f v0, RcVec3f v1, RcVec3f v2) public static int GetColorByNormal(float[] vertices, int v0, int v1, int v2)
{ {
RcVec3f e0 = new RcVec3f();
RcVec3f e1 = new RcVec3f();
RcVec3f normal = new RcVec3f(); RcVec3f normal = new RcVec3f();
RcVec3f e0 = v1 - v0; for (int j = 0; j < 3; ++j)
RcVec3f e1 = v2 - v0; {
e0 = RcVecUtils.Subtract(vertices, v1, v0);
e1 = RcVecUtils.Subtract(vertices, v2, v0);
}
normal.X = e0.Y * e1.Z - e0.Z * e1.Y; normal.X = e0.Y * e1.Z - e0.Z * e1.Y;
normal.Y = e0.Z * e1.X - e0.X * e1.Z; normal.Y = e0.Z * e1.X - e0.X * e1.Z;
@ -60,7 +65,7 @@ public static class GizmoRenderer
var trX = new RcVec3f(box.halfEdges[0].X, box.halfEdges[1].X, box.halfEdges[2].X); var trX = new RcVec3f(box.halfEdges[0].X, box.halfEdges[1].X, box.halfEdges[2].X);
var trY = new RcVec3f(box.halfEdges[0].Y, box.halfEdges[1].Y, box.halfEdges[2].Y); var trY = new RcVec3f(box.halfEdges[0].Y, box.halfEdges[1].Y, box.halfEdges[2].Y);
var trZ = new RcVec3f(box.halfEdges[0].Z, box.halfEdges[1].Z, box.halfEdges[2].Z); var trZ = new RcVec3f(box.halfEdges[0].Z, box.halfEdges[1].Z, box.halfEdges[2].Z);
Span<float> vertices = stackalloc float[8 * 3]; float[] vertices = new float[8 * 3];
for (int i = 0; i < 8; i++) for (int i = 0; i < 8; i++)
{ {
vertices[i * 3 + 0] = RcVec3f.Dot(RcBoxGizmo.VERTS[i], trX) + box.center.X; vertices[i * 3 + 0] = RcVec3f.Dot(RcBoxGizmo.VERTS[i], trX) + box.center.X;
@ -155,13 +160,13 @@ public static class GizmoRenderer
debugDraw.Begin(DebugDrawPrimitives.TRIS); debugDraw.Begin(DebugDrawPrimitives.TRIS);
for (int i = 0; i < trimesh.triangles.Length; i += 3) for (int i = 0; i < trimesh.triangles.Length; i += 3)
{ {
RcVec3f v0 = RcVec.Create(trimesh.vertices, 3 * trimesh.triangles[i]); int v0 = 3 * trimesh.triangles[i];
RcVec3f v1 = RcVec.Create(trimesh.vertices, 3 * trimesh.triangles[i + 1]); int v1 = 3 * trimesh.triangles[i + 1];
RcVec3f v2 = RcVec.Create(trimesh.vertices, 3 * trimesh.triangles[i + 2]); int v2 = 3 * trimesh.triangles[i + 2];
int col = GetColorByNormal(v0, v1, v2); int col = GetColorByNormal(trimesh.vertices, v0, v1, v2);
debugDraw.Vertex(v0.X, v0.Y, v0.Z, col); debugDraw.Vertex(trimesh.vertices[v0], trimesh.vertices[v0 + 1], trimesh.vertices[v0 + 2], col);
debugDraw.Vertex(v1.X, v1.Y, v1.Z, col); debugDraw.Vertex(trimesh.vertices[v1], trimesh.vertices[v1 + 1], trimesh.vertices[v1 + 2], col);
debugDraw.Vertex(v2.X, v2.Y, v2.Z, col); debugDraw.Vertex(trimesh.vertices[v2], trimesh.vertices[v2 + 1], trimesh.vertices[v2 + 2], col);
} }
debugDraw.End(); debugDraw.End();

View File

@ -1,7 +1,7 @@
/* /*
Copyright (c) 2009-2010 Mikko Mononen memon@inside.org Copyright (c) 2009-2010 Mikko Mononen memon@inside.org
recast4j copyright (c) 2015-2019 Piotr Piastucki piotr@jtilia.org recast4j copyright (c) 2015-2019 Piotr Piastucki piotr@jtilia.org
DotRecast Copyright (c) 2023-2024 Choi Ikpil ikpil@naver.com DotRecast Copyright (c) 2023 Choi Ikpil ikpil@naver.com
This software is provided 'as-is', without any express or implied This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages warranty. In no event will the authors be held liable for any damages

View File

@ -16,7 +16,6 @@ freely, subject to the following restrictions:
3. This notice may not be removed or altered from any source distribution. 3. This notice may not be removed or altered from any source distribution.
*/ */
using System.Linq;
using DotRecast.Core.Numerics; using DotRecast.Core.Numerics;
using DotRecast.Detour.Extras.Jumplink; using DotRecast.Detour.Extras.Jumplink;
using DotRecast.Recast.Demo.Draw; using DotRecast.Recast.Demo.Draw;
@ -97,25 +96,13 @@ public class JumpLinkBuilderSampleTool : ISampleTool
if (build || _cfg.buildOffMeshConnections) if (build || _cfg.buildOffMeshConnections)
{ {
do if (0 < _sample.GetRecastResults().Count)
{ {
if (0 >= _sample.GetRecastResults().Count)
{
Logger.Error("build navmesh");
break;
}
if (_sample.GetRecastResults().Any(x => null == x.SolidHeightfiled))
{
Logger.Error("Tick 'Keep Itermediate Results' option");
break;
}
var geom = _sample.GetInputGeom(); var geom = _sample.GetInputGeom();
var settings = _sample.GetSettings(); var settings = _sample.GetSettings();
_tool.Build(geom, settings, _sample.GetRecastResults(), _cfg); _tool.Build(geom, settings, _sample.GetRecastResults(), _cfg);
} while (false); }
} }
ImGui.NewLine(); ImGui.NewLine();
@ -417,7 +404,7 @@ public class JumpLinkBuilderSampleTool : ISampleTool
} }
private void DrawTrajectory(RecastDebugDraw dd, JumpLink link, RcVec3f pa, RcVec3f pb, ITrajectory tra, int cola) private void DrawTrajectory(RecastDebugDraw dd, JumpLink link, RcVec3f pa, RcVec3f pb, Trajectory tra, int cola)
{ {
} }

View File

@ -1,4 +1,4 @@
using DotRecast.Core; using DotRecast.Core;
using DotRecast.Core.Numerics; using DotRecast.Core.Numerics;
using DotRecast.Detour.TileCache; using DotRecast.Detour.TileCache;
using DotRecast.Detour.TileCache.Io.Compress; using DotRecast.Detour.TileCache.Io.Compress;
@ -32,7 +32,8 @@ public class ObstacleSampleTool : ISampleTool
var buildResult = _tool.Build(geom, settings, RcByteOrder.LITTLE_ENDIAN, true); var buildResult = _tool.Build(geom, settings, RcByteOrder.LITTLE_ENDIAN, true);
if (buildResult.Success) if (buildResult.Success)
{ {
_sample.Update(_sample.GetInputGeom(), buildResult.Cfg, buildResult.RecastBuilderResults, buildResult.NavMesh); _sample.Update(_sample.GetInputGeom(), buildResult.RecastBuilderResults, buildResult.NavMesh);
_sample.SetChanged(false);
} }
} }

View File

@ -1,7 +1,7 @@
/* /*
Copyright (c) 2009-2010 Mikko Mononen memon@inside.org Copyright (c) 2009-2010 Mikko Mononen memon@inside.org
recast4j copyright (c) 2015-2019 Piotr Piastucki piotr@jtilia.org recast4j copyright (c) 2015-2019 Piotr Piastucki piotr@jtilia.org
DotRecast Copyright (c) 2023-2024 Choi Ikpil ikpil@naver.com DotRecast Copyright (c) 2023 Choi Ikpil ikpil@naver.com
This software is provided 'as-is', without any express or implied This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages warranty. In no event will the authors be held liable for any damages

View File

@ -57,8 +57,7 @@ public class TestNavmeshSampleTool : ISampleTool
private bool m_hitResult; private bool m_hitResult;
private float m_distanceToWall; private float m_distanceToWall;
private DtStraightPath[] m_straightPath; private List<DtStraightPath> m_straightPath;
private int m_straightPathCount;
private List<long> m_polys; private List<long> m_polys;
private List<long> m_parent; private List<long> m_parent;
private float m_neighbourhoodRadius; private float m_neighbourhoodRadius;
@ -78,8 +77,6 @@ public class TestNavmeshSampleTool : ISampleTool
SampleAreaModifications.SAMPLE_POLYFLAGS_DISABLED, SampleAreaModifications.SAMPLE_POLYFLAGS_DISABLED,
new float[] { 1f, 1f, 1f, 1f, 2f, 1.5f } new float[] { 1f, 1f, 1f, 1f, 2f, 1.5f }
); );
m_straightPath = new DtStraightPath[MAX_POLYS];
m_straightPathCount = 0;
} }
public void Layout() public void Layout()
@ -140,22 +137,22 @@ public class TestNavmeshSampleTool : ISampleTool
ImGui.Text("Common"); ImGui.Text("Common");
ImGui.Separator(); ImGui.Separator();
ImGui.Text("+ Include Flags"); ImGui.Text("Include Flags");
ImGui.Separator(); ImGui.Separator();
ImGui.CheckboxFlags("+ Walk", ref _includeFlags, SampleAreaModifications.SAMPLE_POLYFLAGS_WALK); ImGui.CheckboxFlags("Walk", ref _includeFlags, SampleAreaModifications.SAMPLE_POLYFLAGS_WALK);
ImGui.CheckboxFlags("+ Swim", ref _includeFlags, SampleAreaModifications.SAMPLE_POLYFLAGS_SWIM); ImGui.CheckboxFlags("Swim", ref _includeFlags, SampleAreaModifications.SAMPLE_POLYFLAGS_SWIM);
ImGui.CheckboxFlags("+ Door", ref _includeFlags, SampleAreaModifications.SAMPLE_POLYFLAGS_DOOR); ImGui.CheckboxFlags("Door", ref _includeFlags, SampleAreaModifications.SAMPLE_POLYFLAGS_DOOR);
ImGui.CheckboxFlags("+ Jump", ref _includeFlags, SampleAreaModifications.SAMPLE_POLYFLAGS_JUMP); ImGui.CheckboxFlags("Jump", ref _includeFlags, SampleAreaModifications.SAMPLE_POLYFLAGS_JUMP);
ImGui.NewLine(); ImGui.NewLine();
m_filter.SetIncludeFlags(_includeFlags); m_filter.SetIncludeFlags(_includeFlags);
ImGui.Text("- Exclude Flags"); ImGui.Text("Exclude Flags");
ImGui.Separator(); ImGui.Separator();
ImGui.CheckboxFlags("- Walk", ref _excludeFlags, SampleAreaModifications.SAMPLE_POLYFLAGS_WALK); ImGui.CheckboxFlags("Walk", ref _excludeFlags, SampleAreaModifications.SAMPLE_POLYFLAGS_WALK);
ImGui.CheckboxFlags("- Swim", ref _excludeFlags, SampleAreaModifications.SAMPLE_POLYFLAGS_SWIM); ImGui.CheckboxFlags("Swim", ref _excludeFlags, SampleAreaModifications.SAMPLE_POLYFLAGS_SWIM);
ImGui.CheckboxFlags("- Door", ref _excludeFlags, SampleAreaModifications.SAMPLE_POLYFLAGS_DOOR); ImGui.CheckboxFlags("Door", ref _excludeFlags, SampleAreaModifications.SAMPLE_POLYFLAGS_DOOR);
ImGui.CheckboxFlags("- Jump", ref _excludeFlags, SampleAreaModifications.SAMPLE_POLYFLAGS_JUMP); ImGui.CheckboxFlags("Jump", ref _excludeFlags, SampleAreaModifications.SAMPLE_POLYFLAGS_JUMP);
ImGui.NewLine(); ImGui.NewLine();
m_filter.SetExcludeFlags(_excludeFlags); m_filter.SetExcludeFlags(_excludeFlags);
@ -287,7 +284,7 @@ public class TestNavmeshSampleTool : ISampleTool
int spathCol = DuRGBA(64, 16, 0, 220); int spathCol = DuRGBA(64, 16, 0, 220);
int offMeshCol = DuRGBA(128, 96, 0, 220); int offMeshCol = DuRGBA(128, 96, 0, 220);
dd.Begin(LINES, 2.0f); dd.Begin(LINES, 2.0f);
for (int i = 0; i < m_straightPathCount - 1; ++i) for (int i = 0; i < m_straightPath.Count - 1; ++i)
{ {
DtStraightPath straightPathItem = m_straightPath[i]; DtStraightPath straightPathItem = m_straightPath[i];
DtStraightPath straightPathItem2 = m_straightPath[i + 1]; DtStraightPath straightPathItem2 = m_straightPath[i + 1];
@ -307,7 +304,7 @@ public class TestNavmeshSampleTool : ISampleTool
dd.End(); dd.End();
dd.Begin(POINTS, 6.0f); dd.Begin(POINTS, 6.0f);
for (int i = 0; i < m_straightPathCount; ++i) for (int i = 0; i < m_straightPath.Count; ++i)
{ {
DtStraightPath straightPathItem = m_straightPath[i]; DtStraightPath straightPathItem = m_straightPath[i];
int col; int col;
@ -352,7 +349,7 @@ public class TestNavmeshSampleTool : ISampleTool
dd.DepthMask(false); dd.DepthMask(false);
int spathCol = m_hitResult ? DuRGBA(64, 16, 0, 220) : DuRGBA(240, 240, 240, 220); int spathCol = m_hitResult ? DuRGBA(64, 16, 0, 220) : DuRGBA(240, 240, 240, 220);
dd.Begin(LINES, 2.0f); dd.Begin(LINES, 2.0f);
for (int i = 0; i < m_straightPathCount - 1; ++i) for (int i = 0; i < m_straightPath.Count - 1; ++i)
{ {
DtStraightPath straightPathItem = m_straightPath[i]; DtStraightPath straightPathItem = m_straightPath[i];
DtStraightPath straightPathItem2 = m_straightPath[i + 1]; DtStraightPath straightPathItem2 = m_straightPath[i + 1];
@ -362,7 +359,7 @@ public class TestNavmeshSampleTool : ISampleTool
dd.End(); dd.End();
dd.Begin(POINTS, 4.0f); dd.Begin(POINTS, 4.0f);
for (int i = 0; i < m_straightPathCount; ++i) for (int i = 0; i < m_straightPath.Count; ++i)
{ {
DtStraightPath straightPathItem = m_straightPath[i]; DtStraightPath straightPathItem = m_straightPath[i];
dd.Vertex(straightPathItem.pos.X, straightPathItem.pos.Y + 0.4f, straightPathItem.pos.Z, spathCol); dd.Vertex(straightPathItem.pos.X, straightPathItem.pos.Y + 0.4f, straightPathItem.pos.Z, spathCol);
@ -472,9 +469,8 @@ public class TestNavmeshSampleTool : ISampleTool
{ {
if (m_polys != null) if (m_polys != null)
{ {
const int MAX_SEGS = DtDetour.DT_VERTS_PER_POLYGON * 4; var segmentVerts = new List<RcSegmentVert>();
Span<RcSegmentVert> segs = stackalloc RcSegmentVert[MAX_SEGS]; var segmentRefs = new List<long>();
Span<long> refs = stackalloc long[MAX_SEGS];
for (int i = 0; i < m_polys.Count; i++) for (int i = 0; i < m_polys.Count; i++)
{ {
@ -492,20 +488,18 @@ public class TestNavmeshSampleTool : ISampleTool
dd.DepthMask(true); dd.DepthMask(true);
if (_sample.GetNavMeshQuery() != null) if (_sample.GetNavMeshQuery() != null)
{ {
int nsegs = 0;
var result = _sample var result = _sample
.GetNavMeshQuery() .GetNavMeshQuery()
.GetPolyWallSegments(m_polys[i], m_filter, segs, refs, ref nsegs, MAX_SEGS); .GetPolyWallSegments(m_polys[i], false, m_filter, ref segmentVerts, ref segmentRefs);
if (result.Succeeded()) if (result.Succeeded())
{ {
dd.Begin(LINES, 2.0f); dd.Begin(LINES, 2.0f);
for (int j = 0; j < nsegs; ++j) for (int j = 0; j < segmentVerts.Count; ++j)
{ {
ref RcSegmentVert s = ref segs[j]; RcSegmentVert s = segmentVerts[j];
var v0 = s.vmin; var v0 = s.vmin;
var s3 = s.vmax; var s3 = s.vmax;
// Skip too distant segments. // Skip too distant segments.
var distSqr = DtUtils.DistancePtSegSqr2D(m_spos, v0, s3, out var tseg); var distSqr = DtUtils.DistancePtSegSqr2D(m_spos, v0, s3, out var tseg);
if (distSqr > RcMath.Sqr(m_neighbourhoodRadius)) if (distSqr > RcMath.Sqr(m_neighbourhoodRadius))
@ -514,13 +508,12 @@ public class TestNavmeshSampleTool : ISampleTool
} }
RcVec3f delta = RcVec3f.Subtract(s3, s.vmin); RcVec3f delta = RcVec3f.Subtract(s3, s.vmin);
RcVec3f p0 = RcVec.Mad(s.vmin, delta, 0.5f); RcVec3f p0 = RcVecUtils.Mad(s.vmin, delta, 0.5f);
RcVec3f norm = new RcVec3f(delta.Z, 0, -delta.X); RcVec3f norm = new RcVec3f(delta.Z, 0, -delta.X);
norm = RcVec3f.Normalize(norm); norm = RcVec3f.Normalize(norm);
RcVec3f p1 = RcVec.Mad(p0, norm, agentRadius * 0.5f); RcVec3f p1 = RcVecUtils.Mad(p0, norm, agentRadius * 0.5f);
// Skip backfacing segments. // Skip backfacing segments.
if (refs[j] != 0) if (segmentRefs[j] != 0)
{ {
int col = DuRGBA(255, 255, 255, 32); int col = DuRGBA(255, 255, 255, 32);
dd.Vertex(s.vmin.X, s.vmin.Y + agentClimb, s.vmin.Z, col); dd.Vertex(s.vmin.X, s.vmin.Y + agentClimb, s.vmin.Z, col);
@ -668,23 +661,23 @@ public class TestNavmeshSampleTool : ISampleTool
if (_mode == RcTestNavmeshToolMode.PATHFIND_FOLLOW) if (_mode == RcTestNavmeshToolMode.PATHFIND_FOLLOW)
{ {
_tool.FindFollowPath(navMesh, navQuery, m_startRef, m_endRef, m_spos, m_epos, m_filter, _enableRaycast, _tool.FindFollowPath(navMesh, navQuery, m_startRef, m_endRef, m_spos, m_epos, m_filter, _enableRaycast,
ref m_polys, m_polys?.Count ?? 0, ref m_smoothPath); ref m_polys, ref m_smoothPath);
} }
else if (_mode == RcTestNavmeshToolMode.PATHFIND_STRAIGHT) else if (_mode == RcTestNavmeshToolMode.PATHFIND_STRAIGHT)
{ {
_tool.FindStraightPath(navQuery, m_startRef, m_endRef, m_spos, m_epos, m_filter, _enableRaycast, _tool.FindStraightPath(navQuery, m_startRef, m_endRef, m_spos, m_epos, m_filter, _enableRaycast,
ref m_polys, m_straightPath, out m_straightPathCount, MAX_POLYS, _straightPathOption); ref m_polys, ref m_straightPath, _straightPathOption);
} }
else if (_mode == RcTestNavmeshToolMode.PATHFIND_SLICED) else if (_mode == RcTestNavmeshToolMode.PATHFIND_SLICED)
{ {
m_polys?.Clear(); m_polys?.Clear();
m_straightPathCount = 0; m_straightPath?.Clear();
m_pathFindStatus = _tool.InitSlicedFindPath(navQuery, m_startRef, m_endRef, m_spos, m_epos, m_filter, _enableRaycast); m_pathFindStatus = _tool.InitSlicedFindPath(navQuery, m_startRef, m_endRef, m_spos, m_epos, m_filter, _enableRaycast);
} }
else if (_mode == RcTestNavmeshToolMode.RAYCAST) else if (_mode == RcTestNavmeshToolMode.RAYCAST)
{ {
_tool.Raycast(navQuery, m_startRef, m_endRef, m_spos, m_epos, m_filter, _tool.Raycast(navQuery, m_startRef, m_endRef, m_spos, m_epos, m_filter,
ref m_polys, m_straightPath, out m_straightPathCount, MAX_POLYS, ref m_hitPos, ref m_hitNormal, ref m_hitResult); ref m_polys, ref m_straightPath, ref m_hitPos, ref m_hitNormal, ref m_hitResult);
} }
else if (_mode == RcTestNavmeshToolMode.DISTANCE_TO_WALL) else if (_mode == RcTestNavmeshToolMode.DISTANCE_TO_WALL)
{ {
@ -705,7 +698,6 @@ public class TestNavmeshSampleTool : ISampleTool
} }
else if (_mode == RcTestNavmeshToolMode.RANDOM_POINTS_IN_CIRCLE) 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); _tool.FindRandomPointAroundCircle(navQuery, m_startRef, m_endRef, m_spos, m_epos, m_filter, _constrainByCircle, _randomPointCount, ref _randomPoints);
} }
} }
@ -719,7 +711,7 @@ public class TestNavmeshSampleTool : ISampleTool
if (m_pathFindStatus.InProgress()) if (m_pathFindStatus.InProgress())
{ {
m_pathFindStatus = _tool.UpdateSlicedFindPath(navQuery, 1, m_endRef, m_spos, m_epos, ref m_polys, m_straightPath, out m_straightPathCount, MAX_POLYS); m_pathFindStatus = _tool.UpdateSlicedFindPath(navQuery, 1, m_endRef, m_spos, m_epos, ref m_polys, ref m_straightPath);
} }
} }
} }

View File

@ -1,4 +1,4 @@
using System; using System;
using DotRecast.Core.Numerics; using DotRecast.Core.Numerics;
using DotRecast.Recast.Demo.Draw; using DotRecast.Recast.Demo.Draw;
using DotRecast.Recast.Toolset; using DotRecast.Recast.Toolset;

View File

@ -1,6 +1,5 @@
/* /*
recast4j Copyright (c) 2015-2019 Piotr Piastucki piotr@jtilia.org recast4j copyright (c) 2015-2019 Piotr Piastucki piotr@jtilia.org
DotRecast Copyright (c) 2023-2024 Choi Ikpil ikpil@naver.com
This software is provided 'as-is', without any express or implied This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages warranty. In no event will the authors be held liable for any damages

View File

@ -1,4 +1,4 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Collections.Immutable; using System.Collections.Immutable;
@ -69,7 +69,7 @@ public class ImFilePicker
ImGui.Text("Current Folder: " + CurrentFolder); ImGui.Text("Current Folder: " + CurrentFolder);
bool result = false; bool result = false;
if (ImGui.BeginChild(1, new Vector2(1024, 400))) if (ImGui.BeginChildFrame(1, new Vector2(1024, 400)))
{ {
var di = new DirectoryInfo(CurrentFolder); var di = new DirectoryInfo(CurrentFolder);
if (di.Exists) if (di.Exists)
@ -111,7 +111,7 @@ public class ImFilePicker
} }
} }
ImGui.EndChild(); ImGui.EndChildFrame();
if (ImGui.Button("Cancel")) if (ImGui.Button("Cancel"))

View File

@ -1,6 +1,5 @@
/* /*
recast4j Copyright (c) 2015-2019 Piotr Piastucki piotr@jtilia.org recast4j copyright (c) 2015-2019 Piotr Piastucki piotr@jtilia.org
DotRecast Copyright (c) 2023-2024 Choi Ikpil ikpil@naver.com
This software is provided 'as-is', without any express or implied This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages warranty. In no event will the authors be held liable for any damages

View File

@ -1,4 +1,4 @@
using System; using System;
using System.Collections.Concurrent; using System.Collections.Concurrent;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
@ -80,7 +80,7 @@ public class RcLogView : IRcView
} }
if (ImGui.BeginChild("scrolling", Vector2.Zero, ImGuiChildFlags.None, ImGuiWindowFlags.HorizontalScrollbar)) if (ImGui.BeginChild("scrolling", Vector2.Zero, false, ImGuiWindowFlags.HorizontalScrollbar))
{ {
_isHovered = ImGui.IsWindowHovered(ImGuiHoveredFlags.RectOnly | ImGuiHoveredFlags.RootAndChildWindows); _isHovered = ImGui.IsWindowHovered(ImGuiHoveredFlags.RectOnly | ImGuiHoveredFlags.RootAndChildWindows);

View File

@ -1,59 +0,0 @@
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

@ -1,6 +1,5 @@
/* /*
recast4j Copyright (c) 2015-2019 Piotr Piastucki piotr@jtilia.org recast4j copyright (c) 2015-2019 Piotr Piastucki piotr@jtilia.org
DotRecast Copyright (c) 2023-2024 Choi Ikpil ikpil@naver.com
This software is provided 'as-is', without any express or implied This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages warranty. In no event will the authors be held liable for any damages
@ -124,7 +123,7 @@ public class RcSettingsView : IRcView
ImGui.Text("Agent"); ImGui.Text("Agent");
ImGui.Separator(); ImGui.Separator();
ImGui.SliderFloat("Height", ref settings.agentHeight, 0.1f, 5f, "%.1f"); ImGui.SliderFloat("Height", ref settings.agentHeight, 0.1f, 5f, "%.1f");
ImGui.SliderFloat("Radius", ref settings.agentRadius, 0.0f, 5f, "%.1f"); ImGui.SliderFloat("Radius", ref settings.agentRadius, 0.1f, 5f, "%.1f");
ImGui.SliderFloat("Max Climb", ref settings.agentMaxClimb, 0.1f, 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 Slope", ref settings.agentMaxSlope, 1f, 90f, "%.0f");
ImGui.SliderFloat("Max Acceleration", ref settings.agentMaxAcceleration, 8f, 999f, "%.1f"); ImGui.SliderFloat("Max Acceleration", ref settings.agentMaxAcceleration, 8f, 999f, "%.1f");
@ -167,10 +166,6 @@ public class RcSettingsView : IRcView
ImGui.SliderFloat("Max Sample Error", ref settings.detailSampleMaxError, 0f, 16f, "%.1f"); ImGui.SliderFloat("Max Sample Error", ref settings.detailSampleMaxError, 0f, 16f, "%.1f");
ImGui.NewLine(); ImGui.NewLine();
ImGui.Checkbox("Keep Itermediate Results", ref settings.keepInterResults);
ImGui.Checkbox("Build All Tiles", ref settings.buildAll);
ImGui.NewLine();
ImGui.Text("Tiling"); ImGui.Text("Tiling");
ImGui.Separator(); ImGui.Separator();
ImGui.Checkbox("Enable", ref settings.tiled); ImGui.Checkbox("Enable", ref settings.tiled);
@ -233,12 +228,6 @@ public class RcSettingsView : IRcView
DrawMode.Values.ForEach(dm => { ImGui.RadioButton(dm.Text, ref drawMode, dm.Idx); }); DrawMode.Values.ForEach(dm => { ImGui.RadioButton(dm.Text, ref drawMode, dm.Idx); });
ImGui.NewLine(); ImGui.NewLine();
ImGui.Separator();
ImGui.Text("Tick 'Keep Itermediate Results'");
ImGui.Text("rebuild some tiles to see");
ImGui.Text("more debug mode options.");
ImGui.NewLine();
ImGui.End(); ImGui.End();
} }

View File

@ -1,7 +1,7 @@
/* /*
Copyright (c) 2009-2010 Mikko Mononen memon@inside.org Copyright (c) 2009-2010 Mikko Mononen memon@inside.org
recast4j copyright (c) 2021 Piotr Piastucki piotr@jtilia.org recast4j copyright (c) 2021 Piotr Piastucki piotr@jtilia.org
DotRecast Copyright (c) 2023-2024 Choi Ikpil ikpil@naver.com DotRecast Copyright (c) 2023 Choi Ikpil ikpil@naver.com
This software is provided 'as-is', without any express or implied This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages warranty. In no event will the authors be held liable for any damages

View File

@ -1,4 +1,4 @@
namespace DotRecast.Recast.Demo.UI.ViewModels; namespace DotRecast.Recast.Demo.UI.ViewModels;
public class LogMessageItem public class LogMessageItem
{ {

View File

@ -10,8 +10,8 @@ namespace DotRecast.Recast.Toolset.Builder
float cellHeight, float agentHeight, float agentRadius, float agentMaxClimb, float cellHeight, float agentHeight, float agentRadius, float agentMaxClimb,
RcBuilderResult rcResult) RcBuilderResult rcResult)
{ {
RcPolyMesh pmesh = rcResult.Mesh; RcPolyMesh pmesh = rcResult.GetMesh();
RcPolyMeshDetail dmesh = rcResult.MeshDetail; RcPolyMeshDetail dmesh = rcResult.GetMeshDetail();
DtNavMeshCreateParams option = new DtNavMeshCreateParams(); DtNavMeshCreateParams option = new DtNavMeshCreateParams();
for (int i = 0; i < pmesh.npolys; ++i) for (int i = 0; i < pmesh.npolys; ++i)
{ {

View File

@ -1,4 +1,4 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using DotRecast.Detour; using DotRecast.Detour;
@ -7,7 +7,6 @@ namespace DotRecast.Recast.Toolset.Builder
public class NavMeshBuildResult public class NavMeshBuildResult
{ {
public readonly bool Success; public readonly bool Success;
public readonly RcConfig Cfg;
public readonly IList<RcBuilderResult> RecastBuilderResults; public readonly IList<RcBuilderResult> RecastBuilderResults;
public readonly DtNavMesh NavMesh; public readonly DtNavMesh NavMesh;
@ -18,22 +17,11 @@ namespace DotRecast.Recast.Toolset.Builder
NavMesh = null; NavMesh = null;
} }
// for solo public NavMeshBuildResult(IList<RcBuilderResult> recastBuilderResults, DtNavMesh navMesh)
public NavMeshBuildResult(RcConfig cfg, IList<RcBuilderResult> recastBuilderResults, DtNavMesh navMesh)
{ {
Success = true; Success = true;
Cfg = cfg;
RecastBuilderResults = recastBuilderResults; RecastBuilderResults = recastBuilderResults;
NavMesh = navMesh; NavMesh = navMesh;
} }
// for tiles
public NavMeshBuildResult(RcConfig cfg, IList<RcBuilderResult> recastBuilderResults)
{
Success = true;
Cfg = cfg;
RecastBuilderResults = recastBuilderResults;
NavMesh = null;
}
} }
} }

View File

@ -1,7 +1,7 @@
/* /*
Copyright (c) 2009-2010 Mikko Mononen memon@inside.org Copyright (c) 2009-2010 Mikko Mononen memon@inside.org
recast4j copyright (c) 2015-2019 Piotr Piastucki piotr@jtilia.org recast4j copyright (c) 2015-2019 Piotr Piastucki piotr@jtilia.org
DotRecast Copyright (c) 2023-2024 Choi Ikpil ikpil@naver.com DotRecast Copyright (c) 2023 Choi Ikpil ikpil@naver.com
This software is provided 'as-is', without any express or implied This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages warranty. In no event will the authors be held liable for any damages
@ -34,12 +34,12 @@ namespace DotRecast.Recast.Toolset.Builder
public const int SAMPLE_POLYAREA_TYPE_JUMP_AUTO = 0x6; public const int SAMPLE_POLYAREA_TYPE_JUMP_AUTO = 0x6;
public const int SAMPLE_POLYAREA_TYPE_WALKABLE = 0x3f; public const int SAMPLE_POLYAREA_TYPE_WALKABLE = 0x3f;
public const int SAMPLE_POLYFLAGS_WALK = 0x01; // Ability to walk (ground, grass, road) public static readonly int SAMPLE_POLYFLAGS_WALK = 0x01; // Ability to walk (ground, grass, road)
public const int SAMPLE_POLYFLAGS_SWIM = 0x02; // Ability to swim (water). public static readonly int SAMPLE_POLYFLAGS_SWIM = 0x02; // Ability to swim (water).
public const int SAMPLE_POLYFLAGS_DOOR = 0x04; // Ability to move through doors. public static readonly int SAMPLE_POLYFLAGS_DOOR = 0x04; // Ability to move through doors.
public const int SAMPLE_POLYFLAGS_JUMP = 0x08; // Ability to jump. public static readonly int SAMPLE_POLYFLAGS_JUMP = 0x08; // Ability to jump.
public const int SAMPLE_POLYFLAGS_DISABLED = 0x10; // Disabled polygon public static readonly int SAMPLE_POLYFLAGS_DISABLED = 0x10; // Disabled polygon
public const int SAMPLE_POLYFLAGS_ALL = 0xffff; // All abilities. public static readonly int SAMPLE_POLYFLAGS_ALL = 0xffff; // All abilities.
public static readonly RcAreaModification SAMPLE_AREAMOD_WALKABLE = new RcAreaModification(SAMPLE_POLYAREA_TYPE_WALKABLE); public static readonly RcAreaModification SAMPLE_AREAMOD_WALKABLE = new RcAreaModification(SAMPLE_POLYAREA_TYPE_WALKABLE);
public static readonly RcAreaModification SAMPLE_AREAMOD_GROUND = new RcAreaModification(SAMPLE_POLYAREA_TYPE_GROUND); public static readonly RcAreaModification SAMPLE_AREAMOD_GROUND = new RcAreaModification(SAMPLE_POLYAREA_TYPE_GROUND);
@ -61,9 +61,9 @@ namespace DotRecast.Recast.Toolset.Builder
public static RcAreaModification OfValue(int value) public static RcAreaModification OfValue(int value)
{ {
foreach (var v in Values) foreach(var v in Values)
{ {
if (v.Value == value) if(v.Value == value)
{ {
return v; return v;
} }

View File

@ -1,6 +1,5 @@
/* /*
recast4j Copyright (c) 2015-2019 Piotr Piastucki piotr@jtilia.org recast4j copyright (c) 2015-2019 Piotr Piastucki piotr@jtilia.org
DotRecast Copyright (c) 2023-2024 Choi Ikpil ikpil@naver.com
This software is provided 'as-is', without any express or implied This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages warranty. In no event will the authors be held liable for any damages
@ -35,8 +34,7 @@ namespace DotRecast.Recast.Toolset.Builder
settings.edgeMaxLen, settings.edgeMaxError, settings.edgeMaxLen, settings.edgeMaxError,
settings.vertsPerPoly, settings.vertsPerPoly,
settings.detailSampleDist, settings.detailSampleMaxError, settings.detailSampleDist, settings.detailSampleMaxError,
settings.filterLowHangingObstacles, settings.filterLedgeSpans, settings.filterWalkableLowHeightSpans, settings.filterLowHangingObstacles, settings.filterLedgeSpans, settings.filterWalkableLowHeightSpans);
settings.keepInterResults);
} }
public NavMeshBuildResult Build(DemoInputGeomProvider geom, public NavMeshBuildResult Build(DemoInputGeomProvider geom,
@ -47,8 +45,7 @@ namespace DotRecast.Recast.Toolset.Builder
float edgeMaxLen, float edgeMaxError, float edgeMaxLen, float edgeMaxError,
int vertsPerPoly, int vertsPerPoly,
float detailSampleDist, float detailSampleMaxError, float detailSampleDist, float detailSampleMaxError,
bool filterLowHangingObstacles, bool filterLedgeSpans, bool filterWalkableLowHeightSpans, bool filterLowHangingObstacles, bool filterLedgeSpans, bool filterWalkableLowHeightSpans)
bool keepInterResults)
{ {
RcConfig cfg = new RcConfig( RcConfig cfg = new RcConfig(
partitionType, partitionType,
@ -61,7 +58,7 @@ namespace DotRecast.Recast.Toolset.Builder
filterLowHangingObstacles, filterLedgeSpans, filterWalkableLowHeightSpans, filterLowHangingObstacles, filterLedgeSpans, filterWalkableLowHeightSpans,
SampleAreaModifications.SAMPLE_AREAMOD_WALKABLE, true); SampleAreaModifications.SAMPLE_AREAMOD_WALKABLE, true);
RcBuilderResult rcResult = BuildRecastResult(geom, cfg, keepInterResults); RcBuilderResult rcResult = BuildRecastResult(geom, cfg);
var meshData = BuildMeshData(geom, cellSize, cellHeight, agentHeight, agentRadius, agentMaxClimb, rcResult); var meshData = BuildMeshData(geom, cellSize, cellHeight, agentHeight, agentRadius, agentMaxClimb, rcResult);
if (null == meshData) if (null == meshData)
{ {
@ -69,26 +66,19 @@ namespace DotRecast.Recast.Toolset.Builder
} }
var navMesh = BuildNavMesh(meshData, vertsPerPoly); var navMesh = BuildNavMesh(meshData, vertsPerPoly);
return new NavMeshBuildResult(cfg, RcImmutableArray.Create(rcResult), navMesh); return new NavMeshBuildResult(RcImmutableArray.Create(rcResult), navMesh);
} }
private DtNavMesh BuildNavMesh(DtMeshData meshData, int vertsPerPoly) private DtNavMesh BuildNavMesh(DtMeshData meshData, int vertsPerPoly)
{ {
var mesh = new DtNavMesh(); return new DtNavMesh(meshData, vertsPerPoly, 0);
var status = mesh.Init(meshData, vertsPerPoly, 0);
if (status.Failed())
{
return null;
} }
return mesh; private RcBuilderResult BuildRecastResult(DemoInputGeomProvider geom, RcConfig cfg)
}
private RcBuilderResult BuildRecastResult(DemoInputGeomProvider geom, RcConfig cfg, bool keepInterResults)
{ {
RcBuilderConfig bcfg = new RcBuilderConfig(cfg, geom.GetMeshBoundsMin(), geom.GetMeshBoundsMax()); RcBuilderConfig bcfg = new RcBuilderConfig(cfg, geom.GetMeshBoundsMin(), geom.GetMeshBoundsMax());
RcBuilder rcBuilder = new RcBuilder(); RcBuilder rcBuilder = new RcBuilder();
return rcBuilder.Build(geom, bcfg, keepInterResults); return rcBuilder.Build(geom, bcfg);
} }
public DtMeshData BuildMeshData(DemoInputGeomProvider geom, public DtMeshData BuildMeshData(DemoInputGeomProvider geom,

View File

@ -1,6 +1,5 @@
/* /*
recast4j Copyright (c) 2015-2019 Piotr Piastucki piotr@jtilia.org recast4j copyright (c) 2015-2019 Piotr Piastucki piotr@jtilia.org
DotRecast Copyright (c) 2023-2024 Choi Ikpil ikpil@naver.com
This software is provided 'as-is', without any express or implied This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages warranty. In no event will the authors be held liable for any damages
@ -42,8 +41,7 @@ namespace DotRecast.Recast.Toolset.Builder
settings.minRegionSize, settings.mergedRegionSize, settings.minRegionSize, settings.mergedRegionSize,
settings.edgeMaxLen, settings.edgeMaxError, settings.edgeMaxLen, settings.edgeMaxError,
settings.vertsPerPoly, settings.detailSampleDist, settings.detailSampleMaxError, settings.vertsPerPoly, settings.detailSampleDist, settings.detailSampleMaxError,
settings.filterLowHangingObstacles, settings.filterLedgeSpans, settings.filterWalkableLowHeightSpans, settings.filterLowHangingObstacles, settings.filterLedgeSpans, settings.filterWalkableLowHeightSpans);
settings.keepInterResults, settings.buildAll);
} }
public NavMeshBuildResult Build(IInputGeomProvider geom, public NavMeshBuildResult Build(IInputGeomProvider geom,
@ -55,10 +53,9 @@ namespace DotRecast.Recast.Toolset.Builder
float edgeMaxLen, float edgeMaxError, float edgeMaxLen, float edgeMaxError,
int vertsPerPoly, int vertsPerPoly,
float detailSampleDist, float detailSampleMaxError, float detailSampleDist, float detailSampleMaxError,
bool filterLowHangingObstacles, bool filterLedgeSpans, bool filterWalkableLowHeightSpans, bool filterLowHangingObstacles, bool filterLedgeSpans, bool filterWalkableLowHeightSpans)
bool keepInterResults, bool buildAll)
{ {
NavMeshBuildResult result = BuildRecastResult( List<RcBuilderResult> results = BuildRecastResult(
geom, geom,
tileSize, tileSize,
partitionType, partitionType,
@ -68,16 +65,15 @@ namespace DotRecast.Recast.Toolset.Builder
edgeMaxLen, edgeMaxError, edgeMaxLen, edgeMaxError,
vertsPerPoly, vertsPerPoly,
detailSampleDist, detailSampleMaxError, detailSampleDist, detailSampleMaxError,
filterLowHangingObstacles, filterLedgeSpans, filterWalkableLowHeightSpans, filterLowHangingObstacles, filterLedgeSpans, filterWalkableLowHeightSpans
keepInterResults, buildAll
); );
var tileMeshData = BuildMeshData(geom, cellSize, cellHeight, agentHeight, agentRadius, agentMaxClimb, result.RecastBuilderResults); var tileMeshData = BuildMeshData(geom, cellSize, cellHeight, agentHeight, agentRadius, agentMaxClimb, results);
var tileNavMesh = BuildNavMesh(geom, tileMeshData, cellSize, tileSize, vertsPerPoly); var tileNavMesh = BuildNavMesh(geom, tileMeshData, cellSize, tileSize, vertsPerPoly);
return new NavMeshBuildResult(result.Cfg, result.RecastBuilderResults, tileNavMesh); return new NavMeshBuildResult(results, tileNavMesh);
} }
public NavMeshBuildResult BuildRecastResult(IInputGeomProvider geom, public List<RcBuilderResult> BuildRecastResult(IInputGeomProvider geom,
int tileSize, int tileSize,
RcPartition partitionType, RcPartition partitionType,
float cellSize, float cellHeight, float cellSize, float cellHeight,
@ -86,8 +82,7 @@ namespace DotRecast.Recast.Toolset.Builder
float edgeMaxLen, float edgeMaxError, float edgeMaxLen, float edgeMaxError,
int vertsPerPoly, int vertsPerPoly,
float detailSampleDist, float detailSampleMaxError, float detailSampleDist, float detailSampleMaxError,
bool filterLowHangingObstacles, bool filterLedgeSpans, bool filterWalkableLowHeightSpans, bool filterLowHangingObstacles, bool filterLedgeSpans, bool filterWalkableLowHeightSpans)
bool keepInterResults, bool buildAll)
{ {
RcConfig cfg = new RcConfig(true, tileSize, tileSize, RcConfig cfg = new RcConfig(true, tileSize, tileSize,
@ -102,8 +97,7 @@ namespace DotRecast.Recast.Toolset.Builder
filterLowHangingObstacles, filterLedgeSpans, filterWalkableLowHeightSpans, filterLowHangingObstacles, filterLedgeSpans, filterWalkableLowHeightSpans,
SampleAreaModifications.SAMPLE_AREAMOD_WALKABLE, true); SampleAreaModifications.SAMPLE_AREAMOD_WALKABLE, true);
RcBuilder rcBuilder = new RcBuilder(); RcBuilder rcBuilder = new RcBuilder();
var results = rcBuilder.BuildTiles(geom, cfg, keepInterResults, buildAll, Environment.ProcessorCount + 1, Task.Factory); return rcBuilder.BuildTiles(geom, cfg, Task.Factory);
return new NavMeshBuildResult(cfg, results);
} }
public DtNavMesh BuildNavMesh(IInputGeomProvider geom, List<DtMeshData> meshData, float cellSize, int tileSize, int vertsPerPoly) public DtNavMesh BuildNavMesh(IInputGeomProvider geom, List<DtMeshData> meshData, float cellSize, int tileSize, int vertsPerPoly)
@ -117,9 +111,8 @@ namespace DotRecast.Recast.Toolset.Builder
navMeshParams.maxTiles = GetMaxTiles(geom, cellSize, tileSize); navMeshParams.maxTiles = GetMaxTiles(geom, cellSize, tileSize);
navMeshParams.maxPolys = GetMaxPolysPerTile(geom, cellSize, tileSize); navMeshParams.maxPolys = GetMaxPolysPerTile(geom, cellSize, tileSize);
DtNavMesh navMesh = new DtNavMesh(); DtNavMesh navMesh = new DtNavMesh(navMeshParams, vertsPerPoly);
navMesh.Init(navMeshParams, vertsPerPoly); meshData.ForEach(md => navMesh.AddTile(md, 0, 0));
meshData.ForEach(md => navMesh.AddTile(md, 0, 0, out _));
return navMesh; return navMesh;
} }
@ -130,9 +123,10 @@ namespace DotRecast.Recast.Toolset.Builder
List<DtMeshData> meshData = new List<DtMeshData>(); List<DtMeshData> meshData = new List<DtMeshData>();
foreach (RcBuilderResult result in results) foreach (RcBuilderResult result in results)
{ {
int x = result.TileX; int x = result.tileX;
int z = result.TileZ; int z = result.tileZ;
DtNavMeshCreateParams option = DemoNavMeshBuilder.GetNavMeshCreateParams(geom, cellSize, cellHeight, agentHeight, agentRadius, agentMaxClimb, result); DtNavMeshCreateParams option = DemoNavMeshBuilder
.GetNavMeshCreateParams(geom, cellSize, cellHeight, agentHeight, agentRadius, agentMaxClimb, result);
option.tileX = x; option.tileX = x;
option.tileZ = z; option.tileZ = z;
@ -161,7 +155,7 @@ namespace DotRecast.Recast.Toolset.Builder
private int GetTileBits(IInputGeomProvider geom, float cellSize, int tileSize) private int GetTileBits(IInputGeomProvider geom, float cellSize, int tileSize)
{ {
RcRecast.CalcGridSize(geom.GetMeshBoundsMin(), geom.GetMeshBoundsMax(), cellSize, out var gw, out var gh); RcCommons.CalcGridSize(geom.GetMeshBoundsMin(), geom.GetMeshBoundsMax(), cellSize, out var gw, out var gh);
int tw = (gw + tileSize - 1) / tileSize; int tw = (gw + tileSize - 1) / tileSize;
int th = (gh + tileSize - 1) / tileSize; int th = (gh + tileSize - 1) / tileSize;
int tileBits = Math.Min(DtUtils.Ilog2(DtUtils.NextPow2(tw * th)), 14); int tileBits = Math.Min(DtUtils.Ilog2(DtUtils.NextPow2(tw * th)), 14);
@ -170,7 +164,7 @@ namespace DotRecast.Recast.Toolset.Builder
public int[] GetTiles(DemoInputGeomProvider geom, float cellSize, int tileSize) public int[] GetTiles(DemoInputGeomProvider geom, float cellSize, int tileSize)
{ {
RcRecast.CalcGridSize(geom.GetMeshBoundsMin(), geom.GetMeshBoundsMax(), cellSize, out var gw, out var gh); RcCommons.CalcGridSize(geom.GetMeshBoundsMin(), geom.GetMeshBoundsMax(), cellSize, out var gw, out var gh);
int tw = (gw + tileSize - 1) / tileSize; int tw = (gw + tileSize - 1) / tileSize;
int th = (gh + tileSize - 1) / tileSize; int th = (gh + tileSize - 1) / tileSize;
return new int[] { tw, th }; return new int[] { tw, th };

View File

@ -1,22 +0,0 @@
{
"name": "DotRecast.Recast.Toolset",
"rootNamespace": "DotRecast.Recast.Toolset",
"references": [
"DotRecast.Core",
"DotRecast.Recast",
"DotRecast.Detour",
"DotRecast.Detour.Crowd",
"DotRecast.Detour.Dynamic",
"DotRecast.Detour.Extras",
"DotRecast.Detour.TileCache"
],
"includePlatforms": [],
"excludePlatforms": [],
"allowUnsafeCode": false,
"overrideReferences": false,
"precompiledReferences": [],
"autoReferenced": true,
"defineConstraints": [],
"versionDefines": [],
"noEngineReferences": true
}

View File

@ -1,16 +1,15 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<TargetFrameworks>netstandard2.1;net6.0;net7.0;net8.0</TargetFrameworks> <TargetFrameworks>netstandard2.1;net6.0;net7.0</TargetFrameworks>
<PackageId>DotRecast.Recast.Toolset</PackageId> <PackageId>DotRecast.Recast.Toolset</PackageId>
<PackageReadmeFile>README.md</PackageReadmeFile> <PackageReadmeFile>README.md</PackageReadmeFile>
<Authors>ikpil</Authors> <Authors>ikpil</Authors>
<Description>DotRecast - a port of Recast Detour, Industry-standard navigation mesh toolset for .NET, C#, Unity3D, games, servers</Description> <Description>DotRecast - a port of Recast Detour, navigation mesh toolset for games, Unity3D, servers, C#</Description>
<RepositoryType>git</RepositoryType> <RepositoryType>git</RepositoryType>
<PackageProjectUrl>https://github.com/ikpil/DotRecast</PackageProjectUrl> <PackageProjectUrl>https://github.com/ikpil/DotRecast</PackageProjectUrl>
<RepositoryUrl>https://github.com/ikpil/DotRecast</RepositoryUrl> <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> <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> </PropertyGroup>
<ItemGroup> <ItemGroup>

View File

@ -1,4 +1,4 @@
using DotRecast.Detour; using DotRecast.Detour;
using DotRecast.Detour.TileCache; using DotRecast.Detour.TileCache;
using DotRecast.Recast.Geom; using DotRecast.Recast.Geom;
using DotRecast.Recast.Toolset.Builder; using DotRecast.Recast.Toolset.Builder;
@ -46,13 +46,13 @@ namespace DotRecast.Recast.Toolset.Geom
if (null != _geom) if (null != _geom)
{ {
var offMeshConnections = _geom.GetOffMeshConnections(); var offMeshConnections = _geom.GetOffMeshConnections();
option.offMeshConCount = offMeshConnections.Count;
option.offMeshConVerts = new float[option.offMeshConCount * 6]; option.offMeshConVerts = new float[option.offMeshConCount * 6];
option.offMeshConRad = new float[option.offMeshConCount]; option.offMeshConRad = new float[option.offMeshConCount];
option.offMeshConDir = new int[option.offMeshConCount]; option.offMeshConDir = new int[option.offMeshConCount];
option.offMeshConAreas = new int[option.offMeshConCount]; option.offMeshConAreas = new int[option.offMeshConCount];
option.offMeshConFlags = new int[option.offMeshConCount]; option.offMeshConFlags = new int[option.offMeshConCount];
option.offMeshConUserID = new int[option.offMeshConCount]; option.offMeshConUserID = new int[option.offMeshConCount];
option.offMeshConCount = offMeshConnections.Count;
for (int i = 0; i < option.offMeshConCount; i++) for (int i = 0; i < option.offMeshConCount; i++)
{ {
RcOffMeshConnection offMeshCon = offMeshConnections[i]; RcOffMeshConnection offMeshCon = offMeshConnections[i];
@ -65,7 +65,7 @@ namespace DotRecast.Recast.Toolset.Geom
option.offMeshConDir[i] = offMeshCon.bidir ? 1 : 0; option.offMeshConDir[i] = offMeshCon.bidir ? 1 : 0;
option.offMeshConAreas[i] = offMeshCon.area; option.offMeshConAreas[i] = offMeshCon.area;
option.offMeshConFlags[i] = offMeshCon.flags; option.offMeshConFlags[i] = offMeshCon.flags;
option.offMeshConUserID[i] = offMeshCon.userId; // option.offMeshConUserID[i] = offMeshCon.userId;
} }
} }
} }

View File

@ -1,7 +1,7 @@
/* /*
Copyright (c) 2009-2010 Mikko Mononen memon@inside.org Copyright (c) 2009-2010 Mikko Mononen memon@inside.org
recast4j copyright (c) 2015-2019 Piotr Piastucki piotr@jtilia.org recast4j copyright (c) 2015-2019 Piotr Piastucki piotr@jtilia.org
DotRecast Copyright (c) 2023-2024 Choi Ikpil ikpil@naver.com DotRecast Copyright (c) 2023 Choi Ikpil ikpil@naver.com
This software is provided 'as-is', without any express or implied This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages warranty. In no event will the authors be held liable for any damages
@ -41,7 +41,7 @@ namespace DotRecast.Recast.Toolset.Geom
public static DemoInputGeomProvider LoadFile(string objFilePath) public static DemoInputGeomProvider LoadFile(string objFilePath)
{ {
byte[] chunk = RcIO.ReadFileIfFound(objFilePath); byte[] chunk = RcResources.Load(objFilePath);
var context = RcObjImporter.LoadContext(chunk); var context = RcObjImporter.LoadContext(chunk);
return new DemoInputGeomProvider(context.vertexPositions, context.meshFaces); return new DemoInputGeomProvider(context.vertexPositions, context.meshFaces);
} }
@ -57,12 +57,12 @@ namespace DotRecast.Recast.Toolset.Geom
this.faces = faces; this.faces = faces;
normals = new float[faces.Length]; normals = new float[faces.Length];
CalculateNormals(); CalculateNormals();
bmin = new RcVec3f(vertices); bmin = RcVecUtils.Create(vertices);
bmax = new RcVec3f(vertices); bmax = RcVecUtils.Create(vertices);
for (int i = 1; i < vertices.Length / 3; i++) for (int i = 1; i < vertices.Length / 3; i++)
{ {
bmin = RcVec3f.Min(bmin, RcVec.Create(vertices, i * 3)); bmin = RcVecUtils.Min(bmin, vertices, i * 3);
bmax = RcVec3f.Max(bmax, RcVec.Create(vertices, i * 3)); bmax = RcVecUtils.Max(bmax, vertices, i * 3);
} }
_mesh = new RcTriMesh(vertices, faces); _mesh = new RcTriMesh(vertices, faces);
@ -87,11 +87,11 @@ namespace DotRecast.Recast.Toolset.Geom
{ {
for (int i = 0; i < faces.Length; i += 3) for (int i = 0; i < faces.Length; i += 3)
{ {
RcVec3f v0 = RcVec.Create(vertices, faces[i] * 3); int v0 = faces[i] * 3;
RcVec3f v1 = RcVec.Create(vertices, faces[i + 1] * 3); int v1 = faces[i + 1] * 3;
RcVec3f v2 = RcVec.Create(vertices, faces[i + 2] * 3); int v2 = faces[i + 2] * 3;
RcVec3f e0 = v1 - v0; var e0 = RcVecUtils.Subtract(vertices, v1, v0);
RcVec3f e1 = v2 - v0; var e1 = RcVecUtils.Subtract(vertices, v2, v0);
normals[i] = e0.Y * e1.Z - e0.Z * e1.Y; normals[i] = e0.Y * e1.Z - e0.Z * e1.Y;
normals[i + 1] = e0.Z * e1.X - e0.X * e1.Z; normals[i + 1] = e0.Z * e1.X - e0.X * e1.Z;
@ -150,7 +150,7 @@ namespace DotRecast.Recast.Toolset.Geom
q.X = src.X + (dst.X - src.X) * btmax; q.X = src.X + (dst.X - src.X) * btmax;
q.Y = src.Z + (dst.Z - src.Z) * btmax; q.Y = src.Z + (dst.Z - src.Z) * btmax;
List<RcChunkyTriMeshNode> chunks = RcChunkyTriMeshs.GetChunksOverlappingSegment(_mesh.chunkyTriMesh, p, q); List<RcChunkyTriMeshNode> chunks = _mesh.chunkyTriMesh.GetChunksOverlappingSegment(p, q);
if (0 == chunks.Count) if (0 == chunks.Count)
{ {
return false; return false;

View File

@ -19,7 +19,7 @@ namespace DotRecast.Recast.Toolset.Gizmos
0.5f * (start.Z + end.Z) 0.5f * (start.Z + end.Z)
}; };
RcVec3f axis = new RcVec3f(end.X - start.X, end.Y - start.Y, end.Z - start.Z); RcVec3f axis = new RcVec3f(end.X - start.X, end.Y - start.Y, end.Z - start.Z);
Span<RcVec3f> normals = stackalloc RcVec3f[3]; RcVec3f[] normals = new RcVec3f[3];
normals[1] = new RcVec3f(end.X - start.X, end.Y - start.Y, end.Z - start.Z); normals[1] = new RcVec3f(end.X - start.X, end.Y - start.Y, end.Z - start.Z);
normals[1] = RcVec3f.Normalize(normals[1]); normals[1] = RcVec3f.Normalize(normals[1]);
normals[0] = GetSideVector(axis); normals[0] = GetSideVector(axis);

View File

@ -1,4 +1,4 @@
namespace DotRecast.Recast.Toolset.Gizmos namespace DotRecast.Recast.Toolset.Gizmos
{ {
public class RcCompositeGizmo : IRcGizmoMeshFilter public class RcCompositeGizmo : IRcGizmoMeshFilter
{ {

View File

@ -19,7 +19,7 @@ namespace DotRecast.Recast.Toolset.Gizmos
0.5f * (start.Z + end.Z) 0.5f * (start.Z + end.Z)
); );
RcVec3f axis = new RcVec3f(end.X - start.X, end.Y - start.Y, end.Z - start.Z); RcVec3f axis = new RcVec3f(end.X - start.X, end.Y - start.Y, end.Z - start.Z);
Span<RcVec3f> normals = stackalloc RcVec3f[3]; RcVec3f[] normals = new RcVec3f[3];
normals[1] = new RcVec3f(end.X - start.X, end.Y - start.Y, end.Z - start.Z); normals[1] = new RcVec3f(end.X - start.X, end.Y - start.Y, end.Z - start.Z);
normals[1] = RcVec3f.Normalize(normals[1]); normals[1] = RcVec3f.Normalize(normals[1]);
normals[0] = GetSideVector(axis); normals[0] = GetSideVector(axis);

View File

@ -1,4 +1,4 @@
using DotRecast.Detour.Dynamic.Colliders; using DotRecast.Detour.Dynamic.Colliders;
using DotRecast.Recast.Toolset.Gizmos; using DotRecast.Recast.Toolset.Gizmos;
namespace DotRecast.Recast.Toolset.Gizmos namespace DotRecast.Recast.Toolset.Gizmos

View File

@ -1,4 +1,4 @@
using DotRecast.Core.Numerics; using DotRecast.Core.Numerics;
namespace DotRecast.Recast.Toolset.Gizmos namespace DotRecast.Recast.Toolset.Gizmos
{ {

View File

@ -4,8 +4,8 @@ namespace DotRecast.Recast.Toolset.Gizmos
{ {
public static class RcGizmoHelper public static class RcGizmoHelper
{ {
private const int SEGMENTS = 16; private static readonly int SEGMENTS = 16;
private const int RINGS = 8; private static readonly int RINGS = 8;
private static float[] sphericalVertices; private static float[] sphericalVertices;

View File

@ -1,6 +1,7 @@
using DotRecast.Core.Numerics; using DotRecast.Core.Numerics;
using static DotRecast.Recast.Toolset.Gizmos.RcGizmoHelper; using static DotRecast.Recast.Toolset.Gizmos.RcGizmoHelper;
namespace DotRecast.Recast.Toolset.Gizmos namespace DotRecast.Recast.Toolset.Gizmos
{ {
public class RcSphereGizmo : IRcGizmoMeshFilter public class RcSphereGizmo : IRcGizmoMeshFilter

View File

@ -1,4 +1,4 @@
namespace DotRecast.Recast.Toolset.Gizmos namespace DotRecast.Recast.Toolset.Gizmos
{ {
public class RcTrimeshGizmo : IRcGizmoMeshFilter public class RcTrimeshGizmo : IRcGizmoMeshFilter
{ {

View File

@ -1,4 +1,4 @@
namespace DotRecast.Recast.Toolset namespace DotRecast.Recast.Toolset
{ {
public interface IRcToolable public interface IRcToolable
{ {

View File

@ -1,4 +1,4 @@
namespace DotRecast.Recast.Toolset namespace DotRecast.Recast.Toolset
{ {
public class RcNavMeshBuildSettings public class RcNavMeshBuildSettings
{ {
@ -31,8 +31,5 @@ namespace DotRecast.Recast.Toolset
public bool tiled = false; public bool tiled = false;
public int tileSize = 32; public int tileSize = 32;
public bool keepInterResults = true; // full memory
public bool buildAll = true;
} }
} }

View File

@ -1,4 +1,4 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using DotRecast.Core; using DotRecast.Core;
using DotRecast.Core.Numerics; using DotRecast.Core.Numerics;
@ -74,7 +74,7 @@ namespace DotRecast.Recast.Toolset.Tools
} }
public bool TryRemove(IInputGeomProvider geom, RcVec3f pos, out RcConvexVolume volume) public RcConvexVolume RemoveByPos(IInputGeomProvider geom, RcVec3f pos)
{ {
// Delete // Delete
int nearestIndex = -1; int nearestIndex = -1;
@ -90,57 +90,26 @@ namespace DotRecast.Recast.Toolset.Tools
// If end point close enough, delete it. // If end point close enough, delete it.
if (nearestIndex == -1) if (nearestIndex == -1)
{ return null;
volume = null;
return false;
}
var removal = geom.ConvexVolumes()[nearestIndex]; var removal = geom.ConvexVolumes()[nearestIndex];
geom.ConvexVolumes().RemoveAt(nearestIndex); geom.ConvexVolumes().RemoveAt(nearestIndex);
volume = removal; return removal;
return null != volume;
} }
public bool TryAdd(IInputGeomProvider geom, RcVec3f p, RcAreaModification areaType, float boxDescent, float boxHeight, float polyOffset, out RcConvexVolume volume) public void Add(IInputGeomProvider geom, RcConvexVolume 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); 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) 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. // Create shape.
float[] verts = new float[hull.Count * 3]; float[] verts = new float[hull.Count * 3];
for (int i = 0; i < hull.Count; ++i) for (int i = 0; i < hull.Count; ++i)
@ -165,7 +134,7 @@ namespace DotRecast.Recast.Toolset.Tools
int noffset = RcAreas.OffsetPoly(verts, hull.Count, polyOffset, offset, offset.Length); int noffset = RcAreas.OffsetPoly(verts, hull.Count, polyOffset, offset, offset.Length);
if (noffset > 0) if (noffset > 0)
{ {
verts = RcArrays.CopyOf(offset, 0, noffset * 3); verts = RcArrayUtils.CopyOf(offset, 0, noffset * 3);
} }
} }

View File

@ -1,4 +1,4 @@
using DotRecast.Core.Numerics; using DotRecast.Core.Numerics;
namespace DotRecast.Recast.Toolset.Tools namespace DotRecast.Recast.Toolset.Tools
{ {

View File

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

View File

@ -1,4 +1,4 @@
namespace DotRecast.Recast.Toolset.Tools namespace DotRecast.Recast.Toolset.Tools
{ {
public class RcCrowdAgentProfilingToolConfig public class RcCrowdAgentProfilingToolConfig
{ {
@ -12,7 +12,5 @@ namespace DotRecast.Recast.Toolset.Tools
public float percentTravellers = 15f; public float percentTravellers = 15f;
public int pathQueueSize = 32; public int pathQueueSize = 32;
public int maxIterations = 300; public int maxIterations = 300;
public bool showAgents = true;
} }
} }

View File

@ -1,4 +1,4 @@
namespace DotRecast.Recast.Toolset.Tools namespace DotRecast.Recast.Toolset.Tools
{ {
public class RcCrowdAgentTrail public class RcCrowdAgentTrail
{ {

View File

@ -1,4 +1,4 @@
namespace DotRecast.Recast.Toolset.Tools namespace DotRecast.Recast.Toolset.Tools
{ {
public enum RcCrowdAgentType public enum RcCrowdAgentType
{ {

View File

@ -1,4 +1,4 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using DotRecast.Core; using DotRecast.Core;
using DotRecast.Core.Numerics; using DotRecast.Core.Numerics;
@ -297,7 +297,7 @@ namespace DotRecast.Recast.Toolset.Tools
RcVec3f vel = RcVec3f.Subtract(tgt, pos); RcVec3f vel = RcVec3f.Subtract(tgt, pos);
vel.Y = 0.0f; vel.Y = 0.0f;
vel = RcVec3f.Normalize(vel); vel = RcVec3f.Normalize(vel);
return vel * speed; return vel.Scale(speed);
} }
public long GetCrowdUpdateTime() public long GetCrowdUpdateTime()

View File

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

View File

@ -1,4 +1,4 @@
namespace DotRecast.Recast.Toolset.Tools namespace DotRecast.Recast.Toolset.Tools
{ {
public enum RcDynamicColliderShape public enum RcDynamicColliderShape
{ {

View File

@ -1,4 +1,4 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;
using System.Threading.Tasks; using System.Threading.Tasks;
@ -18,12 +18,12 @@ namespace DotRecast.Recast.Toolset.Tools
private DtDynamicNavMesh dynaMesh; private DtDynamicNavMesh dynaMesh;
private readonly Dictionary<long, RcGizmo> colliderGizmos; private readonly Dictionary<long, RcGizmo> colliderGizmos;
private readonly IRcRand random; private readonly Random random;
private readonly DemoInputGeomProvider bridgeGeom; private readonly DemoInputGeomProvider bridgeGeom;
private readonly DemoInputGeomProvider houseGeom; private readonly DemoInputGeomProvider houseGeom;
private readonly DemoInputGeomProvider convexGeom; private readonly DemoInputGeomProvider convexGeom;
public RcDynamicUpdateTool(IRcRand rand, DemoInputGeomProvider bridgeGeom, DemoInputGeomProvider houseGeom, DemoInputGeomProvider convexGeom) public RcDynamicUpdateTool(Random rand, DemoInputGeomProvider bridgeGeom, DemoInputGeomProvider houseGeom, DemoInputGeomProvider convexGeom)
{ {
this.colliderGizmos = new Dictionary<long, RcGizmo>(); this.colliderGizmos = new Dictionary<long, RcGizmo>();
this.random = rand; this.random = rand;
@ -141,16 +141,6 @@ namespace DotRecast.Recast.Toolset.Tools
return colliderWithGizmo; return colliderWithGizmo;
} }
public DtDynamicNavMesh Copy(RcConfig cfg, IList<RcBuilderResult> results)
{
var voxelFile = DtVoxelFile.From(cfg, results);
dynaMesh = new DtDynamicNavMesh(voxelFile);
dynaMesh.config.keepIntermediateResults = true;
colliderGizmos.Clear();
return dynaMesh;
}
public DtDynamicNavMesh Load(string filename, IRcCompressor compressor) public DtDynamicNavMesh Load(string filename, IRcCompressor compressor)
{ {
using var fs = new FileStream(filename, FileMode.Open, FileAccess.Read, FileShare.Read); using var fs = new FileStream(filename, FileMode.Open, FileAccess.Read, FileShare.Read);
@ -169,7 +159,7 @@ namespace DotRecast.Recast.Toolset.Tools
public void Save(string filename, bool compression, IRcCompressor compressor) public void Save(string filename, bool compression, IRcCompressor compressor)
{ {
DtVoxelFile voxelFile = DtVoxelFile.From(dynaMesh); DtVoxelFile voxelFile = DtVoxelFile.From(dynaMesh);
using var fs = new FileStream(filename, FileMode.OpenOrCreate, FileAccess.Write, FileShare.ReadWrite); using var fs = new FileStream(filename, FileMode.CreateNew, FileAccess.Write);
using var bw = new BinaryWriter(fs); using var bw = new BinaryWriter(fs);
DtVoxelFileWriter writer = new DtVoxelFileWriter(compressor); DtVoxelFileWriter writer = new DtVoxelFileWriter(compressor);
writer.Write(bw, voxelFile, compression); writer.Write(bw, voxelFile, compression);
@ -349,14 +339,20 @@ namespace DotRecast.Recast.Toolset.Tools
return resultvector; return resultvector;
} }
public bool Update(TaskFactory executor) public bool UpdateDynaMesh(TaskFactory executor)
{ {
if (dynaMesh == null) if (dynaMesh == null)
{ {
return false; return false;
} }
return dynaMesh.Update(executor); bool updated = dynaMesh.Update(executor).Result;
if (updated)
{
return false;
}
return true;
} }
public bool Raycast(RcVec3f spos, RcVec3f epos, out float hitPos, out RcVec3f raycastHitPos) public bool Raycast(RcVec3f spos, RcVec3f epos, out float hitPos, out RcVec3f raycastHitPos)

View File

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

View File

@ -1,4 +1,4 @@
using System.Collections.Generic; using System.Collections.Generic;
using DotRecast.Core.Numerics; using DotRecast.Core.Numerics;
using DotRecast.Detour.Extras.Jumplink; using DotRecast.Detour.Extras.Jumplink;
using DotRecast.Recast.Geom; using DotRecast.Recast.Geom;
@ -116,7 +116,7 @@ namespace DotRecast.Recast.Toolset.Tools
{ {
RcVec3f p = link.startSamples[i].p; RcVec3f p = link.startSamples[i].p;
RcVec3f q = link.endSamples[i].p; RcVec3f q = link.endSamples[i].p;
if (i == 0 || RcVec.Dist2D(prev, p) > agentRadius) if (i == 0 || RcVecUtils.Dist2D(prev, p) > agentRadius)
{ {
geom.AddOffMeshConnection(p, q, agentRadius, false, area, flags); geom.AddOffMeshConnection(p, q, agentRadius, false, area, flags);
prev = p; prev = p;

View File

@ -1,4 +1,4 @@
using System; using System;
using System.Linq; using System.Linq;
using DotRecast.Core; using DotRecast.Core;
using DotRecast.Core.Collections; using DotRecast.Core.Collections;
@ -42,7 +42,7 @@ namespace DotRecast.Recast.Toolset.Tools
// Init cache // Init cache
var bmin = geom.GetMeshBoundsMin(); var bmin = geom.GetMeshBoundsMin();
var bmax = geom.GetMeshBoundsMax(); var bmax = geom.GetMeshBoundsMax();
RcRecast.CalcGridSize(bmin, bmax, setting.cellSize, out var gw, out var gh); RcCommons.CalcGridSize(bmin, bmax, setting.cellSize, out var gw, out var gh);
int ts = setting.tileSize; int ts = setting.tileSize;
int tw = (gw + ts - 1) / ts; int tw = (gw + ts - 1) / ts;
int th = (gh + ts - 1) / ts; int th = (gh + ts - 1) / ts;
@ -78,7 +78,7 @@ namespace DotRecast.Recast.Toolset.Tools
_tc.BuildNavMeshTile(refs); _tc.BuildNavMeshTile(refs);
} }
return new NavMeshBuildResult(cfg, RcImmutableArray<RcBuilderResult>.Empty, _tc.GetNavMesh()); return new NavMeshBuildResult(RcImmutableArray<RcBuilderResult>.Empty, _tc.GetNavMesh());
} }
public void ClearAllTempObstacles() public void ClearAllTempObstacles()
@ -141,8 +141,7 @@ namespace DotRecast.Recast.Toolset.Tools
navMeshParams.maxTiles = 256; // .. navMeshParams.maxTiles = 256; // ..
navMeshParams.maxPolys = 16384; navMeshParams.maxPolys = 16384;
var navMesh = new DtNavMesh(); var navMesh = new DtNavMesh(navMeshParams, 6);
navMesh.Init(navMeshParams, 6);
var comp = _comp.Create(cCompatibility ? 0 : 1); var comp = _comp.Create(cCompatibility ? 0 : 1);
var storageParams = new DtTileCacheStorageParams(order, cCompatibility); var storageParams = new DtTileCacheStorageParams(order, cCompatibility);
DtTileCache tc = new DtTileCache(option, storageParams, navMesh, comp, _proc); DtTileCache tc = new DtTileCache(option, storageParams, navMesh, comp, _proc);

View File

@ -1,4 +1,4 @@
using System; using System;
using DotRecast.Core.Numerics; using DotRecast.Core.Numerics;
using DotRecast.Recast.Geom; using DotRecast.Recast.Geom;
using DotRecast.Recast.Toolset.Builder; using DotRecast.Recast.Toolset.Builder;
@ -34,7 +34,7 @@ namespace DotRecast.Recast.Toolset.Tools
RcOffMeshConnection nearestConnection = null; RcOffMeshConnection nearestConnection = null;
foreach (RcOffMeshConnection offMeshCon in geom.GetOffMeshConnections()) foreach (RcOffMeshConnection offMeshCon in geom.GetOffMeshConnections())
{ {
float d = Math.Min(RcVec.DistanceSquared(p, offMeshCon.verts, 0), RcVec.DistanceSquared(p, offMeshCon.verts, 3)); float d = Math.Min(RcVecUtils.DistanceSquared(p, offMeshCon.verts, 0), RcVecUtils.DistanceSquared(p, offMeshCon.verts, 3));
if (d < nearestDist && Math.Sqrt(d) < settings.agentRadius) if (d < nearestDist && Math.Sqrt(d) < settings.agentRadius)
{ {
nearestDist = d; nearestDist = d;

View File

@ -1,4 +1,4 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using DotRecast.Core; using DotRecast.Core;
using DotRecast.Core.Numerics; using DotRecast.Core.Numerics;
@ -11,6 +11,7 @@ namespace DotRecast.Recast.Toolset.Tools
public const int MAX_POLYS = 256; public const int MAX_POLYS = 256;
public const int MAX_SMOOTH = 2048; public const int MAX_SMOOTH = 2048;
public RcTestNavMeshTool() public RcTestNavMeshTool()
{ {
} }
@ -21,52 +22,45 @@ namespace DotRecast.Recast.Toolset.Tools
} }
public DtStatus FindFollowPath(DtNavMesh navMesh, DtNavMeshQuery navQuery, long startRef, long endRef, RcVec3f startPt, RcVec3f endPt, IDtQueryFilter filter, bool enableRaycast, public DtStatus FindFollowPath(DtNavMesh navMesh, DtNavMeshQuery navQuery, long startRef, long endRef, RcVec3f startPt, RcVec3f endPt, IDtQueryFilter filter, bool enableRaycast,
ref List<long> pathIterPolys, int pathIterPolyCount, ref List<RcVec3f> smoothPath) ref List<long> polys, ref List<RcVec3f> smoothPath)
{ {
if (startRef == 0 || endRef == 0) if (startRef == 0 || endRef == 0)
{ {
pathIterPolys?.Clear(); polys?.Clear();
smoothPath?.Clear(); smoothPath?.Clear();
return DtStatus.DT_FAILURE; return DtStatus.DT_FAILURE;
} }
pathIterPolys ??= new List<long>(); polys ??= new List<long>();
smoothPath ??= new List<RcVec3f>(); smoothPath ??= new List<RcVec3f>();
pathIterPolys.Clear(); polys.Clear();
pathIterPolyCount = 0;
smoothPath.Clear(); smoothPath.Clear();
var opt = new DtFindPathOption(enableRaycast ? DtFindPathOptions.DT_FINDPATH_ANY_ANGLE : 0, float.MaxValue); var opt = new DtFindPathOption(enableRaycast ? DtFindPathOptions.DT_FINDPATH_ANY_ANGLE : 0, float.MaxValue);
navQuery.FindPath(startRef, endRef, startPt, endPt, filter, ref pathIterPolys, opt); navQuery.FindPath(startRef, endRef, startPt, endPt, filter, ref polys, opt);
if (0 >= pathIterPolys.Count) if (0 >= polys.Count)
return DtStatus.DT_FAILURE; return DtStatus.DT_FAILURE;
pathIterPolyCount = pathIterPolys.Count;
// Iterate over the path to find smooth path on the detail mesh surface. // Iterate over the path to find smooth path on the detail mesh surface.
navQuery.ClosestPointOnPoly(startRef, startPt, out var iterPos, out var _); navQuery.ClosestPointOnPoly(startRef, startPt, out var iterPos, out var _);
navQuery.ClosestPointOnPoly(pathIterPolys[pathIterPolys.Count - 1], endPt, out var targetPos, out var _); navQuery.ClosestPointOnPoly(polys[polys.Count - 1], endPt, out var targetPos, out var _);
const float STEP_SIZE = 0.5f; float STEP_SIZE = 0.5f;
const float SLOP = 0.01f; float SLOP = 0.01f;
smoothPath.Clear(); smoothPath.Clear();
smoothPath.Add(iterPos); smoothPath.Add(iterPos);
var visited = new List<long>();
Span<long> visited = stackalloc long[16];
int nvisited = 0;
// Move towards target a small advancement at a time until target reached or // Move towards target a small advancement at a time until target reached or
// when ran out of memory to store the path. // when ran out of memory to store the path.
while (0 < pathIterPolyCount && smoothPath.Count < MAX_SMOOTH) while (0 < polys.Count && smoothPath.Count < MAX_SMOOTH)
{ {
// Find location to steer towards. // Find location to steer towards.
if (!DtPathUtils.GetSteerTarget(navQuery, iterPos, targetPos, SLOP, if (!DtPathUtils.GetSteerTarget(navQuery, iterPos, targetPos, SLOP,
pathIterPolys, pathIterPolyCount, out var steerPos, out var steerPosFlag, out var steerPosRef)) polys, out var steerPos, out var steerPosFlag, out var steerPosRef))
{ {
break; break;
} }
@ -91,17 +85,17 @@ namespace DotRecast.Recast.Toolset.Tools
len = STEP_SIZE / len; len = STEP_SIZE / len;
} }
RcVec3f moveTgt = RcVec.Mad(iterPos, delta, len); RcVec3f moveTgt = RcVecUtils.Mad(iterPos, delta, len);
// Move // Move
navQuery.MoveAlongSurface(pathIterPolys[0], iterPos, moveTgt, filter, out var result, visited, out nvisited, 16); navQuery.MoveAlongSurface(polys[0], iterPos, moveTgt, filter, out var result, ref visited);
iterPos = result; iterPos = result;
pathIterPolyCount = DtPathUtils.MergeCorridorStartMoved(pathIterPolys, pathIterPolyCount, MAX_POLYS, visited, nvisited); polys = DtPathUtils.MergeCorridorStartMoved(polys, visited);
pathIterPolyCount = DtPathUtils.FixupShortcuts(ref pathIterPolys, pathIterPolyCount, navQuery); polys = DtPathUtils.FixupShortcuts(polys, navQuery);
var status = navQuery.GetPolyHeight(pathIterPolys[0], result, out var h); var status = navQuery.GetPolyHeight(polys[0], result, out var h);
if (status.Succeeded()) if (status.Succeeded())
{ {
iterPos.Y = h; iterPos.Y = h;
@ -127,17 +121,16 @@ namespace DotRecast.Recast.Toolset.Tools
// Advance the path up to and over the off-mesh connection. // Advance the path up to and over the off-mesh connection.
long prevRef = 0; long prevRef = 0;
long polyRef = pathIterPolys[0]; long polyRef = polys[0];
int npos = 0; int npos = 0;
while (npos < pathIterPolyCount && polyRef != steerPosRef) while (npos < polys.Count && polyRef != steerPosRef)
{ {
prevRef = polyRef; prevRef = polyRef;
polyRef = pathIterPolys[npos]; polyRef = polys[npos];
npos++; npos++;
} }
pathIterPolys = pathIterPolys.GetRange(npos, pathIterPolys.Count - npos); polys = polys.GetRange(npos, polys.Count - npos);
pathIterPolyCount -= npos;
// Handle the connection. // Handle the connection.
var status2 = navMesh.GetOffMeshConnectionPolyEndPoints(prevRef, polyRef, ref startPos, ref endPos); var status2 = navMesh.GetOffMeshConnectionPolyEndPoints(prevRef, polyRef, ref startPos, ref endPos);
@ -155,7 +148,7 @@ namespace DotRecast.Recast.Toolset.Tools
// Move position at the other side of the off-mesh link. // Move position at the other side of the off-mesh link.
iterPos = endPos; iterPos = endPos;
navQuery.GetPolyHeight(pathIterPolys[0], iterPos, out var eh); navQuery.GetPolyHeight(polys[0], iterPos, out var eh);
iterPos.Y = eh; iterPos.Y = eh;
} }
} }
@ -167,19 +160,19 @@ namespace DotRecast.Recast.Toolset.Tools
} }
} }
return DtStatus.DT_SUCCESS; return DtStatus.DT_SUCCSESS;
} }
public DtStatus FindStraightPath(DtNavMeshQuery navQuery, long startRef, long endRef, RcVec3f startPt, RcVec3f endPt, IDtQueryFilter filter, bool enableRaycast, public DtStatus FindStraightPath(DtNavMeshQuery navQuery, long startRef, long endRef, RcVec3f startPt, RcVec3f endPt, IDtQueryFilter filter, bool enableRaycast,
ref List<long> polys, Span<DtStraightPath> straightPath, out int straightPathCount, int maxStraightPath, int straightPathOptions) ref List<long> polys, ref List<DtStraightPath> straightPath, int straightPathOptions)
{ {
straightPathCount = 0;
if (startRef == 0 || endRef == 0) if (startRef == 0 || endRef == 0)
{ {
return DtStatus.DT_FAILURE; return DtStatus.DT_FAILURE;
} }
polys ??= new List<long>(); polys ??= new List<long>();
straightPath ??= new List<DtStraightPath>();
polys.Clear(); polys.Clear();
straightPath.Clear(); straightPath.Clear();
@ -201,9 +194,9 @@ namespace DotRecast.Recast.Toolset.Tools
} }
} }
navQuery.FindStraightPath(startPt, epos, polys, polys.Count, straightPath, out straightPathCount, maxStraightPath, straightPathOptions); navQuery.FindStraightPath(startPt, epos, polys, ref straightPath, MAX_POLYS, straightPathOptions);
return DtStatus.DT_SUCCESS; return DtStatus.DT_SUCCSESS;
} }
public DtStatus InitSlicedFindPath(DtNavMeshQuery navQuery, long startRef, long endRef, RcVec3f startPos, RcVec3f endPos, IDtQueryFilter filter, bool enableRaycast) public DtStatus InitSlicedFindPath(DtNavMeshQuery navQuery, long startRef, long endRef, RcVec3f startPos, RcVec3f endPos, IDtQueryFilter filter, bool enableRaycast)
@ -220,9 +213,8 @@ namespace DotRecast.Recast.Toolset.Tools
} }
public DtStatus UpdateSlicedFindPath(DtNavMeshQuery navQuery, int maxIter, long endRef, RcVec3f startPos, RcVec3f endPos, public DtStatus UpdateSlicedFindPath(DtNavMeshQuery navQuery, int maxIter, long endRef, RcVec3f startPos, RcVec3f endPos,
ref List<long> path, Span<DtStraightPath> straightPath, out int straightPathCount, int maxStraightPath) ref List<long> path, ref List<DtStraightPath> straightPath)
{ {
straightPathCount = 0;
var status = navQuery.UpdateSlicedFindPath(maxIter, out _); var status = navQuery.UpdateSlicedFindPath(maxIter, out _);
if (!status.Succeeded()) if (!status.Succeeded())
@ -232,6 +224,7 @@ namespace DotRecast.Recast.Toolset.Tools
navQuery.FinalizeSlicedFindPath(ref path); navQuery.FinalizeSlicedFindPath(ref path);
straightPath?.Clear();
if (path != null) if (path != null)
{ {
// In case of partial path, make sure the end point is clamped to the last polygon. // In case of partial path, make sure the end point is clamped to the last polygon.
@ -245,34 +238,35 @@ namespace DotRecast.Recast.Toolset.Tools
} }
} }
navQuery.FindStraightPath(startPos, epos, path, path.Count, straightPath, out straightPathCount, maxStraightPath, DtStraightPathOptions.DT_STRAIGHTPATH_ALL_CROSSINGS); straightPath = new List<DtStraightPath>(MAX_POLYS);
navQuery.FindStraightPath(startPos, epos, path, ref straightPath, MAX_POLYS, DtStraightPathOptions.DT_STRAIGHTPATH_ALL_CROSSINGS);
} }
return DtStatus.DT_SUCCESS; return DtStatus.DT_SUCCSESS;
} }
public DtStatus Raycast(DtNavMeshQuery navQuery, long startRef, long endRef, RcVec3f startPos, RcVec3f endPos, IDtQueryFilter filter, public DtStatus Raycast(DtNavMeshQuery navQuery, long startRef, long endRef, RcVec3f startPos, RcVec3f endPos, IDtQueryFilter filter,
ref List<long> polys, Span<DtStraightPath> straightPath, out int straightPathCount, int maxStraightPath, ref RcVec3f hitPos, ref RcVec3f hitNormal, ref bool hitResult) ref List<long> polys, ref List<DtStraightPath> straightPath, ref RcVec3f hitPos, ref RcVec3f hitNormal, ref bool hitResult)
{ {
straightPathCount = 0;
if (startRef == 0 || endRef == 0) if (startRef == 0 || endRef == 0)
{ {
polys?.Clear(); polys?.Clear();
straightPath?.Clear();
return DtStatus.DT_FAILURE; return DtStatus.DT_FAILURE;
} }
var path = new List<long>(); var status = navQuery.Raycast(startRef, startPos, endPos, filter, 0, 0, out var rayHit);
var status = navQuery.Raycast(startRef, startPos, endPos, filter, out var t, out var hitNormal2, ref path);
if (!status.Succeeded()) if (!status.Succeeded())
{ {
return status; return status;
} }
// results ... // results ...
polys = path; polys = rayHit.path;
if (t >= 1) if (rayHit.t > 1)
{ {
// No hit // No hit
hitPos = endPos; hitPos = endPos;
@ -281,23 +275,25 @@ namespace DotRecast.Recast.Toolset.Tools
else else
{ {
// Hit // Hit
hitPos = RcVec3f.Lerp(startPos, endPos, t); hitPos = RcVec3f.Lerp(startPos, endPos, rayHit.t);
hitNormal = hitNormal2; hitNormal = rayHit.hitNormal;
hitResult = true; hitResult = true;
} }
// Adjust height. // Adjust height.
if (path.Count > 0) if (rayHit.path.Count > 0)
{ {
var result = navQuery.GetPolyHeight(path[path.Count - 1], hitPos, out var h); var result = navQuery.GetPolyHeight(rayHit.path[rayHit.path.Count - 1], hitPos, out var h);
if (result.Succeeded()) if (result.Succeeded())
{ {
hitPos.Y = h; hitPos.Y = h;
} }
} }
straightPath[straightPathCount++] = new DtStraightPath(startPos, 0, 0); straightPath ??= new List<DtStraightPath>();
straightPath[straightPathCount++] = new DtStraightPath(hitPos, 0, 0); straightPath.Clear();
straightPath.Add(new DtStraightPath(startPos, 0, 0));
straightPath.Add(new DtStraightPath(hitPos, 0, 0));
return status; return status;
} }
@ -380,7 +376,7 @@ namespace DotRecast.Recast.Toolset.Tools
float nx = (epos.Z - spos.Z) * 0.25f; float nx = (epos.Z - spos.Z) * 0.25f;
float nz = -(epos.X - spos.X) * 0.25f; float nz = -(epos.X - spos.X) * 0.25f;
RcVec3f[] tempQueryPoly = new RcVec3f[4]; var tempQueryPoly = new RcVec3f[4];
tempQueryPoly[0].X = spos.X + nx * 1.2f; tempQueryPoly[0].X = spos.X + nx * 1.2f;
tempQueryPoly[0].Y = spos.Y + agentHeight / 2; tempQueryPoly[0].Y = spos.Y + agentHeight / 2;
tempQueryPoly[0].Z = spos.Z + nz * 1.2f; tempQueryPoly[0].Z = spos.Z + nz * 1.2f;
@ -442,7 +438,7 @@ namespace DotRecast.Recast.Toolset.Tools
} }
} }
return DtStatus.DT_SUCCESS; return DtStatus.DT_SUCCSESS;
} }
} }
} }

View File

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

View File

@ -1,4 +1,4 @@
using System.Linq; using System.Linq;
using DotRecast.Core; using DotRecast.Core;
using DotRecast.Core.Collections; using DotRecast.Core.Collections;
using DotRecast.Core.Numerics; using DotRecast.Core.Numerics;
@ -23,7 +23,7 @@ namespace DotRecast.Recast.Toolset.Tools
var bmin = geom.GetMeshBoundsMin(); var bmin = geom.GetMeshBoundsMin();
var bmax = geom.GetMeshBoundsMax(); var bmax = geom.GetMeshBoundsMax();
int gw = 0, gh = 0; int gw = 0, gh = 0;
RcRecast.CalcGridSize(bmin, bmax, settings.cellSize, out gw, out gh); RcCommons.CalcGridSize(bmin, bmax, settings.cellSize, out gw, out gh);
int ts = settings.tileSize; int ts = settings.tileSize;
int tw = (gw + ts - 1) / ts; int tw = (gw + ts - 1) / ts;
@ -47,7 +47,7 @@ namespace DotRecast.Recast.Toolset.Tools
var bmin = geom.GetMeshBoundsMin(); var bmin = geom.GetMeshBoundsMin();
var bmax = geom.GetMeshBoundsMax(); var bmax = geom.GetMeshBoundsMax();
int gw = 0, gh = 0; int gw = 0, gh = 0;
RcRecast.CalcGridSize(bmin, bmax, settings.cellSize, out gw, out gh); RcCommons.CalcGridSize(bmin, bmax, settings.cellSize, out gw, out gh);
int ts = settings.tileSize; int ts = settings.tileSize;
int tw = (gw + ts - 1) / ts; int tw = (gw + ts - 1) / ts;
@ -68,8 +68,8 @@ namespace DotRecast.Recast.Toolset.Tools
tileTriCount = 0; // ... tileTriCount = 0; // ...
tileMemUsage = 0; // ... tileMemUsage = 0; // ...
var availableTile = navMesh.IsAvailableTileCount(); var availableTileCount = navMesh.GetAvailableTileCount();
if (!availableTile) if (0 >= availableTileCount)
{ {
return false; return false;
} }
@ -94,7 +94,7 @@ namespace DotRecast.Recast.Toolset.Tools
var beginTick = RcFrequency.Ticks; var beginTick = RcFrequency.Ticks;
var rb = new RcBuilder(); var rb = new RcBuilder();
var result = rb.BuildTile(geom, cfg, bmin, bmax, tx, ty, new RcAtomicInteger(0), 1, settings.keepInterResults); var result = rb.BuildTile(geom, cfg, bmin, bmax, tx, ty, new RcAtomicInteger(0), 1);
var tb = new TileNavMeshBuilder(); var tb = new TileNavMeshBuilder();
var meshData = tb.BuildMeshData(geom, settings.cellSize, settings.cellHeight, settings.agentHeight, settings.agentRadius, settings.agentMaxClimb, RcImmutableArray.Create(result) var meshData = tb.BuildMeshData(geom, settings.cellSize, settings.cellHeight, settings.agentHeight, settings.agentRadius, settings.agentMaxClimb, RcImmutableArray.Create(result)

View File

@ -1,16 +0,0 @@
{
"name": "DotRecast.Recast",
"rootNamespace": "DotRecast.Recast",
"references": [
"DotRecast.Core"
],
"includePlatforms": [],
"excludePlatforms": [],
"allowUnsafeCode": false,
"overrideReferences": false,
"precompiledReferences": [],
"autoReferenced": true,
"defineConstraints": [],
"versionDefines": [],
"noEngineReferences": true
}

View File

@ -1,16 +1,15 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<TargetFrameworks>netstandard2.1;net6.0;net7.0;net8.0</TargetFrameworks> <TargetFrameworks>netstandard2.1;net6.0;net7.0</TargetFrameworks>
<PackageId>DotRecast.Recast</PackageId> <PackageId>DotRecast.Recast</PackageId>
<PackageReadmeFile>README.md</PackageReadmeFile> <PackageReadmeFile>README.md</PackageReadmeFile>
<Authors>ikpil</Authors> <Authors>ikpil</Authors>
<Description>DotRecast - a port of Recast Detour, Industry-standard navigation mesh toolset for .NET, C#, Unity3D, games, servers</Description> <Description>DotRecast - a port of Recast Detour, navigation mesh toolset for games, Unity3D, servers, C#</Description>
<RepositoryType>git</RepositoryType> <RepositoryType>git</RepositoryType>
<PackageProjectUrl>https://github.com/ikpil/DotRecast</PackageProjectUrl> <PackageProjectUrl>https://github.com/ikpil/DotRecast</PackageProjectUrl>
<RepositoryUrl>https://github.com/ikpil/DotRecast</RepositoryUrl> <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> <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> </PropertyGroup>
<ItemGroup> <ItemGroup>

View File

@ -1,8 +0,0 @@
namespace DotRecast.Recast
{
public static class EdgeValues
{
public const int EV_UNDEF = -1;
public const int EV_HULL = -2;
}
}

View File

@ -1,4 +1,4 @@
using DotRecast.Core.Numerics; using DotRecast.Core.Numerics;
namespace DotRecast.Recast.Geom namespace DotRecast.Recast.Geom
{ {

View File

@ -1,4 +1,4 @@
using System.Collections.Generic; using System.Collections.Generic;
namespace DotRecast.Recast.Geom namespace DotRecast.Recast.Geom
{ {

View File

@ -1,4 +1,4 @@
using System.Collections.Generic; using System.Collections.Generic;
namespace DotRecast.Recast.Geom namespace DotRecast.Recast.Geom
{ {

View File

@ -1,7 +1,7 @@
/* /*
Copyright (c) 2009-2010 Mikko Mononen memon@inside.org Copyright (c) 2009-2010 Mikko Mononen memon@inside.org
recast4j copyright (c) 2015-2019 Piotr Piastucki piotr@jtilia.org recast4j copyright (c) 2015-2019 Piotr Piastucki piotr@jtilia.org
DotRecast Copyright (c) 2023-2024 Choi Ikpil ikpil@naver.com DotRecast Copyright (c) 2023 Choi Ikpil ikpil@naver.com
This software is provided 'as-is', without any express or implied This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages warranty. In no event will the authors be held liable for any damages

View File

@ -1,7 +1,7 @@
/* /*
Copyright (c) 2009-2010 Mikko Mononen memon@inside.org Copyright (c) 2009-2010 Mikko Mononen memon@inside.org
recast4j copyright (c) 2015-2019 Piotr Piastucki piotr@jtilia.org recast4j copyright (c) 2015-2019 Piotr Piastucki piotr@jtilia.org
DotRecast Copyright (c) 2023-2024 Choi Ikpil ikpil@naver.com DotRecast Copyright (c) 2023 Choi Ikpil ikpil@naver.com
This software is provided 'as-is', without any express or implied This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages warranty. In no event will the authors be held liable for any damages
@ -18,14 +18,278 @@ freely, subject to the following restrictions:
3. This notice may not be removed or altered from any source distribution. 3. This notice may not be removed or altered from any source distribution.
*/ */
using System;
using System.Collections.Generic; using System.Collections.Generic;
using DotRecast.Core.Numerics;
namespace DotRecast.Recast.Geom namespace DotRecast.Recast.Geom
{ {
public class RcChunkyTriMesh public class RcChunkyTriMesh
{ {
public List<RcChunkyTriMeshNode> nodes; private List<RcChunkyTriMeshNode> nodes;
public int ntris; private int ntris;
public int maxTrisPerChunk; private int maxTrisPerChunk;
private void CalcExtends(BoundsItem[] items, int imin, int imax, ref RcVec2f bmin, ref RcVec2f bmax)
{
bmin.X = items[imin].bmin.X;
bmin.Y = items[imin].bmin.Y;
bmax.X = items[imin].bmax.X;
bmax.Y = items[imin].bmax.Y;
for (int i = imin + 1; i < imax; ++i)
{
BoundsItem it = items[i];
if (it.bmin.X < bmin.X)
{
bmin.X = it.bmin.X;
}
if (it.bmin.Y < bmin.Y)
{
bmin.Y = it.bmin.Y;
}
if (it.bmax.X > bmax.X)
{
bmax.X = it.bmax.X;
}
if (it.bmax.Y > bmax.Y)
{
bmax.Y = it.bmax.Y;
}
}
}
private int LongestAxis(float x, float y)
{
return y > x ? 1 : 0;
}
private void Subdivide(BoundsItem[] items, int imin, int imax, int trisPerChunk, List<RcChunkyTriMeshNode> nodes, int[] inTris)
{
int inum = imax - imin;
RcChunkyTriMeshNode node = new RcChunkyTriMeshNode();
nodes.Add(node);
if (inum <= trisPerChunk)
{
// Leaf
CalcExtends(items, imin, imax, ref node.bmin, ref node.bmax);
// Copy triangles.
node.i = nodes.Count;
node.tris = new int[inum * 3];
int dst = 0;
for (int i = imin; i < imax; ++i)
{
int src = items[i].i * 3;
node.tris[dst++] = inTris[src];
node.tris[dst++] = inTris[src + 1];
node.tris[dst++] = inTris[src + 2];
}
}
else
{
// Split
CalcExtends(items, imin, imax, ref node.bmin, ref node.bmax);
int axis = LongestAxis(node.bmax.X - node.bmin.X, node.bmax.Y - node.bmin.Y);
if (axis == 0)
{
Array.Sort(items, imin, imax - imin, BoundsItemXComparer.Shared);
// Sort along x-axis
}
else if (axis == 1)
{
Array.Sort(items, imin, imax - imin, BoundsItemYComparer.Shared);
// Sort along y-axis
}
int isplit = imin + inum / 2;
// Left
Subdivide(items, imin, isplit, trisPerChunk, nodes, inTris);
// Right
Subdivide(items, isplit, imax, trisPerChunk, nodes, inTris);
// Negative index means escape.
node.i = -nodes.Count;
}
}
public RcChunkyTriMesh(float[] verts, int[] tris, int ntris, int trisPerChunk)
{
int nchunks = (ntris + trisPerChunk - 1) / trisPerChunk;
nodes = new List<RcChunkyTriMeshNode>(nchunks);
this.ntris = ntris;
// Build tree
BoundsItem[] items = new BoundsItem[ntris];
for (int i = 0; i < ntris; i++)
{
int t = i * 3;
BoundsItem it = items[i] = new BoundsItem();
it.i = i;
// Calc triangle XZ bounds.
it.bmin.X = it.bmax.X = verts[tris[t] * 3 + 0];
it.bmin.Y = it.bmax.Y = verts[tris[t] * 3 + 2];
for (int j = 1; j < 3; ++j)
{
int v = tris[t + j] * 3;
if (verts[v] < it.bmin.X)
{
it.bmin.X = verts[v];
}
if (verts[v + 2] < it.bmin.Y)
{
it.bmin.Y = verts[v + 2];
}
if (verts[v] > it.bmax.X)
{
it.bmax.X = verts[v];
}
if (verts[v + 2] > it.bmax.Y)
{
it.bmax.Y = verts[v + 2];
}
}
}
Subdivide(items, 0, ntris, trisPerChunk, nodes, tris);
// Calc max tris per node.
maxTrisPerChunk = 0;
foreach (RcChunkyTriMeshNode node in nodes)
{
bool isLeaf = node.i >= 0;
if (!isLeaf)
{
continue;
}
if (node.tris.Length / 3 > maxTrisPerChunk)
{
maxTrisPerChunk = node.tris.Length / 3;
}
}
}
private bool CheckOverlapRect(float[] amin, float[] amax, RcVec2f bmin, RcVec2f bmax)
{
bool overlap = true;
overlap = (amin[0] > bmax.X || amax[0] < bmin.X) ? false : overlap;
overlap = (amin[1] > bmax.Y || amax[1] < bmin.Y) ? false : overlap;
return overlap;
}
public List<RcChunkyTriMeshNode> GetChunksOverlappingRect(float[] bmin, float[] bmax)
{
// Traverse tree
List<RcChunkyTriMeshNode> ids = new List<RcChunkyTriMeshNode>();
int i = 0;
while (i < nodes.Count)
{
RcChunkyTriMeshNode node = nodes[i];
bool overlap = CheckOverlapRect(bmin, bmax, node.bmin, node.bmax);
bool isLeafNode = node.i >= 0;
if (isLeafNode && overlap)
{
ids.Add(node);
}
if (overlap || isLeafNode)
{
i++;
}
else
{
i = -node.i;
}
}
return ids;
}
public List<RcChunkyTriMeshNode> GetChunksOverlappingSegment(RcVec2f p, RcVec2f q)
{
// Traverse tree
List<RcChunkyTriMeshNode> ids = new List<RcChunkyTriMeshNode>();
int i = 0;
while (i < nodes.Count)
{
RcChunkyTriMeshNode node = nodes[i];
bool overlap = CheckOverlapSegment(p, q, node.bmin, node.bmax);
bool isLeafNode = node.i >= 0;
if (isLeafNode && overlap)
{
ids.Add(node);
}
if (overlap || isLeafNode)
{
i++;
}
else
{
i = -node.i;
}
}
return ids;
}
private bool CheckOverlapSegment(RcVec2f p, RcVec2f q, RcVec2f bmin, RcVec2f bmax)
{
const float EPSILON = 1e-6f;
float tmin = 0;
float tmax = 1;
var d = new RcVec2f();
d.X = q.X - p.X;
d.Y = q.Y - p.Y;
for (int i = 0; i < 2; i++)
{
if (MathF.Abs(d.Get(i)) < EPSILON)
{
// Ray is parallel to slab. No hit if origin not within slab
if (p.Get(i) < bmin.Get(i) || p.Get(i) > bmax.Get(i))
return false;
}
else
{
// Compute intersection t value of ray with near and far plane of slab
float ood = 1.0f / d.Get(i);
float t1 = (bmin.Get(i) - p.Get(i)) * ood;
float t2 = (bmax.Get(i) - p.Get(i)) * ood;
if (t1 > t2)
{
(t1, t2) = (t2, t1);
}
if (t1 > tmin)
tmin = t1;
if (t2 < tmax)
tmax = t2;
if (tmin > tmax)
return false;
}
}
return true;
}
} }
} }

View File

@ -1,4 +1,4 @@
using DotRecast.Core.Numerics; using DotRecast.Core.Numerics;
namespace DotRecast.Recast.Geom namespace DotRecast.Recast.Geom
{ {

View File

@ -1,305 +0,0 @@
/*
Copyright (c) 2009-2010 Mikko Mononen memon@inside.org
recast4j copyright (c) 2015-2019 Piotr Piastucki piotr@jtilia.org
DotRecast Copyright (c) 2023-2024 Choi Ikpil ikpil@naver.com
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
*/
using System;
using System.Collections.Generic;
using DotRecast.Core.Numerics;
namespace DotRecast.Recast.Geom
{
public static class RcChunkyTriMeshs
{
/// Creates partitioned triangle mesh (AABB tree),
/// where each node contains at max trisPerChunk triangles.
public static bool CreateChunkyTriMesh(float[] verts, int[] tris, int ntris, int trisPerChunk, RcChunkyTriMesh cm)
{
int nchunks = (ntris + trisPerChunk - 1) / trisPerChunk;
cm.nodes = new List<RcChunkyTriMeshNode>(nchunks);
cm.ntris = ntris;
// Build tree
BoundsItem[] items = new BoundsItem[ntris];
for (int i = 0; i < ntris; ++i)
{
items[i] = new BoundsItem();
}
for (int i = 0; i < ntris; i++)
{
int t = i * 3;
BoundsItem it = items[i];
it.i = i;
// Calc triangle XZ bounds.
it.bmin.X = it.bmax.X = verts[tris[t] * 3 + 0];
it.bmin.Y = it.bmax.Y = verts[tris[t] * 3 + 2];
for (int j = 1; j < 3; ++j)
{
int v = tris[t + j] * 3;
if (verts[v] < it.bmin.X)
{
it.bmin.X = verts[v];
}
if (verts[v + 2] < it.bmin.Y)
{
it.bmin.Y = verts[v + 2];
}
if (verts[v] > it.bmax.X)
{
it.bmax.X = verts[v];
}
if (verts[v + 2] > it.bmax.Y)
{
it.bmax.Y = verts[v + 2];
}
}
}
Subdivide(items, 0, ntris, trisPerChunk, cm.nodes, tris);
items = null;
// Calc max tris per node.
cm.maxTrisPerChunk = 0;
foreach (RcChunkyTriMeshNode node in cm.nodes)
{
bool isLeaf = node.i >= 0;
if (!isLeaf)
{
continue;
}
if (node.tris.Length / 3 > cm.maxTrisPerChunk)
{
cm.maxTrisPerChunk = node.tris.Length / 3;
}
}
return true;
}
/// Returns the chunk indices which overlap the input rectable.
public static List<RcChunkyTriMeshNode> GetChunksOverlappingRect(RcChunkyTriMesh cm, RcVec2f bmin, RcVec2f bmax)
{
// Traverse tree
List<RcChunkyTriMeshNode> ids = new List<RcChunkyTriMeshNode>();
int i = 0;
while (i < cm.nodes.Count)
{
RcChunkyTriMeshNode node = cm.nodes[i];
bool overlap = CheckOverlapRect(bmin, bmax, node.bmin, node.bmax);
bool isLeafNode = node.i >= 0;
if (isLeafNode && overlap)
{
ids.Add(node);
}
if (overlap || isLeafNode)
{
i++;
}
else
{
i = -node.i;
}
}
return ids;
}
/// Returns the chunk indices which overlap the input segment.
public static List<RcChunkyTriMeshNode> GetChunksOverlappingSegment(RcChunkyTriMesh cm, RcVec2f p, RcVec2f q)
{
// Traverse tree
List<RcChunkyTriMeshNode> ids = new List<RcChunkyTriMeshNode>();
int i = 0;
while (i < cm.nodes.Count)
{
RcChunkyTriMeshNode node = cm.nodes[i];
bool overlap = CheckOverlapSegment(p, q, node.bmin, node.bmax);
bool isLeafNode = node.i >= 0;
if (isLeafNode && overlap)
{
ids.Add(node);
}
if (overlap || isLeafNode)
{
i++;
}
else
{
i = -node.i;
}
}
return ids;
}
private static void CalcExtends(BoundsItem[] items, int imin, int imax, ref RcVec2f bmin, ref RcVec2f bmax)
{
bmin.X = items[imin].bmin.X;
bmin.Y = items[imin].bmin.Y;
bmax.X = items[imin].bmax.X;
bmax.Y = items[imin].bmax.Y;
for (int i = imin + 1; i < imax; ++i)
{
BoundsItem it = items[i];
if (it.bmin.X < bmin.X)
{
bmin.X = it.bmin.X;
}
if (it.bmin.Y < bmin.Y)
{
bmin.Y = it.bmin.Y;
}
if (it.bmax.X > bmax.X)
{
bmax.X = it.bmax.X;
}
if (it.bmax.Y > bmax.Y)
{
bmax.Y = it.bmax.Y;
}
}
}
private static int LongestAxis(float x, float y)
{
return y > x ? 1 : 0;
}
private static void Subdivide(BoundsItem[] items, int imin, int imax, int trisPerChunk, List<RcChunkyTriMeshNode> nodes, int[] inTris)
{
int inum = imax - imin;
RcChunkyTriMeshNode node = new RcChunkyTriMeshNode();
nodes.Add(node);
if (inum <= trisPerChunk)
{
// Leaf
CalcExtends(items, imin, imax, ref node.bmin, ref node.bmax);
// Copy triangles.
node.i = nodes.Count;
node.tris = new int[inum * 3];
int dst = 0;
for (int i = imin; i < imax; ++i)
{
int src = items[i].i * 3;
node.tris[dst++] = inTris[src];
node.tris[dst++] = inTris[src + 1];
node.tris[dst++] = inTris[src + 2];
}
}
else
{
// Split
CalcExtends(items, imin, imax, ref node.bmin, ref node.bmax);
int axis = LongestAxis(node.bmax.X - node.bmin.X, node.bmax.Y - node.bmin.Y);
if (axis == 0)
{
Array.Sort(items, imin, imax - imin, BoundsItemXComparer.Shared);
// Sort along x-axis
}
else if (axis == 1)
{
Array.Sort(items, imin, imax - imin, BoundsItemYComparer.Shared);
// Sort along y-axis
}
int isplit = imin + inum / 2;
// Left
Subdivide(items, imin, isplit, trisPerChunk, nodes, inTris);
// Right
Subdivide(items, isplit, imax, trisPerChunk, nodes, inTris);
// Negative index means escape.
node.i = -nodes.Count;
}
}
private static bool CheckOverlapRect(RcVec2f amin, RcVec2f amax, RcVec2f bmin, RcVec2f 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;
return overlap;
}
private static bool CheckOverlapSegment(RcVec2f p, RcVec2f q, RcVec2f bmin, RcVec2f bmax)
{
const float EPSILON = 1e-6f;
float tmin = 0;
float tmax = 1;
var d = new RcVec2f();
d.X = q.X - p.X;
d.Y = q.Y - p.Y;
for (int i = 0; i < 2; i++)
{
if (MathF.Abs(d.Get(i)) < EPSILON)
{
// Ray is parallel to slab. No hit if origin not within slab
if (p.Get(i) < bmin.Get(i) || p.Get(i) > bmax.Get(i))
return false;
}
else
{
// Compute intersection t value of ray with near and far plane of slab
float ood = 1.0f / d.Get(i);
float t1 = (bmin.Get(i) - p.Get(i)) * ood;
float t2 = (bmax.Get(i) - p.Get(i)) * ood;
if (t1 > t2)
{
(t1, t2) = (t2, t1);
}
if (t1 > tmin)
tmin = t1;
if (t2 < tmax)
tmax = t2;
if (tmin > tmax)
return false;
}
}
return true;
}
}
}

View File

@ -1,7 +1,7 @@
/* /*
Copyright (c) 2009-2010 Mikko Mononen memon@inside.org Copyright (c) 2009-2010 Mikko Mononen memon@inside.org
recast4j copyright (c) 2015-2019 Piotr Piastucki piotr@jtilia.org recast4j copyright (c) 2015-2019 Piotr Piastucki piotr@jtilia.org
DotRecast Copyright (c) 2023-2024 Choi Ikpil ikpil@naver.com DotRecast Copyright (c) 2023 Choi Ikpil ikpil@naver.com
This software is provided 'as-is', without any express or implied This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages warranty. In no event will the authors be held liable for any damages
@ -33,7 +33,7 @@ namespace DotRecast.Recast.Geom
public readonly int flags; public readonly int flags;
public readonly int userId; public readonly int userId;
public RcOffMeshConnection(RcVec3f start, RcVec3f end, float radius, bool bidir, int area, int flags, int userId = 0) public RcOffMeshConnection(RcVec3f start, RcVec3f end, float radius, bool bidir, int area, int flags)
{ {
verts = new float[6]; verts = new float[6];
verts[0] = start.X; verts[0] = start.X;
@ -46,7 +46,6 @@ namespace DotRecast.Recast.Geom
this.bidir = bidir; this.bidir = bidir;
this.area = area; this.area = area;
this.flags = flags; this.flags = flags;
this.userId = userId;
} }
} }
} }

View File

@ -1,7 +1,7 @@
/* /*
Copyright (c) 2009-2010 Mikko Mononen memon@inside.org Copyright (c) 2009-2010 Mikko Mononen memon@inside.org
recast4j copyright (c) 2015-2019 Piotr Piastucki piotr@jtilia.org recast4j copyright (c) 2015-2019 Piotr Piastucki piotr@jtilia.org
DotRecast Copyright (c) 2023-2024 Choi Ikpil ikpil@naver.com DotRecast Copyright (c) 2023 Choi Ikpil ikpil@naver.com
This software is provided 'as-is', without any express or implied This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages warranty. In no event will the authors be held liable for any damages
@ -19,7 +19,6 @@ freely, subject to the following restrictions:
*/ */
using System.Collections.Generic; using System.Collections.Generic;
using DotRecast.Core.Numerics;
namespace DotRecast.Recast.Geom namespace DotRecast.Recast.Geom
{ {
@ -33,8 +32,7 @@ namespace DotRecast.Recast.Geom
{ {
this.vertices = vertices; this.vertices = vertices;
this.faces = faces; this.faces = faces;
chunkyTriMesh = new RcChunkyTriMesh(); chunkyTriMesh = new RcChunkyTriMesh(vertices, faces, faces.Length / 3, 32);
RcChunkyTriMeshs.CreateChunkyTriMesh(vertices, faces, faces.Length / 3, 32, chunkyTriMesh);
} }
public int[] GetTris() public int[] GetTris()
@ -47,9 +45,9 @@ namespace DotRecast.Recast.Geom
return vertices; return vertices;
} }
public List<RcChunkyTriMeshNode> GetChunksOverlappingRect(RcVec2f bmin, RcVec2f bmax) public List<RcChunkyTriMeshNode> GetChunksOverlappingRect(float[] bmin, float[] bmax)
{ {
return RcChunkyTriMeshs.GetChunksOverlappingRect(chunkyTriMesh, bmin, bmax); return chunkyTriMesh.GetChunksOverlappingRect(bmin, bmax);
} }
} }
} }

View File

@ -1,7 +1,7 @@
/* /*
Copyright (c) 2009-2010 Mikko Mononen memon@inside.org Copyright (c) 2009-2010 Mikko Mononen memon@inside.org
recast4j copyright (c) 2015-2019 Piotr Piastucki piotr@jtilia.org recast4j copyright (c) 2015-2019 Piotr Piastucki piotr@jtilia.org
DotRecast Copyright (c) 2023-2024 Choi Ikpil ikpil@naver.com DotRecast Copyright (c) 2023 Choi Ikpil ikpil@naver.com
This software is provided 'as-is', without any express or implied This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages warranty. In no event will the authors be held liable for any damages
@ -39,7 +39,7 @@ namespace DotRecast.Recast.Geom
public static SimpleInputGeomProvider LoadFile(string objFilePath) public static SimpleInputGeomProvider LoadFile(string objFilePath)
{ {
byte[] chunk = RcIO.ReadFileIfFound(objFilePath); byte[] chunk = RcResources.Load(objFilePath);
var context = RcObjImporter.LoadContext(chunk); var context = RcObjImporter.LoadContext(chunk);
return new SimpleInputGeomProvider(context.vertexPositions, context.meshFaces); return new SimpleInputGeomProvider(context.vertexPositions, context.meshFaces);
} }
@ -77,12 +77,12 @@ namespace DotRecast.Recast.Geom
this.faces = faces; this.faces = faces;
normals = new float[faces.Length]; normals = new float[faces.Length];
CalculateNormals(); CalculateNormals();
bmin = new RcVec3f(vertices); bmin = RcVecUtils.Create(vertices);
bmax = new RcVec3f(vertices); bmax = RcVecUtils.Create(vertices);
for (int i = 1; i < vertices.Length / 3; i++) for (int i = 1; i < vertices.Length / 3; i++)
{ {
bmin = RcVec3f.Min(bmin, RcVec.Create(vertices, i * 3)); bmin = RcVecUtils.Min(bmin, vertices, i * 3);
bmax = RcVec3f.Max(bmax, RcVec.Create(vertices, i * 3)); bmax = RcVecUtils.Max(bmax, vertices, i * 3);
} }
_mesh = new RcTriMesh(vertices, faces); _mesh = new RcTriMesh(vertices, faces);

View File

@ -1,4 +1,4 @@
namespace DotRecast.Recast namespace DotRecast.Recast
{ {
public interface IRcBuilderProgressListener public interface IRcBuilderProgressListener
{ {

View File

@ -1,7 +1,7 @@
/* /*
Copyright (c) 2009-2010 Mikko Mononen memon@inside.org Copyright (c) 2009-2010 Mikko Mononen memon@inside.org
recast4j copyright (c) 2015-2019 Piotr Piastucki piotr@jtilia.org recast4j copyright (c) 2015-2019 Piotr Piastucki piotr@jtilia.org
DotRecast Copyright (c) 2023-2024 Choi Ikpil ikpil@naver.com DotRecast Copyright (c) 2023 Choi Ikpil ikpil@naver.com
This software is provided 'as-is', without any express or implied This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages warranty. In no event will the authors be held liable for any damages

View File

@ -1,7 +1,7 @@
/* /*
Copyright (c) 2009-2010 Mikko Mononen memon@inside.org Copyright (c) 2009-2010 Mikko Mononen memon@inside.org
recast4j copyright (c) 2015-2019 Piotr Piastucki piotr@jtilia.org recast4j copyright (c) 2015-2019 Piotr Piastucki piotr@jtilia.org
DotRecast Copyright (c) 2023-2024 Choi Ikpil ikpil@naver.com DotRecast Copyright (c) 2023 Choi Ikpil ikpil@naver.com
This software is provided 'as-is', without any express or implied This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages warranty. In no event will the authors be held liable for any damages
@ -25,7 +25,8 @@ using DotRecast.Core.Numerics;
namespace DotRecast.Recast namespace DotRecast.Recast
{ {
using static RcRecast; using static RcConstants;
using static RcCommons;
public static class RcAreas public static class RcAreas
{ {
@ -43,7 +44,7 @@ namespace DotRecast.Recast
/// @param[in] erosionRadius The radius of erosion. [Limits: 0 < value < 255] [Units: vx] /// @param[in] erosionRadius The radius of erosion. [Limits: 0 < value < 255] [Units: vx]
/// @param[in,out] compactHeightfield The populated compact heightfield to erode. /// @param[in,out] compactHeightfield The populated compact heightfield to erode.
/// @returns True if the operation completed successfully. /// @returns True if the operation completed successfully.
public static void ErodeWalkableArea(RcContext context, int erosionRadius, RcCompactHeightfield compactHeightfield) public static void ErodeWalkableArea(RcTelemetry context, int erosionRadius, RcCompactHeightfield compactHeightfield)
{ {
int xSize = compactHeightfield.width; int xSize = compactHeightfield.width;
int zSize = compactHeightfield.height; int zSize = compactHeightfield.height;
@ -59,7 +60,7 @@ namespace DotRecast.Recast
{ {
for (int x = 0; x < xSize; ++x) for (int x = 0; x < xSize; ++x)
{ {
ref RcCompactCell cell = ref compactHeightfield.cells[x + z * zStride]; RcCompactCell cell = compactHeightfield.cells[x + z * zStride];
for (int spanIndex = cell.index, maxSpanIndex = cell.index + cell.count; spanIndex < maxSpanIndex; ++spanIndex) for (int spanIndex = cell.index, maxSpanIndex = cell.index + cell.count; spanIndex < maxSpanIndex; ++spanIndex)
{ {
if (compactHeightfield.areas[spanIndex] == RC_NULL_AREA) if (compactHeightfield.areas[spanIndex] == RC_NULL_AREA)
@ -68,13 +69,13 @@ namespace DotRecast.Recast
} }
else else
{ {
ref RcCompactSpan span = ref compactHeightfield.spans[spanIndex]; RcCompactSpan span = compactHeightfield.spans[spanIndex];
// Check that there is a non-null adjacent span in each of the 4 cardinal directions. // Check that there is a non-null adjacent span in each of the 4 cardinal directions.
int neighborCount = 0; int neighborCount = 0;
for (int direction = 0; direction < 4; ++direction) for (int direction = 0; direction < 4; ++direction)
{ {
int neighborConnection = GetCon(ref span, direction); int neighborConnection = GetCon(span, direction);
if (neighborConnection == RC_NOT_CONNECTED) if (neighborConnection == RC_NOT_CONNECTED)
{ {
break; break;
@ -82,7 +83,7 @@ namespace DotRecast.Recast
int neighborX = x + GetDirOffsetX(direction); int neighborX = x + GetDirOffsetX(direction);
int neighborZ = z + GetDirOffsetY(direction); int neighborZ = z + GetDirOffsetY(direction);
int neighborSpanIndex = compactHeightfield.cells[neighborX + neighborZ * zStride].index + GetCon(ref span, direction); int neighborSpanIndex = compactHeightfield.cells[neighborX + neighborZ * zStride].index + GetCon(span, direction);
if (compactHeightfield.areas[neighborSpanIndex] == RC_NULL_AREA) if (compactHeightfield.areas[neighborSpanIndex] == RC_NULL_AREA)
{ {
break; break;
@ -108,19 +109,19 @@ namespace DotRecast.Recast
{ {
for (int x = 0; x < xSize; ++x) for (int x = 0; x < xSize; ++x)
{ {
ref RcCompactCell cell = ref compactHeightfield.cells[x + z * zStride]; RcCompactCell cell = compactHeightfield.cells[x + z * zStride];
int maxSpanIndex = cell.index + cell.count; int maxSpanIndex = cell.index + cell.count;
for (int spanIndex = cell.index; spanIndex < maxSpanIndex; ++spanIndex) for (int spanIndex = cell.index; spanIndex < maxSpanIndex; ++spanIndex)
{ {
ref RcCompactSpan span = ref compactHeightfield.spans[spanIndex]; RcCompactSpan span = compactHeightfield.spans[spanIndex];
if (GetCon(ref span, 0) != RC_NOT_CONNECTED) if (GetCon(span, 0) != RC_NOT_CONNECTED)
{ {
// (-1,0) // (-1,0)
int aX = x + GetDirOffsetX(0); int aX = x + GetDirOffsetX(0);
int aY = z + GetDirOffsetY(0); int aY = z + GetDirOffsetY(0);
int aIndex = compactHeightfield.cells[aX + aY * xSize].index + GetCon(ref span, 0); int aIndex = compactHeightfield.cells[aX + aY * xSize].index + GetCon(span, 0);
ref RcCompactSpan aSpan = ref compactHeightfield.spans[aIndex]; RcCompactSpan aSpan = compactHeightfield.spans[aIndex];
newDistance = Math.Min(distanceToBoundary[aIndex] + 2, 255); newDistance = Math.Min(distanceToBoundary[aIndex] + 2, 255);
if (newDistance < distanceToBoundary[spanIndex]) if (newDistance < distanceToBoundary[spanIndex])
{ {
@ -128,11 +129,11 @@ namespace DotRecast.Recast
} }
// (-1,-1) // (-1,-1)
if (GetCon(ref aSpan, 3) != RC_NOT_CONNECTED) if (GetCon(aSpan, 3) != RC_NOT_CONNECTED)
{ {
int bX = aX + GetDirOffsetX(3); int bX = aX + GetDirOffsetX(3);
int bY = aY + GetDirOffsetY(3); int bY = aY + GetDirOffsetY(3);
int bIndex = compactHeightfield.cells[bX + bY * xSize].index + GetCon(ref aSpan, 3); int bIndex = compactHeightfield.cells[bX + bY * xSize].index + GetCon(aSpan, 3);
newDistance = Math.Min(distanceToBoundary[bIndex] + 3, 255); newDistance = Math.Min(distanceToBoundary[bIndex] + 3, 255);
if (newDistance < distanceToBoundary[spanIndex]) if (newDistance < distanceToBoundary[spanIndex])
{ {
@ -141,13 +142,13 @@ namespace DotRecast.Recast
} }
} }
if (GetCon(ref span, 3) != RC_NOT_CONNECTED) if (GetCon(span, 3) != RC_NOT_CONNECTED)
{ {
// (0,-1) // (0,-1)
int aX = x + GetDirOffsetX(3); int aX = x + GetDirOffsetX(3);
int aY = z + GetDirOffsetY(3); int aY = z + GetDirOffsetY(3);
int aIndex = compactHeightfield.cells[aX + aY * xSize].index + GetCon(ref span, 3); int aIndex = compactHeightfield.cells[aX + aY * xSize].index + GetCon(span, 3);
ref RcCompactSpan aSpan = ref compactHeightfield.spans[aIndex]; RcCompactSpan aSpan = compactHeightfield.spans[aIndex];
newDistance = Math.Min(distanceToBoundary[aIndex] + 2, 255); newDistance = Math.Min(distanceToBoundary[aIndex] + 2, 255);
if (newDistance < distanceToBoundary[spanIndex]) if (newDistance < distanceToBoundary[spanIndex])
{ {
@ -155,11 +156,11 @@ namespace DotRecast.Recast
} }
// (1,-1) // (1,-1)
if (GetCon(ref aSpan, 2) != RC_NOT_CONNECTED) if (GetCon(aSpan, 2) != RC_NOT_CONNECTED)
{ {
int bX = aX + GetDirOffsetX(2); int bX = aX + GetDirOffsetX(2);
int bY = aY + GetDirOffsetY(2); int bY = aY + GetDirOffsetY(2);
int bIndex = compactHeightfield.cells[bX + bY * xSize].index + GetCon(ref aSpan, 2); int bIndex = compactHeightfield.cells[bX + bY * xSize].index + GetCon(aSpan, 2);
newDistance = Math.Min(distanceToBoundary[bIndex] + 3, 255); newDistance = Math.Min(distanceToBoundary[bIndex] + 3, 255);
if (newDistance < distanceToBoundary[spanIndex]) if (newDistance < distanceToBoundary[spanIndex])
{ {
@ -176,19 +177,19 @@ namespace DotRecast.Recast
{ {
for (int x = xSize - 1; x >= 0; --x) for (int x = xSize - 1; x >= 0; --x)
{ {
ref RcCompactCell cell = ref compactHeightfield.cells[x + z * zStride]; RcCompactCell cell = compactHeightfield.cells[x + z * zStride];
int maxSpanIndex = cell.index + cell.count; int maxSpanIndex = cell.index + cell.count;
for (int i = cell.index; i < maxSpanIndex; ++i) for (int i = cell.index; i < maxSpanIndex; ++i)
{ {
ref RcCompactSpan span = ref compactHeightfield.spans[i]; RcCompactSpan span = compactHeightfield.spans[i];
if (GetCon(ref span, 2) != RC_NOT_CONNECTED) if (GetCon(span, 2) != RC_NOT_CONNECTED)
{ {
// (1,0) // (1,0)
int aX = x + GetDirOffsetX(2); int aX = x + GetDirOffsetX(2);
int aY = z + GetDirOffsetY(2); int aY = z + GetDirOffsetY(2);
int aIndex = compactHeightfield.cells[aX + aY * xSize].index + GetCon(ref span, 2); int aIndex = compactHeightfield.cells[aX + aY * xSize].index + GetCon(span, 2);
ref RcCompactSpan aSpan = ref compactHeightfield.spans[aIndex]; RcCompactSpan aSpan = compactHeightfield.spans[aIndex];
newDistance = Math.Min(distanceToBoundary[aIndex] + 2, 255); newDistance = Math.Min(distanceToBoundary[aIndex] + 2, 255);
if (newDistance < distanceToBoundary[i]) if (newDistance < distanceToBoundary[i])
{ {
@ -196,11 +197,11 @@ namespace DotRecast.Recast
} }
// (1,1) // (1,1)
if (GetCon(ref aSpan, 1) != RC_NOT_CONNECTED) if (GetCon(aSpan, 1) != RC_NOT_CONNECTED)
{ {
int bX = aX + GetDirOffsetX(1); int bX = aX + GetDirOffsetX(1);
int bY = aY + GetDirOffsetY(1); int bY = aY + GetDirOffsetY(1);
int bIndex = compactHeightfield.cells[bX + bY * xSize].index + GetCon(ref aSpan, 1); int bIndex = compactHeightfield.cells[bX + bY * xSize].index + GetCon(aSpan, 1);
newDistance = Math.Min(distanceToBoundary[bIndex] + 3, 255); newDistance = Math.Min(distanceToBoundary[bIndex] + 3, 255);
if (newDistance < distanceToBoundary[i]) if (newDistance < distanceToBoundary[i])
{ {
@ -209,13 +210,13 @@ namespace DotRecast.Recast
} }
} }
if (GetCon(ref span, 1) != RC_NOT_CONNECTED) if (GetCon(span, 1) != RC_NOT_CONNECTED)
{ {
// (0,1) // (0,1)
int aX = x + GetDirOffsetX(1); int aX = x + GetDirOffsetX(1);
int aY = z + GetDirOffsetY(1); int aY = z + GetDirOffsetY(1);
int aIndex = compactHeightfield.cells[aX + aY * xSize].index + GetCon(ref span, 1); int aIndex = compactHeightfield.cells[aX + aY * xSize].index + GetCon(span, 1);
ref RcCompactSpan aSpan = ref compactHeightfield.spans[aIndex]; RcCompactSpan aSpan = compactHeightfield.spans[aIndex];
newDistance = Math.Min(distanceToBoundary[aIndex] + 2, 255); newDistance = Math.Min(distanceToBoundary[aIndex] + 2, 255);
if (newDistance < distanceToBoundary[i]) if (newDistance < distanceToBoundary[i])
{ {
@ -223,11 +224,11 @@ namespace DotRecast.Recast
} }
// (-1,1) // (-1,1)
if (GetCon(ref aSpan, 0) != RC_NOT_CONNECTED) if (GetCon(aSpan, 0) != RC_NOT_CONNECTED)
{ {
int bX = aX + GetDirOffsetX(0); int bX = aX + GetDirOffsetX(0);
int bY = aY + GetDirOffsetY(0); int bY = aY + GetDirOffsetY(0);
int bIndex = compactHeightfield.cells[bX + bY * xSize].index + GetCon(ref aSpan, 0); int bIndex = compactHeightfield.cells[bX + bY * xSize].index + GetCon(aSpan, 0);
newDistance = Math.Min(distanceToBoundary[bIndex] + 3, 255); newDistance = Math.Min(distanceToBoundary[bIndex] + 3, 255);
if (newDistance < distanceToBoundary[i]) if (newDistance < distanceToBoundary[i])
{ {
@ -260,7 +261,7 @@ namespace DotRecast.Recast
/// @param[in,out] context The build context to use during the operation. /// @param[in,out] context The build context to use during the operation.
/// @param[in,out] compactHeightfield A populated compact heightfield. /// @param[in,out] compactHeightfield A populated compact heightfield.
/// @returns True if the operation completed successfully. /// @returns True if the operation completed successfully.
public static bool MedianFilterWalkableArea(RcContext context, RcCompactHeightfield compactHeightfield) public static bool MedianFilterWalkableArea(RcTelemetry context, RcCompactHeightfield compactHeightfield)
{ {
int xSize = compactHeightfield.width; int xSize = compactHeightfield.width;
int zSize = compactHeightfield.height; int zSize = compactHeightfield.height;
@ -274,11 +275,11 @@ namespace DotRecast.Recast
{ {
for (int x = 0; x < xSize; ++x) for (int x = 0; x < xSize; ++x)
{ {
ref RcCompactCell cell = ref compactHeightfield.cells[x + z * zStride]; RcCompactCell cell = compactHeightfield.cells[x + z * zStride];
int maxSpanIndex = cell.index + cell.count; int maxSpanIndex = cell.index + cell.count;
for (int spanIndex = cell.index; spanIndex < maxSpanIndex; ++spanIndex) for (int spanIndex = cell.index; spanIndex < maxSpanIndex; ++spanIndex)
{ {
ref RcCompactSpan span = ref compactHeightfield.spans[spanIndex]; RcCompactSpan span = compactHeightfield.spans[spanIndex];
if (compactHeightfield.areas[spanIndex] == RC_NULL_AREA) if (compactHeightfield.areas[spanIndex] == RC_NULL_AREA)
{ {
areas[spanIndex] = compactHeightfield.areas[spanIndex]; areas[spanIndex] = compactHeightfield.areas[spanIndex];
@ -293,27 +294,27 @@ namespace DotRecast.Recast
for (int dir = 0; dir < 4; ++dir) for (int dir = 0; dir < 4; ++dir)
{ {
if (GetCon(ref span, dir) == RC_NOT_CONNECTED) if (GetCon(span, dir) == RC_NOT_CONNECTED)
{ {
continue; continue;
} }
int aX = x + GetDirOffsetX(dir); int aX = x + GetDirOffsetX(dir);
int aZ = z + GetDirOffsetY(dir); int aZ = z + GetDirOffsetY(dir);
int aIndex = compactHeightfield.cells[aX + aZ * zStride].index + GetCon(ref span, dir); int aIndex = compactHeightfield.cells[aX + aZ * zStride].index + GetCon(span, dir);
if (compactHeightfield.areas[aIndex] != RC_NULL_AREA) if (compactHeightfield.areas[aIndex] != RC_NULL_AREA)
{ {
neighborAreas[dir * 2 + 0] = compactHeightfield.areas[aIndex]; neighborAreas[dir * 2 + 0] = compactHeightfield.areas[aIndex];
} }
ref RcCompactSpan aSpan = ref compactHeightfield.spans[aIndex]; RcCompactSpan aSpan = compactHeightfield.spans[aIndex];
int dir2 = (dir + 1) & 0x3; int dir2 = (dir + 1) & 0x3;
int neighborConnection2 = GetCon(ref aSpan, dir2); int neighborConnection2 = GetCon(aSpan, dir2);
if (neighborConnection2 != RC_NOT_CONNECTED) if (neighborConnection2 != RC_NOT_CONNECTED)
{ {
int bX = aX + GetDirOffsetX(dir2); int bX = aX + GetDirOffsetX(dir2);
int bZ = aZ + GetDirOffsetY(dir2); int bZ = aZ + GetDirOffsetY(dir2);
int bIndex = compactHeightfield.cells[bX + bZ * zStride].index + GetCon(ref aSpan, dir2); int bIndex = compactHeightfield.cells[bX + bZ * zStride].index + GetCon(aSpan, dir2);
if (compactHeightfield.areas[bIndex] != RC_NULL_AREA) if (compactHeightfield.areas[bIndex] != RC_NULL_AREA)
{ {
neighborAreas[dir * 2 + 1] = compactHeightfield.areas[bIndex]; neighborAreas[dir * 2 + 1] = compactHeightfield.areas[bIndex];
@ -343,7 +344,7 @@ namespace DotRecast.Recast
/// @param[in] boxMaxBounds The maximum extents of the bounding box. [(x, y, z)] [Units: wu] /// @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] areaId The area id to apply. [Limit: <= #RC_WALKABLE_AREA]
/// @param[in,out] compactHeightfield A populated compact heightfield. /// @param[in,out] compactHeightfield A populated compact heightfield.
public static void MarkBoxArea(RcContext context, float[] boxMinBounds, float[] boxMaxBounds, RcAreaModification areaId, RcCompactHeightfield compactHeightfield) public static void MarkBoxArea(RcTelemetry context, float[] boxMinBounds, float[] boxMaxBounds, RcAreaModification areaId, RcCompactHeightfield compactHeightfield)
{ {
using var timer = context.ScopedTimer(RcTimerLabel.RC_TIMER_MARK_BOX_AREA); using var timer = context.ScopedTimer(RcTimerLabel.RC_TIMER_MARK_BOX_AREA);
@ -403,11 +404,11 @@ namespace DotRecast.Recast
{ {
for (int x = minX; x <= maxX; ++x) for (int x = minX; x <= maxX; ++x)
{ {
ref RcCompactCell cell = ref compactHeightfield.cells[x + z * zStride]; RcCompactCell cell = compactHeightfield.cells[x + z * zStride];
int maxSpanIndex = cell.index + cell.count; int maxSpanIndex = cell.index + cell.count;
for (int spanIndex = cell.index; spanIndex < maxSpanIndex; ++spanIndex) for (int spanIndex = cell.index; spanIndex < maxSpanIndex; ++spanIndex)
{ {
ref RcCompactSpan span = ref compactHeightfield.spans[spanIndex]; RcCompactSpan span = compactHeightfield.spans[spanIndex];
// Skip if the span is outside the box extents. // Skip if the span is outside the box extents.
if (span.y < minY || span.y > maxY) if (span.y < minY || span.y > maxY)
@ -445,7 +446,7 @@ namespace DotRecast.Recast
/// @param[in] maxY The height of the top of the polygon. [Units: wu] /// @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] areaId The area id to apply. [Limit: <= #RC_WALKABLE_AREA]
/// @param[in,out] compactHeightfield A populated compact heightfield. /// @param[in,out] compactHeightfield A populated compact heightfield.
public static void MarkConvexPolyArea(RcContext context, float[] verts, public static void MarkConvexPolyArea(RcTelemetry context, float[] verts,
float minY, float maxY, RcAreaModification areaId, float minY, float maxY, RcAreaModification areaId,
RcCompactHeightfield compactHeightfield) RcCompactHeightfield compactHeightfield)
{ {
@ -456,12 +457,12 @@ namespace DotRecast.Recast
int zStride = xSize; // For readability int zStride = xSize; // For readability
// Compute the bounding box of the polygon // Compute the bounding box of the polygon
RcVec3f bmin = new RcVec3f(verts); RcVec3f bmin = RcVecUtils.Create(verts);
RcVec3f bmax = new RcVec3f(verts); RcVec3f bmax = RcVecUtils.Create(verts);
for (int i = 3; i < verts.Length; i += 3) for (int i = 3; i < verts.Length; i += 3)
{ {
bmin = RcVec3f.Min(bmin, RcVec.Create(verts, i)); bmin = RcVecUtils.Min(bmin, verts, i);
bmax = RcVec3f.Max(bmax, RcVec.Create(verts, i)); bmax = RcVecUtils.Max(bmax, verts, i);
} }
bmin.Y = minY; bmin.Y = minY;
@ -522,11 +523,11 @@ namespace DotRecast.Recast
{ {
for (int x = minx; x <= maxx; ++x) for (int x = minx; x <= maxx; ++x)
{ {
ref RcCompactCell cell = ref compactHeightfield.cells[x + z * zStride]; RcCompactCell cell = compactHeightfield.cells[x + z * zStride];
int maxSpanIndex = cell.index + cell.count; int maxSpanIndex = cell.index + cell.count;
for (int spanIndex = cell.index; spanIndex < maxSpanIndex; ++spanIndex) for (int spanIndex = cell.index; spanIndex < maxSpanIndex; ++spanIndex)
{ {
ref RcCompactSpan span = ref compactHeightfield.spans[spanIndex]; RcCompactSpan span = compactHeightfield.spans[spanIndex];
// Skip if span is removed. // Skip if span is removed.
if (compactHeightfield.areas[spanIndex] == RC_NULL_AREA) if (compactHeightfield.areas[spanIndex] == RC_NULL_AREA)
@ -566,7 +567,7 @@ namespace DotRecast.Recast
/// @param[in] height The height of the cylinder. [Units: wu] [Limit: > 0] /// @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] areaId The area id to apply. [Limit: <= #RC_WALKABLE_AREA]
/// @param[in,out] compactHeightfield A populated compact heightfield. /// @param[in,out] compactHeightfield A populated compact heightfield.
public static void MarkCylinderArea(RcContext context, float[] position, float radius, float height, public static void MarkCylinderArea(RcTelemetry context, float[] position, float radius, float height,
RcAreaModification areaId, RcCompactHeightfield compactHeightfield) RcAreaModification areaId, RcCompactHeightfield compactHeightfield)
{ {
using var timer = context.ScopedTimer(RcTimerLabel.RC_TIMER_MARK_CYLINDER_AREA); using var timer = context.ScopedTimer(RcTimerLabel.RC_TIMER_MARK_CYLINDER_AREA);
@ -643,7 +644,7 @@ namespace DotRecast.Recast
{ {
for (int x = minx; x <= maxx; ++x) for (int x = minx; x <= maxx; ++x)
{ {
ref RcCompactCell cell = ref compactHeightfield.cells[x + z * zStride]; RcCompactCell cell = compactHeightfield.cells[x + z * zStride];
int maxSpanIndex = cell.index + cell.count; int maxSpanIndex = cell.index + cell.count;
float cellX = compactHeightfield.bmin.X + ((float)x + 0.5f) * compactHeightfield.cs; float cellX = compactHeightfield.bmin.X + ((float)x + 0.5f) * compactHeightfield.cs;
@ -660,7 +661,7 @@ namespace DotRecast.Recast
// Mark all overlapping spans // Mark all overlapping spans
for (int spanIndex = cell.index; spanIndex < maxSpanIndex; ++spanIndex) for (int spanIndex = cell.index; spanIndex < maxSpanIndex; ++spanIndex)
{ {
ref RcCompactSpan span = ref compactHeightfield.spans[spanIndex]; RcCompactSpan span = compactHeightfield.spans[spanIndex];
// Skip if span is removed. // Skip if span is removed.
if (compactHeightfield.areas[spanIndex] == RC_NULL_AREA) if (compactHeightfield.areas[spanIndex] == RC_NULL_AREA)
@ -752,19 +753,19 @@ namespace DotRecast.Recast
int vertIndexB = vertIndex; int vertIndexB = vertIndex;
int vertIndexC = (vertIndex + 1) % numVerts; int vertIndexC = (vertIndex + 1) % numVerts;
RcVec3f vertA = RcVec.Create(verts, vertIndexA * 3); RcVec3f vertA = RcVecUtils.Create(verts, vertIndexA * 3);
RcVec3f vertB = RcVec.Create(verts, vertIndexB * 3); RcVec3f vertB = RcVecUtils.Create(verts, vertIndexB * 3);
RcVec3f vertC = RcVec.Create(verts, vertIndexC * 3); RcVec3f vertC = RcVecUtils.Create(verts, vertIndexC * 3);
// From A to B on the x/z plane // From A to B on the x/z plane
RcVec3f prevSegmentDir = RcVec3f.Subtract(vertB, vertA); RcVec3f prevSegmentDir = RcVec3f.Subtract(vertB, vertA);
prevSegmentDir.Y = 0; // Squash onto x/z plane prevSegmentDir.Y = 0; // Squash onto x/z plane
prevSegmentDir = RcVec.SafeNormalize(prevSegmentDir); prevSegmentDir = RcVecUtils.SafeNormalize(prevSegmentDir);
// From B to C on the x/z plane // From B to C on the x/z plane
RcVec3f currSegmentDir = RcVec3f.Subtract(vertC, vertB); RcVec3f currSegmentDir = RcVec3f.Subtract(vertC, vertB);
currSegmentDir.Y = 0; // Squash onto x/z plane currSegmentDir.Y = 0; // Squash onto x/z plane
currSegmentDir = RcVec.SafeNormalize(currSegmentDir); currSegmentDir = RcVecUtils.SafeNormalize(currSegmentDir);
// The y component of the cross product of the two normalized segment directions. // The y component of the cross product of the two normalized segment directions.
// The X and Z components of the cross product are both zero because the two // The X and Z components of the cross product are both zero because the two
@ -791,7 +792,7 @@ namespace DotRecast.Recast
bool bevel = cornerMiterSqMag * MITER_LIMIT * MITER_LIMIT < 1.0f; bool bevel = cornerMiterSqMag * MITER_LIMIT * MITER_LIMIT < 1.0f;
// Scale the corner miter so it's proportional to how much the corner should be offset compared to the edges. // Scale the corner miter so it's proportional to how much the corner should be offset compared to the edges.
if (cornerMiterSqMag > RcVec.EPSILON) if (cornerMiterSqMag > RcVecUtils.EPSILON)
{ {
float scale = 1.0f / cornerMiterSqMag; float scale = 1.0f / cornerMiterSqMag;
cornerMiterX *= scale; cornerMiterX *= scale;

View File

@ -1,9 +0,0 @@
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

@ -1,4 +1,4 @@
namespace DotRecast.Recast namespace DotRecast.Recast
{ {
/// Contour build flags. /// Contour build flags.
/// @see rcBuildContours /// @see rcBuildContours

View File

@ -1,7 +1,7 @@
/* /*
Copyright (c) 2009-2010 Mikko Mononen memon@inside.org Copyright (c) 2009-2010 Mikko Mononen memon@inside.org
recast4j copyright (c) 2015-2019 Piotr Piastucki piotr@jtilia.org recast4j copyright (c) 2015-2019 Piotr Piastucki piotr@jtilia.org
DotRecast Copyright (c) 2023-2024 Choi Ikpil ikpil@naver.com DotRecast Copyright (c) 2023 Choi Ikpil ikpil@naver.com
This software is provided 'as-is', without any express or implied This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages warranty. In no event will the authors be held liable for any damages
@ -19,9 +19,7 @@ freely, subject to the following restrictions:
*/ */
using System; using System;
using System.Collections.Concurrent;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using DotRecast.Core; using DotRecast.Core;
@ -30,7 +28,7 @@ using DotRecast.Recast.Geom;
namespace DotRecast.Recast namespace DotRecast.Recast
{ {
using static RcRecast; using static RcCommons;
using static RcAreas; using static RcAreas;
public class RcBuilder public class RcBuilder
@ -47,162 +45,183 @@ namespace DotRecast.Recast
_progressListener = progressListener; _progressListener = progressListener;
} }
public List<RcBuilderResult> BuildTiles(IInputGeomProvider geom, RcConfig cfg, bool keepInterResults, bool buildAll, public List<RcBuilderResult> BuildTiles(IInputGeomProvider geom, RcConfig cfg, TaskFactory taskFactory)
int threads = 0, TaskFactory taskFactory = null, CancellationToken cancellation = default)
{ {
RcVec3f bmin = geom.GetMeshBoundsMin(); RcVec3f bmin = geom.GetMeshBoundsMin();
RcVec3f bmax = geom.GetMeshBoundsMax(); RcVec3f bmax = geom.GetMeshBoundsMax();
CalcTileCount(bmin, bmax, cfg.Cs, cfg.TileSizeX, cfg.TileSizeZ, out var tw, out var th); CalcTileCount(bmin, bmax, cfg.Cs, cfg.TileSizeX, cfg.TileSizeZ, out var tw, out var th);
List<RcBuilderResult> results = new List<RcBuilderResult>();
if (1 < threads) if (null != taskFactory)
{ {
return BuildMultiThread(geom, cfg, bmin, bmax, tw, th, threads, taskFactory ?? Task.Factory, cancellation, keepInterResults, buildAll); BuildMultiThreadAsync(geom, cfg, bmin, bmax, tw, th, results, taskFactory, default);
} }
else
return BuildSingleThread(geom, cfg, bmin, bmax, tw, th, keepInterResults, buildAll);
}
private List<RcBuilderResult> BuildSingleThread(IInputGeomProvider geom, RcConfig cfg, RcVec3f bmin, RcVec3f bmax, int tw, int th,
bool keepInterResults, bool buildAll)
{ {
var results = new List<RcBuilderResult>(th * tw); BuildSingleThreadAsync(geom, cfg, bmin, bmax, tw, th, results);
RcAtomicInteger counter = new RcAtomicInteger(0);
for (int y = 0; y < th; ++y)
{
for (int x = 0; x < tw; ++x)
{
var result = BuildTile(geom, cfg, bmin, bmax, x, y, counter, tw * th, keepInterResults);
results.Add(result);
}
} }
return results; return results;
} }
private List<RcBuilderResult> BuildMultiThread(IInputGeomProvider geom, RcConfig cfg, RcVec3f bmin, RcVec3f bmax, int tw, int th,
int threads, TaskFactory taskFactory, CancellationToken cancellation,
bool keepInterResults, bool buildAll)
{
var results = new ConcurrentQueue<RcBuilderResult>();
RcAtomicInteger progress = new RcAtomicInteger(0);
List<Task> limits = new List<Task>(threads); public Task BuildTilesAsync(IInputGeomProvider geom, RcConfig cfg, int threads, List<RcBuilderResult> results, TaskFactory taskFactory, CancellationToken cancellationToken)
{
RcVec3f bmin = geom.GetMeshBoundsMin();
RcVec3f bmax = geom.GetMeshBoundsMax();
CalcTileCount(bmin, bmax, cfg.Cs, cfg.TileSizeX, cfg.TileSizeZ, out var tw, out var th);
Task task;
if (1 < threads)
{
task = BuildMultiThreadAsync(geom, cfg, bmin, bmax, tw, th, results, taskFactory, cancellationToken);
}
else
{
task = BuildSingleThreadAsync(geom, cfg, bmin, bmax, tw, th, results);
}
return task;
}
private Task BuildSingleThreadAsync(IInputGeomProvider geom, RcConfig cfg, RcVec3f bmin, RcVec3f bmax,
int tw, int th, List<RcBuilderResult> results)
{
RcAtomicInteger counter = new RcAtomicInteger(0);
for (int y = 0; y < th; ++y)
{
for (int x = 0; x < tw; ++x)
{
results.Add(BuildTile(geom, cfg, bmin, bmax, x, y, counter, tw * th));
}
}
return Task.CompletedTask;
}
private Task BuildMultiThreadAsync(IInputGeomProvider geom, RcConfig cfg, RcVec3f bmin, RcVec3f bmax,
int tw, int th, List<RcBuilderResult> results, TaskFactory taskFactory, CancellationToken cancellationToken)
{
RcAtomicInteger counter = new RcAtomicInteger(0);
CountdownEvent latch = new CountdownEvent(tw * th);
List<Task> tasks = new List<Task>();
for (int x = 0; x < tw; ++x) for (int x = 0; x < tw; ++x)
{ {
for (int y = 0; y < th; ++y) for (int y = 0; y < th; ++y)
{ {
int tx = x; int tx = x;
int ty = y; int ty = y;
var task = taskFactory.StartNew(state => var task = taskFactory.StartNew(() =>
{ {
if (cancellation.IsCancellationRequested) if (cancellationToken.IsCancellationRequested)
return; return;
try try
{ {
RcBuilderResult result = BuildTile(geom, cfg, bmin, bmax, tx, ty, progress, tw * th, keepInterResults); RcBuilderResult tile = BuildTile(geom, cfg, bmin, bmax, tx, ty, counter, tw * th);
results.Enqueue(result); lock (results)
{
results.Add(tile);
}
} }
catch (Exception e) catch (Exception e)
{ {
Console.WriteLine(e); Console.WriteLine(e);
} }
}, null, cancellation);
limits.Add(task);
if (threads <= limits.Count) latch.Signal();
}, cancellationToken);
tasks.Add(task);
}
}
try
{ {
Task.WaitAll(limits.ToArray()); latch.Wait();
limits.Clear();
} }
} catch (ThreadInterruptedException)
}
if (0 < limits.Count)
{ {
Task.WaitAll(limits.ToArray());
limits.Clear();
} }
var list = results.ToList(); return Task.WhenAll(tasks.ToArray());
return list;
} }
public RcBuilderResult BuildTile(IInputGeomProvider geom, RcConfig cfg, RcVec3f bmin, RcVec3f bmax, int tx, int ty, RcAtomicInteger progress, int total, bool keepInterResults) public RcBuilderResult BuildTile(IInputGeomProvider geom, RcConfig cfg, RcVec3f bmin, RcVec3f bmax, int tx,
int ty, RcAtomicInteger counter, int total)
{ {
var bcfg = new RcBuilderConfig(cfg, bmin, bmax, tx, ty); RcBuilderResult result = Build(geom, new RcBuilderConfig(cfg, bmin, bmax, tx, ty));
RcBuilderResult result = Build(geom, bcfg, keepInterResults);
if (_progressListener != null) if (_progressListener != null)
{ {
_progressListener.OnProgress(progress.IncrementAndGet(), total); _progressListener.OnProgress(counter.IncrementAndGet(), total);
} }
return result; return result;
} }
public RcBuilderResult Build(IInputGeomProvider geom, RcBuilderConfig bcfg, bool keepInterResults) public RcBuilderResult Build(IInputGeomProvider geom, RcBuilderConfig builderCfg)
{ {
RcConfig cfg = bcfg.cfg; RcConfig cfg = builderCfg.cfg;
RcContext ctx = new RcContext(); RcTelemetry ctx = new RcTelemetry();
// //
// Step 1. Rasterize input polygon soup. // Step 1. Rasterize input polygon soup.
// //
RcHeightfield solid = RcVoxelizations.BuildSolidHeightfield(ctx, geom, bcfg); RcHeightfield solid = RcVoxelizations.BuildSolidHeightfield(geom, builderCfg, ctx);
return Build(ctx, bcfg.tileX, bcfg.tileZ, geom, cfg, solid, keepInterResults); return Build(builderCfg.tileX, builderCfg.tileZ, geom, cfg, solid, ctx);
} }
public RcBuilderResult Build(RcContext ctx, int tileX, int tileZ, IInputGeomProvider geom, RcConfig cfg, RcHeightfield solid, bool keepInterResults) public RcBuilderResult Build(int tileX, int tileZ, IInputGeomProvider geom, RcConfig cfg, RcHeightfield solid, RcTelemetry ctx)
{ {
FilterHeightfield(ctx, solid, cfg); FilterHeightfield(solid, cfg, ctx);
RcCompactHeightfield chf = BuildCompactHeightfield(ctx, geom, cfg, solid); RcCompactHeightfield chf = BuildCompactHeightfield(geom, cfg, ctx, 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: // There are 3 partitioning methods, each with some pros and cons:
// 1) Watershed partitioning // 1) Watershed partitioning
// - the classic Recast partitioning // - the classic Recast partitioning
// - creates the nicest tessellation // - creates the nicest tessellation
// - usually slowest // - usually slowest
// - partitions the heightfield into nice regions without holes or overlaps // - partitions the heightfield into nice regions without holes or
// - the are some corner cases where this method creates produces holes and overlaps // overlaps
// - holes may appear when a small obstacles is close to large open area (triangulation can handle this) // - the are some corner cases where this method creates produces holes
// - overlaps may occur if you have narrow spiral corridors (i.e stairs), this make triangulation to fail // and overlaps
// * generally the best choice if you precompute the navmesh, use this if you have large open areas // - holes may appear when a small obstacles is close to large open area
// 2) Monotone partitioning // (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 // - fastest
// - partitions the heightfield into regions without holes and overlaps (guaranteed) // - partitions the heightfield into regions without holes and overlaps
// - creates long thin polygons, which sometimes causes paths with detours // (guaranteed)
// - creates long thin polygons, which sometimes causes paths with
// detours
// * use this if you want fast navmesh generation // * use this if you want fast navmesh generation
// 3) Layer partitoining // 3) Layer partitoining
// - quite fast // - quite fast
// - partitions the heighfield into non-overlapping regions // - partitions the heighfield into non-overlapping regions
// - relies on the triangulation code to cope with holes (thus slower than monotone partitioning) // - relies on the triangulation code to cope with holes (thus slower
// than monotone partitioning)
// - produces better triangles than monotone partitioning // - produces better triangles than monotone partitioning
// - does not have the corner cases of watershed partitioning // - does not have the corner cases of watershed partitioning
// - can be slow and create a bit ugly tessellation (still better than monotone) // - can be slow and create a bit ugly tessellation (still better than
// if you have large open areas with small obstacles (not a problem if you use tiles) // monotone)
// * good choice to use for tiled navmesh with medium and small sized tiles // 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) 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); RcRegions.BuildDistanceField(ctx, chf);
}
// Partition the walkable surface into simple regions without holes. // Partition the walkable surface into simple regions without holes.
RcRegions.BuildRegions(ctx, chf, cfg.MinRegionArea, cfg.MergeRegionArea); RcRegions.BuildRegions(ctx, chf, cfg.MinRegionArea, cfg.MergeRegionArea, RcPartitionType.OfValue(cfg.Partition));
}
else if (cfg.Partition == RcPartitionType.MONOTONE.Value)
{
// Partition the walkable surface into simple regions without holes.
// Monotone partitioning does not need distancefield.
RcRegions.BuildRegionsMonotone(ctx, chf, cfg.MinRegionArea, cfg.MergeRegionArea);
}
else
{
// Partition the walkable surface into simple regions without holes.
RcRegions.BuildLayerRegions(ctx, chf, cfg.MinRegionArea);
}
// //
// Step 5. Trace and simplify region contours. // Step 5. Trace and simplify region contours.
@ -224,23 +243,13 @@ namespace DotRecast.Recast
RcPolyMeshDetail dmesh = cfg.BuildMeshDetail RcPolyMeshDetail dmesh = cfg.BuildMeshDetail
? RcMeshDetails.BuildPolyMeshDetail(ctx, pmesh, chf, cfg.DetailSampleDist, cfg.DetailSampleMaxError) ? RcMeshDetails.BuildPolyMeshDetail(ctx, pmesh, chf, cfg.DetailSampleDist, cfg.DetailSampleMaxError)
: null; : null;
return new RcBuilderResult(tileX, tileZ, solid, chf, cset, pmesh, dmesh, ctx);
return new RcBuilderResult(
tileX,
tileZ,
keepInterResults ? solid : null,
keepInterResults ? chf : null,
keepInterResults ? cset : null,
pmesh,
dmesh,
ctx
);
} }
/* /*
* Step 2. Filter walkable surfaces. * Step 2. Filter walkable surfaces.
*/ */
private void FilterHeightfield(RcContext ctx, RcHeightfield solid, RcConfig cfg) private void FilterHeightfield(RcHeightfield solid, RcConfig cfg, RcTelemetry ctx)
{ {
// Once all geometry is rasterized, we do initial pass of filtering to // Once all geometry is rasterized, we do initial pass of filtering to
// remove unwanted overhangs caused by the conservative rasterization // remove unwanted overhangs caused by the conservative rasterization
@ -264,7 +273,7 @@ namespace DotRecast.Recast
/* /*
* Step 3. Partition walkable surface to simple regions. * Step 3. Partition walkable surface to simple regions.
*/ */
private RcCompactHeightfield BuildCompactHeightfield(RcContext ctx, IInputGeomProvider geom, RcConfig cfg, RcHeightfield solid) private RcCompactHeightfield BuildCompactHeightfield(IInputGeomProvider geom, RcConfig cfg, RcTelemetry ctx, RcHeightfield solid)
{ {
// Compact the heightfield so that it is faster to handle from now on. // 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 // This will result more cache coherent data as well as the neighbours
@ -287,13 +296,11 @@ namespace DotRecast.Recast
public RcHeightfieldLayerSet BuildLayers(IInputGeomProvider geom, RcBuilderConfig builderCfg) public RcHeightfieldLayerSet BuildLayers(IInputGeomProvider geom, RcBuilderConfig builderCfg)
{ {
RcContext ctx = new RcContext(); RcTelemetry ctx = new RcTelemetry();
RcHeightfield solid = RcVoxelizations.BuildSolidHeightfield(ctx, geom, builderCfg); RcHeightfield solid = RcVoxelizations.BuildSolidHeightfield(geom, builderCfg, ctx);
FilterHeightfield(ctx, solid, builderCfg.cfg); FilterHeightfield(solid, builderCfg.cfg, ctx);
RcCompactHeightfield chf = BuildCompactHeightfield(ctx, geom, builderCfg.cfg, solid); RcCompactHeightfield chf = BuildCompactHeightfield(geom, builderCfg.cfg, ctx, solid);
return RcLayers.BuildHeightfieldLayers(ctx, chf, builderCfg.cfg.WalkableHeight);
RcLayers.BuildHeightfieldLayers(ctx, chf, builderCfg.cfg.BorderSize, builderCfg.cfg.WalkableHeight, out var lset);
return lset;
} }
} }
} }

View File

@ -1,7 +1,7 @@
/* /*
Copyright (c) 2009-2010 Mikko Mononen memon@inside.org Copyright (c) 2009-2010 Mikko Mononen memon@inside.org
recast4j copyright (c) 2015-2019 Piotr Piastucki piotr@jtilia.org recast4j copyright (c) 2015-2019 Piotr Piastucki piotr@jtilia.org
DotRecast Copyright (c) 2023-2024 Choi Ikpil ikpil@naver.com DotRecast Copyright (c) 2023 Choi Ikpil ikpil@naver.com
This software is provided 'as-is', without any express or implied This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages warranty. In no event will the authors be held liable for any damages
@ -92,7 +92,7 @@ namespace DotRecast.Recast
} }
else else
{ {
RcRecast.CalcGridSize(this.bmin, this.bmax, cfg.Cs, out width, out height); RcCommons.CalcGridSize(this.bmin, this.bmax, cfg.Cs, out width, out height);
} }
} }
} }

View File

@ -1,29 +1,59 @@
using DotRecast.Core; using DotRecast.Core;
namespace DotRecast.Recast namespace DotRecast.Recast
{ {
public class RcBuilderResult public class RcBuilderResult
{ {
public readonly int TileX; public readonly int tileX;
public readonly int TileZ; public readonly int tileZ;
public readonly RcHeightfield SolidHeightfiled; private readonly RcCompactHeightfield chf;
public readonly RcCompactHeightfield CompactHeightfield; private readonly RcContourSet cs;
public readonly RcContourSet ContourSet; private readonly RcPolyMesh pmesh;
public readonly RcPolyMesh Mesh; private readonly RcPolyMeshDetail dmesh;
public readonly RcPolyMeshDetail MeshDetail; private readonly RcHeightfield solid;
public readonly RcContext Context; private readonly RcTelemetry telemetry;
public RcBuilderResult(int tileX, int tileZ, RcHeightfield solidHeightfiled, RcCompactHeightfield compactHeightfield, RcContourSet contourSet, RcPolyMesh mesh, RcPolyMeshDetail meshDetail, RcContext ctx) public RcBuilderResult(int tileX, int tileZ, RcHeightfield solid, RcCompactHeightfield chf, RcContourSet cs, RcPolyMesh pmesh, RcPolyMeshDetail dmesh, RcTelemetry ctx)
{ {
TileX = tileX; this.tileX = tileX;
TileZ = tileZ; this.tileZ = tileZ;
SolidHeightfiled = solidHeightfiled; this.solid = solid;
CompactHeightfield = compactHeightfield; this.chf = chf;
ContourSet = contourSet; this.cs = cs;
Mesh = mesh; this.pmesh = pmesh;
MeshDetail = meshDetail; this.dmesh = dmesh;
Context = ctx; telemetry = ctx;
}
public RcPolyMesh GetMesh()
{
return pmesh;
}
public RcPolyMeshDetail GetMeshDetail()
{
return dmesh;
}
public RcCompactHeightfield GetCompactHeightfield()
{
return chf;
}
public RcContourSet GetContourSet()
{
return cs;
}
public RcHeightfield GetSolidHeightfield()
{
return solid;
}
public RcTelemetry GetTelemetry()
{
return telemetry;
} }
} }
} }

View File

@ -1,7 +1,7 @@
/* /*
Copyright (c) 2009-2010 Mikko Mononen memon@inside.org Copyright (c) 2009-2010 Mikko Mononen memon@inside.org
recast4j copyright (c) 2015-2019 Piotr Piastucki piotr@jtilia.org recast4j copyright (c) 2015-2019 Piotr Piastucki piotr@jtilia.org
DotRecast Copyright (c) 2023-2024 Choi Ikpil ikpil@naver.com DotRecast Copyright (c) 2023 Choi Ikpil ikpil@naver.com
This software is provided 'as-is', without any express or implied This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages warranty. In no event will the authors be held liable for any damages
@ -24,81 +24,10 @@ using DotRecast.Core.Numerics;
namespace DotRecast.Recast namespace DotRecast.Recast
{ {
public static class RcRecast using static RcConstants;
public static class RcCommons
{ {
/// Represents the null area.
/// When a data element is given this value it is considered to no longer be
/// assigned to a usable area. (E.g. It is un-walkable.)
public const int RC_NULL_AREA = 0;
/// The default area id used to indicate a walkable polygon.
/// This is also the maximum allowed area id, and the only non-null area id
/// recognized by some steps in the build process.
public const int RC_WALKABLE_AREA = 63;
/// The value returned by #rcGetCon if the specified direction is not connected
/// to another span. (Has no neighbor.)
public const int RC_NOT_CONNECTED = 0x3f;
/// Defines the number of bits allocated to rcSpan::smin and rcSpan::smax.
public const int RC_SPAN_HEIGHT_BITS = 20;
/// Defines the maximum value for rcSpan::smin and rcSpan::smax.
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;
// Must be 255 or smaller (not 256) because layer IDs are stored as
// a byte where 255 is a special value.
public const int RC_MAX_LAYERS = RC_NOT_CONNECTED;
public const int RC_MAX_NEIS = 16;
/// Heighfield border flag.
/// If a heightfield region ID has this bit set, then the region is a border
/// region and its spans are considered unwalkable.
/// (Used during the region and contour build process.)
/// @see rcCompactSpan::reg
public const int RC_BORDER_REG = 0x8000;
/// Polygon touches multiple regions.
/// If a polygon has this region ID it was merged with or created
/// from polygons of different regions during the polymesh
/// build step that removes redundant border vertices.
/// (Used during the polymesh and detail polymesh build processes)
/// @see rcPolyMesh::regs
public const int RC_MULTIPLE_REGS = 0;
// Border vertex flag.
/// If a region ID has this bit set, then the associated element lies on
/// a tile border. If a contour vertex's region ID has this bit set, the
/// vertex will later be removed in order to match the segments and vertices
/// at tile boundaries.
/// (Used during the build process.)
/// @see rcCompactSpan::reg, #rcContour::verts, #rcContour::rverts
public const int RC_BORDER_VERTEX = 0x10000;
/// Area border flag.
/// If a region ID has this bit set, then the associated element lies on
/// the border of an area.
/// (Used during the region and contour build process.)
/// @see rcCompactSpan::reg, #rcContour::verts, #rcContour::rverts
public const int RC_AREA_BORDER = 0x20000;
/// Applied to the region id field of contour vertices in order to extract the region id.
/// The region id field of a vertex may have several flags applied to it. So the
/// fields value can't be used directly.
/// @see rcContour::verts, rcContour::rverts
public const int RC_CONTOUR_REG_MASK = 0xffff;
/// A value which indicates an invalid index within a mesh.
/// @note This does not necessarily indicate an error.
/// @see rcPolyMesh::polys
public const int RC_MESH_NULL_IDX = 0xffff;
public const int RC_LOG_WARNING = 1;
private static readonly int[] DirOffsetX = { -1, 0, 1, 0, }; private static readonly int[] DirOffsetX = { -1, 0, 1, 0, };
private static readonly int[] DirOffsetY = { 0, 1, 0, -1 }; private static readonly int[] DirOffsetY = { 0, 1, 0, -1 };
private static readonly int[] DirForOffset = { 3, 0, -1, 2, 1 }; private static readonly int[] DirForOffset = { 3, 0, -1, 2, 1 };
@ -107,7 +36,7 @@ namespace DotRecast.Recast
/// @param[in] span The span to update. /// @param[in] span The span to update.
/// @param[in] direction The direction to set. [Limits: 0 <= value < 4] /// @param[in] direction The direction to set. [Limits: 0 <= value < 4]
/// @param[in] neighborIndex The index of the neighbor span. /// @param[in] neighborIndex The index of the neighbor span.
public static void SetCon(RcCompactSpanBuilder span, int direction, int neighborIndex) public static void SetCon(ref RcCompactSpan span, int direction, int neighborIndex)
{ {
int shift = direction * 6; int shift = direction * 6;
int con = span.con; int con = span.con;
@ -118,7 +47,7 @@ namespace DotRecast.Recast
/// @param[in] span The span to check. /// @param[in] span The span to check.
/// @param[in] direction The direction to check. [Limits: 0 <= value < 4] /// @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. /// @return The neighbor connection data for the specified direction, or #RC_NOT_CONNECTED if there is no connection.
public static int GetCon(ref RcCompactSpan s, int dir) public static int GetCon(RcCompactSpan s, int dir)
{ {
int shift = dir * 6; int shift = dir * 6;
return (s.con >> shift) & 0x3f; return (s.con >> shift) & 0x3f;
@ -190,7 +119,7 @@ namespace DotRecast.Recast
/// See the #rcConfig documentation for more information on the configuration parameters. /// See the #rcConfig documentation for more information on the configuration parameters.
/// ///
/// @see rcHeightfield, rcClearUnwalkableTriangles, rcRasterizeTriangles /// @see rcHeightfield, rcClearUnwalkableTriangles, rcRasterizeTriangles
public static int[] MarkWalkableTriangles(RcContext ctx, float walkableSlopeAngle, float[] verts, int[] tris, int nt, RcAreaModification areaMod) public static int[] MarkWalkableTriangles(RcTelemetry ctx, float walkableSlopeAngle, float[] verts, int[] tris, int nt, RcAreaModification areaMod)
{ {
int[] areas = new int[nt]; int[] areas = new int[nt];
float walkableThr = MathF.Cos(walkableSlopeAngle / 180.0f * MathF.PI); float walkableThr = MathF.Cos(walkableSlopeAngle / 180.0f * MathF.PI);
@ -198,10 +127,7 @@ namespace DotRecast.Recast
for (int i = 0; i < nt; ++i) for (int i = 0; i < nt; ++i)
{ {
int tri = i * 3; int tri = i * 3;
RcVec3f v0 = RcVec.Create(verts, tris[tri + 0] * 3); CalcTriNormal(verts, tris[tri], tris[tri + 1], tris[tri + 2], ref norm);
RcVec3f v1 = RcVec.Create(verts, tris[tri + 1] * 3);
RcVec3f v2 = RcVec.Create(verts, tris[tri + 2] * 3);
CalcTriNormal(v0, v1, v2, ref norm);
// Check if the face is walkable. // Check if the face is walkable.
if (norm.Y > walkableThr) if (norm.Y > walkableThr)
areas[i] = areaMod.Apply(areas[i]); areas[i] = areaMod.Apply(areas[i]);
@ -210,10 +136,10 @@ namespace DotRecast.Recast
return areas; return areas;
} }
public static void CalcTriNormal(RcVec3f v0, RcVec3f v1, RcVec3f v2, ref RcVec3f norm) public static void CalcTriNormal(float[] verts, int v0, int v1, int v2, ref RcVec3f norm)
{ {
var e0 = v1 - v0; var e0 = RcVecUtils.Subtract(verts, v1 * 3, v0 * 3);
var e1 = v2 - v0; var e1 = RcVecUtils.Subtract(verts, v2 * 3, v0 * 3);
norm = RcVec3f.Cross(e0, e1); norm = RcVec3f.Cross(e0, e1);
norm = RcVec3f.Normalize(norm); norm = RcVec3f.Normalize(norm);
} }
@ -227,7 +153,7 @@ namespace DotRecast.Recast
/// See the #rcConfig documentation for more information on the configuration parameters. /// See the #rcConfig documentation for more information on the configuration parameters.
/// ///
/// @see rcHeightfield, rcClearUnwalkableTriangles, rcRasterizeTriangles /// @see rcHeightfield, rcClearUnwalkableTriangles, rcRasterizeTriangles
public static void ClearUnwalkableTriangles(RcContext ctx, float walkableSlopeAngle, float[] verts, int nv, int[] tris, int nt, int[] areas) public static void ClearUnwalkableTriangles(RcTelemetry ctx, float walkableSlopeAngle, float[] verts, int nv, int[] tris, int nt, int[] areas)
{ {
float walkableThr = MathF.Cos(walkableSlopeAngle / 180.0f * MathF.PI); float walkableThr = MathF.Cos(walkableSlopeAngle / 180.0f * MathF.PI);
@ -236,10 +162,7 @@ namespace DotRecast.Recast
for (int i = 0; i < nt; ++i) for (int i = 0; i < nt; ++i)
{ {
int tri = i * 3; int tri = i * 3;
RcVec3f v0 = RcVec.Create(verts, tris[tri + 0] * 3); CalcTriNormal(verts, tris[tri], tris[tri + 1], tris[tri + 2], ref norm);
RcVec3f v1 = RcVec.Create(verts, tris[tri + 1] * 3);
RcVec3f v2 = RcVec.Create(verts, tris[tri + 2] * 3);
CalcTriNormal(v0, v1, v2, ref norm);
// Check if the face is walkable. // Check if the face is walkable.
if (norm.Y <= walkableThr) if (norm.Y <= walkableThr)
areas[i] = RC_NULL_AREA; areas[i] = RC_NULL_AREA;

View File

@ -1,7 +1,7 @@
/* /*
Copyright (c) 2009-2010 Mikko Mononen memon@inside.org Copyright (c) 2009-2010 Mikko Mononen memon@inside.org
recast4j copyright (c) 2015-2019 Piotr Piastucki piotr@jtilia.org recast4j copyright (c) 2015-2019 Piotr Piastucki piotr@jtilia.org
DotRecast Copyright (c) 2023-2024 Choi Ikpil ikpil@naver.com DotRecast Copyright (c) 2023 Choi Ikpil ikpil@naver.com
This software is provided 'as-is', without any express or implied This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages warranty. In no event will the authors be held liable for any damages

View File

@ -1,7 +1,7 @@
/* /*
Copyright (c) 2009-2010 Mikko Mononen memon@inside.org Copyright (c) 2009-2010 Mikko Mononen memon@inside.org
recast4j copyright (c) 2015-2019 Piotr Piastucki piotr@jtilia.org recast4j copyright (c) 2015-2019 Piotr Piastucki piotr@jtilia.org
DotRecast Copyright (c) 2023-2024 Choi Ikpil ikpil@naver.com DotRecast Copyright (c) 2023 Choi Ikpil ikpil@naver.com
This software is provided 'as-is', without any express or implied This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages warranty. In no event will the authors be held liable for any damages

View File

@ -1,7 +1,7 @@
/* /*
Copyright (c) 2009-2010 Mikko Mononen memon@inside.org Copyright (c) 2009-2010 Mikko Mononen memon@inside.org
recast4j copyright (c) 2015-2019 Piotr Piastucki piotr@jtilia.org recast4j copyright (c) 2015-2019 Piotr Piastucki piotr@jtilia.org
DotRecast Copyright (c) 2023-2024 Choi Ikpil ikpil@naver.com DotRecast Copyright (c) 2023 Choi Ikpil ikpil@naver.com
This software is provided 'as-is', without any express or implied This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages warranty. In no event will the authors be held liable for any damages
@ -21,26 +21,18 @@ freely, subject to the following restrictions:
namespace DotRecast.Recast namespace DotRecast.Recast
{ {
/** Represents a span of unobstructed space within a compact heightfield. */ /** Represents a span of unobstructed space within a compact heightfield. */
public readonly struct RcCompactSpan public struct RcCompactSpan
{ {
/** The lower extent of the span. (Measured from the heightfield's base.) */ /** The lower extent of the span. (Measured from the heightfield's base.) */
public readonly int y; public int y;
/** The id of the region the span belongs to. (Or zero if not in a region.) */ /** The id of the region the span belongs to. (Or zero if not in a region.) */
public readonly int reg; public int reg;
/** Packed neighbor connection data. */ /** Packed neighbor connection data. */
public readonly int con; public int con;
/** The height of the span. (Measured from #y.) */ /** The height of the span. (Measured from #y.) */
public readonly int h; public int h;
public RcCompactSpan(RcCompactSpanBuilder span)
{
y = span.y;
reg = span.reg;
con = span.con;
h = span.h;
}
} }
} }

View File

@ -1,40 +0,0 @@
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

@ -1,6 +1,6 @@
/* /*
recast4j copyright (c) 2021 Piotr Piastucki piotr@jtilia.org recast4j copyright (c) 2021 Piotr Piastucki piotr@jtilia.org
DotRecast Copyright (c) 2023-2024 Choi Ikpil ikpil@naver.com DotRecast Copyright (c) 2023 Choi Ikpil ikpil@naver.com
This software is provided 'as-is', without any express or implied This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages warranty. In no event will the authors be held liable for any damages
@ -18,22 +18,21 @@ freely, subject to the following restrictions:
*/ */
using System; using System;
using System.Linq;
using DotRecast.Core; using DotRecast.Core;
using static DotRecast.Recast.RcConstants;
namespace DotRecast.Recast namespace DotRecast.Recast
{ {
using static RcRecast; using static RcCommons;
public static class RcCompacts public static class RcCompacts
{ {
private const int MAX_HEIGHT = RC_SPAN_MAX_HEIGHT; private const int MAX_LAYERS = RC_NOT_CONNECTED - 1;
private const int MAX_HEIGHT = RcConstants.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. /// 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. /// Various filters may be applied, then the distance field and regions built.
@ -42,123 +41,113 @@ namespace DotRecast.Recast
/// See the #rcConfig documentation for more information on the configuration parameters. /// See the #rcConfig documentation for more information on the configuration parameters.
/// ///
/// @see rcAllocCompactHeightfield, rcHeightfield, rcCompactHeightfield, rcConfig /// @see rcAllocCompactHeightfield, rcHeightfield, rcCompactHeightfield, rcConfig
/// @ingroup recast public static RcCompactHeightfield BuildCompactHeightfield(RcTelemetry ctx, int walkableHeight, int walkableClimb,
/// RcHeightfield hf)
/// @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 = context.ScopedTimer(RcTimerLabel.RC_TIMER_BUILD_COMPACTHEIGHTFIELD); using var timer = ctx.ScopedTimer(RcTimerLabel.RC_TIMER_BUILD_COMPACTHEIGHTFIELD);
int xSize = heightfield.width; RcCompactHeightfield chf = new RcCompactHeightfield();
int zSize = heightfield.height; int w = hf.width;
int spanCount = GetHeightFieldSpanCount(context, heightfield); int h = hf.height;
int spanCount = GetHeightFieldSpanCount(hf);
// Fill in header. // Fill in header.
RcCompactHeightfield compactHeightfield = new RcCompactHeightfield(); chf.width = w;
compactHeightfield.width = xSize; chf.height = h;
compactHeightfield.height = zSize; chf.borderSize = hf.borderSize;
compactHeightfield.borderSize = heightfield.borderSize; chf.spanCount = spanCount;
compactHeightfield.spanCount = spanCount; chf.walkableHeight = walkableHeight;
compactHeightfield.walkableHeight = walkableHeight; chf.walkableClimb = walkableClimb;
compactHeightfield.walkableClimb = walkableClimb; chf.maxRegions = 0;
compactHeightfield.maxRegions = 0; chf.bmin = hf.bmin;
compactHeightfield.bmin = heightfield.bmin; chf.bmax = hf.bmax;
compactHeightfield.bmax = heightfield.bmax; chf.bmax.Y += walkableHeight * hf.ch;
compactHeightfield.bmax.Y += walkableHeight * heightfield.ch; chf.cs = hf.cs;
compactHeightfield.cs = heightfield.cs; chf.ch = hf.ch;
compactHeightfield.ch = heightfield.ch; chf.cells = new RcCompactCell[w * h];
compactHeightfield.cells = new RcCompactCell[xSize * zSize]; chf.spans = new RcCompactSpan[spanCount];
//chf.spans = new RcCompactSpan[spanCount]; chf.areas = new int[spanCount];
compactHeightfield.areas = new int[spanCount];
var tempSpans = Enumerable for (int i = 0; i < chf.spans.Length; i++)
.Range(0, spanCount) {
.Select(x => RcCompactSpanBuilder.NewBuilder()) chf.spans[i] = new RcCompactSpan();
.ToArray(); }
// Fill in cells and spans. // Fill in cells and spans.
int currentCellIndex = 0; int idx = 0;
int numColumns = xSize * zSize; for (int y = 0; y < h; ++y)
for (int columnIndex = 0; columnIndex < numColumns; ++columnIndex)
{ {
RcSpan span = heightfield.spans[columnIndex]; for (int x = 0; x < w; ++x)
{
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 there are no spans at this cell, just leave the data to index=0, count=0.
if (span == null) if (s == null)
continue; continue;
int tmpIdx = currentCellIndex; int tmpIdx = idx;
int tmpCount = 0; int tmpCount = 0;
for (; span != null; span = span.next) while (s != null)
{ {
if (span.area != RC_NULL_AREA) if (s.area != RC_NULL_AREA)
{ {
int bot = span.smax; int bot = s.smax;
int top = span.next != null ? (int)span.next.smin : MAX_HEIGHT; int top = s.next != null ? (int)s.next.smin : MAX_HEIGHT;
tempSpans[currentCellIndex].y = Math.Clamp(bot, 0, MAX_HEIGHT); chf.spans[idx].y = Math.Clamp(bot, 0, MAX_HEIGHT);
tempSpans[currentCellIndex].h = Math.Clamp(top - bot, 0, MAX_HEIGHT); chf.spans[idx].h = Math.Clamp(top - bot, 0, MAX_HEIGHT);
compactHeightfield.areas[currentCellIndex] = span.area; chf.areas[idx] = s.area;
currentCellIndex++; idx++;
tmpCount++; tmpCount++;
} }
s = s.next;
} }
compactHeightfield.cells[columnIndex] = new RcCompactCell(tmpIdx, tmpCount); chf.cells[x + y * w] = new RcCompactCell(tmpIdx, tmpCount);
}
} }
// Find neighbour connections. // Find neighbour connections.
const int MAX_LAYERS = RC_NOT_CONNECTED - 1; int tooHighNeighbour = 0;
int maxLayerIndex = 0; for (int y = 0; y < h; ++y)
int zStride = xSize; // for readability
for (int z = 0; z < zSize; ++z)
{ {
for (int x = 0; x < xSize; ++x) for (int x = 0; x < w; ++x)
{ {
ref RcCompactCell cell = ref compactHeightfield.cells[x + z * zStride]; RcCompactCell c = chf.cells[x + y * w];
for (int i = cell.index, ni = cell.index + cell.count; i < ni; ++i) for (int i = c.index, ni = c.index + c.count; i < ni; ++i)
{ {
ref RcCompactSpanBuilder s = ref tempSpans[i]; ref RcCompactSpan s = ref chf.spans[i];
for (int dir = 0; dir < 4; ++dir) for (int dir = 0; dir < 4; ++dir)
{ {
SetCon(s, dir, RC_NOT_CONNECTED); SetCon(ref s, dir, RC_NOT_CONNECTED);
int neighborX = x + GetDirOffsetX(dir); int nx = x + GetDirOffsetX(dir);
int neighborZ = z + GetDirOffsetY(dir); int ny = y + GetDirOffsetY(dir);
// First check that the neighbour cell is in bounds. // First check that the neighbour cell is in bounds.
if (neighborX < 0 || neighborZ < 0 || neighborX >= xSize || neighborZ >= zSize) if (nx < 0 || ny < 0 || nx >= w || ny >= h)
{
continue; continue;
}
// Iterate over all neighbour spans and check if any of the is // Iterate over all neighbour spans and check if any of the is
// accessible from current cell. // accessible from current cell.
ref RcCompactCell neighborCell = ref compactHeightfield.cells[neighborX + neighborZ * xSize]; RcCompactCell nc = chf.cells[nx + ny * w];
for (int k = neighborCell.index, nk = neighborCell.index + neighborCell.count; k < nk; ++k) for (int k = nc.index, nk = nc.index + nc.count; k < nk; ++k)
{ {
ref RcCompactSpanBuilder neighborSpan = ref tempSpans[k]; ref RcCompactSpan ns = ref chf.spans[k];
int bot = Math.Max(s.y, neighborSpan.y); int bot = Math.Max(s.y, ns.y);
int top = Math.Min(s.y + s.h, neighborSpan.y + neighborSpan.h); int top = Math.Min(s.y + s.h, ns.y + ns.h);
// Check that the gap between the spans is walkable, // Check that the gap between the spans is walkable,
// and that the climb height between the gaps is not too high. // and that the climb height between the gaps is not too high.
if ((top - bot) >= walkableHeight && MathF.Abs(neighborSpan.y - s.y) <= walkableClimb) if ((top - bot) >= walkableHeight && MathF.Abs(ns.y - s.y) <= walkableClimb)
{ {
// Mark direction as walkable. // Mark direction as walkable.
int layerIndex = k - neighborCell.index; int lidx = k - nc.index;
if (layerIndex < 0 || layerIndex > MAX_LAYERS) if (lidx < 0 || lidx > MAX_LAYERS)
{ {
maxLayerIndex = Math.Max(maxLayerIndex, layerIndex); tooHighNeighbour = Math.Max(tooHighNeighbour, lidx);
continue; continue;
} }
SetCon(s, dir, layerIndex); SetCon(ref s, dir, lidx);
break; break;
} }
} }
@ -167,31 +156,27 @@ namespace DotRecast.Recast
} }
} }
if (maxLayerIndex > MAX_LAYERS) if (tooHighNeighbour > MAX_LAYERS)
{ {
throw new Exception($"rcBuildCompactHeightfield: Heightfield has too many layers {maxLayerIndex} (max: {MAX_LAYERS})"); throw new Exception("rcBuildCompactHeightfield: Heightfield has too many layers " + tooHighNeighbour
+ " (max: " + MAX_LAYERS + ")");
} }
compactHeightfield.spans = tempSpans.Select(x => x.Build()).ToArray(); return chf;
return compactHeightfield;
} }
/// Returns the number of spans contained in the specified heightfield. private static int GetHeightFieldSpanCount(RcHeightfield hf)
/// @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 numCols = heightfield.width * heightfield.height; int w = hf.width;
int h = hf.height;
int spanCount = 0; int spanCount = 0;
for (int columnIndex = 0; columnIndex < numCols; ++columnIndex) for (int y = 0; y < h; ++y)
{ {
for (RcSpan span = heightfield.spans[columnIndex]; span != null; span = span.next) for (int x = 0; x < w; ++x)
{ {
if (span.area != RC_NULL_AREA) for (RcSpan s = hf.spans[x + y * w]; s != null; s = s.next)
{ {
if (s.area != RC_NULL_AREA)
spanCount++; spanCount++;
} }
} }

View File

@ -1,7 +1,7 @@
/* /*
Copyright (c) 2009-2010 Mikko Mononen memon@inside.org Copyright (c) 2009-2010 Mikko Mononen memon@inside.org
recast4j copyright (c) 2015-2019 Piotr Piastucki piotr@jtilia.org recast4j copyright (c) 2015-2019 Piotr Piastucki piotr@jtilia.org
DotRecast Copyright (c) 2023-2024 Choi Ikpil ikpil@naver.com DotRecast Copyright (c) 2023 Choi Ikpil ikpil@naver.com
This software is provided 'as-is', without any express or implied This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages warranty. In no event will the authors be held liable for any damages

View File

@ -0,0 +1,89 @@
/*
Copyright (c) 2009-2010 Mikko Mononen memon@inside.org
recast4j copyright (c) 2015-2019 Piotr Piastucki piotr@jtilia.org
DotRecast Copyright (c) 2023 Choi Ikpil ikpil@naver.com
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
*/
namespace DotRecast.Recast
{
public static class RcConstants
{
/// Represents the null area.
/// When a data element is given this value it is considered to no longer be
/// assigned to a usable area. (E.g. It is un-walkable.)
public const int RC_NULL_AREA = 0;
/// The default area id used to indicate a walkable polygon.
/// This is also the maximum allowed area id, and the only non-null area id
/// recognized by some steps in the build process.
public const int RC_WALKABLE_AREA = 63;
/// The value returned by #rcGetCon if the specified direction is not connected
/// to another span. (Has no neighbor.)
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;
/// Defines the maximum value for rcSpan::smin and rcSpan::smax.
public const int SPAN_MAX_HEIGHT = (1 << SPAN_HEIGHT_BITS) - 1;
/// Heighfield border flag.
/// If a heightfield region ID has this bit set, then the region is a border
/// region and its spans are considered unwalkable.
/// (Used during the region and contour build process.)
/// @see rcCompactSpan::reg
public const int RC_BORDER_REG = 0x8000;
/// Polygon touches multiple regions.
/// If a polygon has this region ID it was merged with or created
/// from polygons of different regions during the polymesh
/// build step that removes redundant border vertices.
/// (Used during the polymesh and detail polymesh build processes)
/// @see rcPolyMesh::regs
public const int RC_MULTIPLE_REGS = 0;
// Border vertex flag.
/// If a region ID has this bit set, then the associated element lies on
/// a tile border. If a contour vertex's region ID has this bit set, the
/// vertex will later be removed in order to match the segments and vertices
/// at tile boundaries.
/// (Used during the build process.)
/// @see rcCompactSpan::reg, #rcContour::verts, #rcContour::rverts
public const int RC_BORDER_VERTEX = 0x10000;
/// Area border flag.
/// If a region ID has this bit set, then the associated element lies on
/// the border of an area.
/// (Used during the region and contour build process.)
/// @see rcCompactSpan::reg, #rcContour::verts, #rcContour::rverts
public const int RC_AREA_BORDER = 0x20000;
/// Applied to the region id field of contour vertices in order to extract the region id.
/// The region id field of a vertex may have several flags applied to it. So the
/// fields value can't be used directly.
/// @see rcContour::verts, rcContour::rverts
public const int RC_CONTOUR_REG_MASK = 0xffff;
/// A value which indicates an invalid index within a mesh.
/// @note This does not necessarily indicate an error.
/// @see rcPolyMesh::polys
public const int RC_MESH_NULL_IDX = 0xffff;
public const int RC_LOG_WARNING = 1;
}
}

View File

@ -1,7 +1,7 @@
/* /*
Copyright (c) 2009-2010 Mikko Mononen memon@inside.org Copyright (c) 2009-2010 Mikko Mononen memon@inside.org
recast4j copyright (c) 2015-2019 Piotr Piastucki piotr@jtilia.org recast4j copyright (c) 2015-2019 Piotr Piastucki piotr@jtilia.org
DotRecast Copyright (c) 2023-2024 Choi Ikpil ikpil@naver.com DotRecast Copyright (c) 2023 Choi Ikpil ikpil@naver.com
This software is provided 'as-is', without any express or implied This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages warranty. In no event will the authors be held liable for any damages
@ -20,14 +20,25 @@ freely, subject to the following restrictions:
namespace DotRecast.Recast namespace DotRecast.Recast
{ {
/// Represents a simple, non-overlapping contour in field space. /** Represents a simple, non-overlapping contour in field space. */
public class RcContour public class RcContour
{ {
public int[] verts; //< Simplified contour vertex and connection data. [Size: 4 * #nverts] /** Simplified contour vertex and connection data. [Size: 4 * #nverts] */
public int nverts; //< The number of vertices in the simplified contour. public int[] verts;
public int[] rverts; //< Raw contour vertex and connection data. [Size: 4 * #nrverts]
public int nrverts; //< The number of vertices in the raw contour. /** The number of vertices in the simplified contour. */
public int reg; //< The region id of the contour. public int nverts;
public int area; //< The area id of the contour.
/** Raw contour vertex and connection data. [Size: 4 * #nrverts] */
public int[] rverts;
/** The number of vertices in the raw contour. */
public int nrverts;
/** The region id of the contour. */
public int area;
/** The area id of the contour. */
public int reg;
} }
} }

View File

@ -1,4 +1,4 @@
namespace DotRecast.Recast namespace DotRecast.Recast
{ {
public class RcContourHole public class RcContourHole
{ {

View File

@ -1,4 +1,4 @@
using System.Collections.Generic; using System.Collections.Generic;
namespace DotRecast.Recast namespace DotRecast.Recast
{ {

View File

@ -1,4 +1,4 @@
namespace DotRecast.Recast namespace DotRecast.Recast
{ {
public class RcContourRegion public class RcContourRegion
{ {

View File

@ -1,7 +1,7 @@
/* /*
Copyright (c) 2009-2010 Mikko Mononen memon@inside.org Copyright (c) 2009-2010 Mikko Mononen memon@inside.org
recast4j copyright (c) 2015-2019 Piotr Piastucki piotr@jtilia.org recast4j copyright (c) 2015-2019 Piotr Piastucki piotr@jtilia.org
DotRecast Copyright (c) 2023-2024 Choi Ikpil ikpil@naver.com DotRecast Copyright (c) 2023 Choi Ikpil ikpil@naver.com
This software is provided 'as-is', without any express or implied This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages warranty. In no event will the authors be held liable for any damages

View File

@ -1,7 +1,7 @@
/* /*
Copyright (c) 2009-2010 Mikko Mononen memon@inside.org Copyright (c) 2009-2010 Mikko Mononen memon@inside.org
recast4j copyright (c) 2015-2019 Piotr Piastucki piotr@jtilia.org recast4j copyright (c) 2015-2019 Piotr Piastucki piotr@jtilia.org
DotRecast Copyright (c) 2023-2024 Choi Ikpil ikpil@naver.com DotRecast Copyright (c) 2023 Choi Ikpil ikpil@naver.com
This software is provided 'as-is', without any express or implied This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages warranty. In no event will the authors be held liable for any damages
@ -24,8 +24,8 @@ using DotRecast.Core;
namespace DotRecast.Recast namespace DotRecast.Recast
{ {
using static RcConstants;
using static RcRecast; using static RcCommons;
public static class RcContours public static class RcContours
{ {
@ -33,7 +33,7 @@ namespace DotRecast.Recast
{ {
isBorderVertex = false; isBorderVertex = false;
ref RcCompactSpan s = ref chf.spans[i]; RcCompactSpan s = chf.spans[i];
int ch = s.y; int ch = s.y;
int dirp = (dir + 1) & 0x3; int dirp = (dir + 1) & 0x3;
@ -46,39 +46,39 @@ namespace DotRecast.Recast
// border vertices which are in between two areas to be removed. // border vertices which are in between two areas to be removed.
regs[0] = chf.spans[i].reg | (chf.areas[i] << 16); regs[0] = chf.spans[i].reg | (chf.areas[i] << 16);
if (GetCon(ref s, dir) != RC_NOT_CONNECTED) if (GetCon(s, dir) != RC_NOT_CONNECTED)
{ {
int ax = x + GetDirOffsetX(dir); int ax = x + GetDirOffsetX(dir);
int ay = y + GetDirOffsetY(dir); int ay = y + GetDirOffsetY(dir);
int ai = chf.cells[ax + ay * chf.width].index + GetCon(ref s, dir); int ai = chf.cells[ax + ay * chf.width].index + GetCon(s, dir);
ref RcCompactSpan @as = ref chf.spans[ai]; RcCompactSpan @as = chf.spans[ai];
ch = Math.Max(ch, @as.y); ch = Math.Max(ch, @as.y);
regs[1] = chf.spans[ai].reg | (chf.areas[ai] << 16); regs[1] = chf.spans[ai].reg | (chf.areas[ai] << 16);
if (GetCon(ref @as, dirp) != RC_NOT_CONNECTED) if (GetCon(@as, dirp) != RC_NOT_CONNECTED)
{ {
int ax2 = ax + GetDirOffsetX(dirp); int ax2 = ax + GetDirOffsetX(dirp);
int ay2 = ay + GetDirOffsetY(dirp); int ay2 = ay + GetDirOffsetY(dirp);
int ai2 = chf.cells[ax2 + ay2 * chf.width].index + GetCon(ref @as, dirp); int ai2 = chf.cells[ax2 + ay2 * chf.width].index + GetCon(@as, dirp);
ref RcCompactSpan as2 = ref chf.spans[ai2]; RcCompactSpan as2 = chf.spans[ai2];
ch = Math.Max(ch, as2.y); ch = Math.Max(ch, as2.y);
regs[2] = chf.spans[ai2].reg | (chf.areas[ai2] << 16); regs[2] = chf.spans[ai2].reg | (chf.areas[ai2] << 16);
} }
} }
if (GetCon(ref s, dirp) != RC_NOT_CONNECTED) if (GetCon(s, dirp) != RC_NOT_CONNECTED)
{ {
int ax = x + GetDirOffsetX(dirp); int ax = x + GetDirOffsetX(dirp);
int ay = y + GetDirOffsetY(dirp); int ay = y + GetDirOffsetY(dirp);
int ai = chf.cells[ax + ay * chf.width].index + GetCon(ref s, dirp); int ai = chf.cells[ax + ay * chf.width].index + GetCon(s, dirp);
ref RcCompactSpan @as = ref chf.spans[ai]; RcCompactSpan @as = chf.spans[ai];
ch = Math.Max(ch, @as.y); ch = Math.Max(ch, @as.y);
regs[3] = chf.spans[ai].reg | (chf.areas[ai] << 16); regs[3] = chf.spans[ai].reg | (chf.areas[ai] << 16);
if (GetCon(ref @as, dir) != RC_NOT_CONNECTED) if (GetCon(@as, dir) != RC_NOT_CONNECTED)
{ {
int ax2 = ax + GetDirOffsetX(dir); int ax2 = ax + GetDirOffsetX(dir);
int ay2 = ay + GetDirOffsetY(dir); int ay2 = ay + GetDirOffsetY(dir);
int ai2 = chf.cells[ax2 + ay2 * chf.width].index + GetCon(ref @as, dir); int ai2 = chf.cells[ax2 + ay2 * chf.width].index + GetCon(@as, dir);
ref RcCompactSpan as2 = ref chf.spans[ai2]; RcCompactSpan as2 = chf.spans[ai2];
ch = Math.Max(ch, as2.y); ch = Math.Max(ch, as2.y);
regs[2] = chf.spans[ai2].reg | (chf.areas[ai2] << 16); regs[2] = chf.spans[ai2].reg | (chf.areas[ai2] << 16);
} }
@ -146,12 +146,12 @@ namespace DotRecast.Recast
} }
int r = 0; int r = 0;
ref RcCompactSpan s = ref chf.spans[i]; RcCompactSpan s = chf.spans[i];
if (GetCon(ref s, dir) != RC_NOT_CONNECTED) if (GetCon(s, dir) != RC_NOT_CONNECTED)
{ {
int ax = x + GetDirOffsetX(dir); int ax = x + GetDirOffsetX(dir);
int ay = y + GetDirOffsetY(dir); int ay = y + GetDirOffsetY(dir);
int ai = chf.cells[ax + ay * chf.width].index + GetCon(ref s, dir); int ai = chf.cells[ax + ay * chf.width].index + GetCon(s, dir);
r = chf.spans[ai].reg; r = chf.spans[ai].reg;
if (area != chf.areas[ai]) if (area != chf.areas[ai])
isAreaBorder = true; isAreaBorder = true;
@ -174,11 +174,11 @@ namespace DotRecast.Recast
int ni = -1; int ni = -1;
int nx = x + GetDirOffsetX(dir); int nx = x + GetDirOffsetX(dir);
int ny = y + GetDirOffsetY(dir); int ny = y + GetDirOffsetY(dir);
ref RcCompactSpan s = ref chf.spans[i]; RcCompactSpan s = chf.spans[i];
if (GetCon(ref s, dir) != RC_NOT_CONNECTED) if (GetCon(s, dir) != RC_NOT_CONNECTED)
{ {
ref RcCompactCell nc = ref chf.cells[nx + ny * chf.width]; RcCompactCell nc = chf.cells[nx + ny * chf.width];
ni = nc.index + GetCon(ref s, dir); ni = nc.index + GetCon(s, dir);
} }
if (ni == -1) if (ni == -1)
@ -614,7 +614,7 @@ namespace DotRecast.Recast
return new int[] { minx, minz, leftmost }; return new int[] { minx, minz, leftmost };
} }
private static void MergeRegionHoles(RcContext ctx, RcContourRegion region) private static void MergeRegionHoles(RcTelemetry ctx, RcContourRegion region)
{ {
// Sort holes from left to right. // Sort holes from left to right.
for (int i = 0; i < region.nholes; i++) for (int i = 0; i < region.nholes; i++)
@ -715,7 +715,7 @@ namespace DotRecast.Recast
/// See the #rcConfig documentation for more information on the configuration parameters. /// See the #rcConfig documentation for more information on the configuration parameters.
/// ///
/// @see rcAllocContourSet, rcCompactHeightfield, rcContourSet, rcConfig /// @see rcAllocContourSet, rcCompactHeightfield, rcContourSet, rcConfig
public static RcContourSet BuildContours(RcContext ctx, RcCompactHeightfield chf, float maxError, int maxEdgeLen, public static RcContourSet BuildContours(RcTelemetry ctx, RcCompactHeightfield chf, float maxError, int maxEdgeLen,
int buildFlags) int buildFlags)
{ {
int w = chf.width; int w = chf.width;
@ -753,11 +753,11 @@ namespace DotRecast.Recast
{ {
for (int x = 0; x < w; ++x) for (int x = 0; x < w; ++x)
{ {
ref RcCompactCell c = ref chf.cells[x + y * w]; RcCompactCell c = chf.cells[x + y * w];
for (int i = c.index, ni = c.index + c.count; i < ni; ++i) for (int i = c.index, ni = c.index + c.count; i < ni; ++i)
{ {
int res = 0; int res = 0;
ref RcCompactSpan s = ref chf.spans[i]; RcCompactSpan s = chf.spans[i];
if (chf.spans[i].reg == 0 || (chf.spans[i].reg & RC_BORDER_REG) != 0) if (chf.spans[i].reg == 0 || (chf.spans[i].reg & RC_BORDER_REG) != 0)
{ {
flags[i] = 0; flags[i] = 0;
@ -767,11 +767,11 @@ namespace DotRecast.Recast
for (int dir = 0; dir < 4; ++dir) for (int dir = 0; dir < 4; ++dir)
{ {
int r = 0; int r = 0;
if (GetCon(ref s, dir) != RC_NOT_CONNECTED) if (GetCon(s, dir) != RC_NOT_CONNECTED)
{ {
int ax = x + GetDirOffsetX(dir); int ax = x + GetDirOffsetX(dir);
int ay = y + GetDirOffsetY(dir); int ay = y + GetDirOffsetY(dir);
int ai = chf.cells[ax + ay * w].index + GetCon(ref s, dir); int ai = chf.cells[ax + ay * w].index + GetCon(s, dir);
r = chf.spans[ai].reg; r = chf.spans[ai].reg;
} }
@ -793,7 +793,7 @@ namespace DotRecast.Recast
{ {
for (int x = 0; x < w; ++x) for (int x = 0; x < w; ++x)
{ {
ref RcCompactCell c = ref chf.cells[x + y * w]; RcCompactCell c = chf.cells[x + y * w];
for (int i = c.index, ni = c.index + c.count; i < ni; ++i) for (int i = c.index, ni = c.index + c.count; i < ni; ++i)
{ {
if (flags[i] == 0 || flags[i] == 0xf) if (flags[i] == 0 || flags[i] == 0xf)

View File

@ -1,7 +1,7 @@
/* /*
Copyright (c) 2009-2010 Mikko Mononen memon@inside.org Copyright (c) 2009-2010 Mikko Mononen memon@inside.org
recast4j copyright (c) 2015-2019 Piotr Piastucki piotr@jtilia.org recast4j copyright (c) 2015-2019 Piotr Piastucki piotr@jtilia.org
DotRecast Copyright (c) 2023-2024 Choi Ikpil ikpil@naver.com DotRecast Copyright (c) 2023 Choi Ikpil ikpil@naver.com
This software is provided 'as-is', without any express or implied This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages warranty. In no event will the authors be held liable for any damages

View File

@ -1,17 +0,0 @@
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

@ -1,6 +1,6 @@
/* /*
recast4j copyright (c) 2021 Piotr Piastucki piotr@jtilia.org recast4j copyright (c) 2021 Piotr Piastucki piotr@jtilia.org
DotRecast Copyright (c) 2023-2024 Choi Ikpil ikpil@naver.com DotRecast Copyright (c) 2023 Choi Ikpil ikpil@naver.com
This software is provided 'as-is', without any express or implied This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages warranty. In no event will the authors be held liable for any damages
@ -20,17 +20,17 @@ freely, subject to the following restrictions:
using System; using System;
using DotRecast.Core; using DotRecast.Core;
using DotRecast.Core.Numerics; using DotRecast.Core.Numerics;
using static DotRecast.Recast.RcConstants;
namespace DotRecast.Recast namespace DotRecast.Recast
{ {
using static RcRecast;
public static class RcFilledVolumeRasterization public static class RcFilledVolumeRasterization
{ {
private const float EPSILON = 0.00001f; 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 }; 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, RcContext ctx) public static void RasterizeSphere(RcHeightfield hf, RcVec3f center, float radius, int area, int flagMergeThr, RcTelemetry ctx)
{ {
using var timer = ctx.ScopedTimer(RcTimerLabel.RC_TIMER_RASTERIZE_SPHERE); using var timer = ctx.ScopedTimer(RcTimerLabel.RC_TIMER_RASTERIZE_SPHERE);
float[] bounds = float[] bounds =
@ -42,7 +42,7 @@ namespace DotRecast.Recast
rectangle => IntersectSphere(rectangle, center, radius * radius)); rectangle => IntersectSphere(rectangle, center, radius * radius));
} }
public static void RasterizeCapsule(RcHeightfield hf, RcVec3f start, RcVec3f end, float radius, int area, int flagMergeThr, RcContext ctx) public static void RasterizeCapsule(RcHeightfield hf, RcVec3f start, RcVec3f end, float radius, int area, int flagMergeThr, RcTelemetry ctx)
{ {
using var timer = ctx.ScopedTimer(RcTimerLabel.RC_TIMER_RASTERIZE_CAPSULE); using var timer = ctx.ScopedTimer(RcTimerLabel.RC_TIMER_RASTERIZE_CAPSULE);
float[] bounds = float[] bounds =
@ -56,7 +56,7 @@ namespace DotRecast.Recast
rectangle => IntersectCapsule(rectangle, start, end, axis, radius * radius)); rectangle => IntersectCapsule(rectangle, start, end, axis, radius * radius));
} }
public static void RasterizeCylinder(RcHeightfield hf, RcVec3f start, RcVec3f end, float radius, int area, int flagMergeThr, RcContext ctx) public static void RasterizeCylinder(RcHeightfield hf, RcVec3f start, RcVec3f end, float radius, int area, int flagMergeThr, RcTelemetry ctx)
{ {
using var timer = ctx.ScopedTimer(RcTimerLabel.RC_TIMER_RASTERIZE_CYLINDER); using var timer = ctx.ScopedTimer(RcTimerLabel.RC_TIMER_RASTERIZE_CYLINDER);
float[] bounds = float[] bounds =
@ -70,7 +70,7 @@ namespace DotRecast.Recast
rectangle => IntersectCylinder(rectangle, start, end, axis, radius * radius)); rectangle => IntersectCylinder(rectangle, start, end, axis, radius * radius));
} }
public static void RasterizeBox(RcHeightfield hf, RcVec3f center, RcVec3f[] halfEdges, int area, int flagMergeThr, RcContext ctx) public static void RasterizeBox(RcHeightfield hf, RcVec3f center, RcVec3f[] halfEdges, int area, int flagMergeThr, RcTelemetry ctx)
{ {
using var timer = ctx.ScopedTimer(RcTimerLabel.RC_TIMER_RASTERIZE_BOX); using var timer = ctx.ScopedTimer(RcTimerLabel.RC_TIMER_RASTERIZE_BOX);
RcVec3f[] normals = RcVec3f[] normals =
@ -105,7 +105,7 @@ namespace DotRecast.Recast
bounds[5] = Math.Max(bounds[5], vertices[i * 3 + 2]); bounds[5] = Math.Max(bounds[5], vertices[i * 3 + 2]);
} }
float[][] planes = RcArrays.Of<float>(6, 4); float[][] planes = RcArrayUtils.Of<float>(6, 4);
for (int i = 0; i < 6; i++) for (int i = 0; i < 6; i++)
{ {
float m = i < 3 ? -1 : 1; float m = i < 3 ? -1 : 1;
@ -120,7 +120,7 @@ namespace DotRecast.Recast
RasterizationFilledShape(hf, bounds, area, flagMergeThr, rectangle => IntersectBox(rectangle, vertices, planes)); RasterizationFilledShape(hf, bounds, area, flagMergeThr, rectangle => IntersectBox(rectangle, vertices, planes));
} }
public static void RasterizeConvex(RcHeightfield hf, float[] vertices, int[] triangles, int area, int flagMergeThr, RcContext ctx) public static void RasterizeConvex(RcHeightfield hf, float[] vertices, int[] triangles, int area, int flagMergeThr, RcTelemetry ctx)
{ {
using var timer = ctx.ScopedTimer(RcTimerLabel.RC_TIMER_RASTERIZE_CONVEX); 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] }; 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 = RcArrays.Of<float>(triangles.Length, 4); float[][] planes = RcArrayUtils.Of<float>(triangles.Length, 4);
float[][] triBounds = RcArrays.Of<float>(triangles.Length / 3, 4); float[][] triBounds = RcArrayUtils.Of<float>(triangles.Length / 3, 4);
for (int i = 0, j = 0; i < triangles.Length; i += 3, j++) for (int i = 0, j = 0; i < triangles.Length; i += 3, j++)
{ {
int a = triangles[i] * 3; int a = triangles[i] * 3;
@ -176,7 +176,7 @@ namespace DotRecast.Recast
private static void Plane(float[][] planes, int p, float[] v1, float[] v2, float[] vertices, int vert) private static void Plane(float[][] planes, int p, float[] v1, float[] v2, float[] vertices, int vert)
{ {
RcVec.Cross(planes[p], v1, v2); RcVecUtils.Cross(planes[p], v1, v2);
planes[p][3] = planes[p][0] * vertices[vert] + planes[p][1] * vertices[vert + 1] + planes[p][2] * vertices[vert + 2]; planes[p][3] = planes[p][0] * vertices[vert] + planes[p][1] * vertices[vert + 1] + planes[p][2] * vertices[vert + 2];
} }
@ -221,8 +221,8 @@ namespace DotRecast.Recast
int smax = (int)MathF.Ceiling((h[1] - hf.bmin.Y) * ich); int smax = (int)MathF.Ceiling((h[1] - hf.bmin.Y) * ich);
if (smin != smax) if (smin != smax)
{ {
int ismin = Math.Clamp(smin, 0, RC_SPAN_MAX_HEIGHT); int ismin = Math.Clamp(smin, 0, SPAN_MAX_HEIGHT);
int ismax = Math.Clamp(smax, ismin + 1, RC_SPAN_MAX_HEIGHT); int ismax = Math.Clamp(smax, ismin + 1, SPAN_MAX_HEIGHT);
RcRasterizations.AddSpan(hf, x, z, ismin, ismax, area, flagMergeThr); RcRasterizations.AddSpan(hf, x, z, ismin, ismax, area, flagMergeThr);
} }
} }
@ -296,8 +296,8 @@ namespace DotRecast.Recast
if (axis.Y * axis.Y > EPSILON) if (axis.Y * axis.Y > EPSILON)
{ {
Span<RcVec3f> rectangleOnStartPlane = stackalloc RcVec3f[4]; RcVec3f[] rectangleOnStartPlane = new RcVec3f[4];
Span<RcVec3f> rectangleOnEndPlane = stackalloc RcVec3f[4]; RcVec3f[] rectangleOnEndPlane = new RcVec3f[4];
float ds = RcVec3f.Dot(axis, start); float ds = RcVec3f.Dot(axis, start);
float de = RcVec3f.Dot(axis, end); float de = RcVec3f.Dot(axis, end);
for (int i = 0; i < 4; i++) for (int i = 0; i < 4; i++)
@ -326,7 +326,7 @@ namespace DotRecast.Recast
return s; return s;
} }
private static float[] CylinderCapIntersection(RcVec3f start, float radiusSqr, float[] s, int i, Span<RcVec3f> rectangleOnPlane) private static float[] CylinderCapIntersection(RcVec3f start, float radiusSqr, float[] s, int i, RcVec3f[] rectangleOnPlane)
{ {
int j = (i + 1) % 4; int j = (i + 1) % 4;
// Ray against sphere intersection // Ray against sphere intersection
@ -507,8 +507,8 @@ namespace DotRecast.Recast
for (int i = 0; i < 8; i++) for (int i = 0; i < 8; i++)
{ {
int vi = i * 3; int vi = i * 3;
if (vertices[vi] >= rectangle[0] && vertices[vi] < rectangle[2] && if (vertices[vi] >= rectangle[0] && vertices[vi] < rectangle[2] && vertices[vi + 2] >= rectangle[1]
vertices[vi + 2] >= rectangle[1] && vertices[vi + 2] < rectangle[3]) && vertices[vi + 2] < rectangle[3])
{ {
yMin = Math.Min(yMin, vertices[vi + 1]); yMin = Math.Min(yMin, vertices[vi + 1]);
yMax = Math.Max(yMax, vertices[vi + 1]); yMax = Math.Max(yMax, vertices[vi + 1]);
@ -525,7 +525,7 @@ namespace DotRecast.Recast
{ {
if (MathF.Abs(planes[j][1]) > EPSILON) if (MathF.Abs(planes[j][1]) > EPSILON)
{ {
float dotNormalPoint = RcVec3f.Dot(new RcVec3f(planes[j]), point); float dotNormalPoint = RcVecUtils.Dot(planes[j], point);
float t = (planes[j][3] - dotNormalPoint) / planes[j][1]; float t = (planes[j][3] - dotNormalPoint) / planes[j][1];
float y = point.Y + t; float y = point.Y + t;
bool valid = true; bool valid = true;
@ -729,15 +729,15 @@ namespace DotRecast.Recast
private static bool RayTriangleIntersection(RcVec3f point, int plane, float[][] planes, out float y) private static bool RayTriangleIntersection(RcVec3f point, int plane, float[][] planes, out float y)
{ {
y = 0.0f; y = 0.0f;
float t = (planes[plane][3] - RcVec3f.Dot(new RcVec3f(planes[plane]), point)) / planes[plane][1]; float t = (planes[plane][3] - RcVecUtils.Dot(planes[plane], point)) / planes[plane][1];
RcVec3f s = new RcVec3f(point.X, point.Y + t, point.Z); float[] s = { point.X, point.Y + t, point.Z };
float u = RcVec3f.Dot(s, new RcVec3f(planes[plane + 1])) - planes[plane + 1][3]; float u = RcVecUtils.Dot(s, planes[plane + 1]) - planes[plane + 1][3];
if (u < 0.0f || u > 1.0f) if (u < 0.0f || u > 1.0f)
{ {
return false; return false;
} }
float v = RcVec3f.Dot(s, new RcVec3f(planes[plane + 2])) - planes[plane + 2][3]; float v = RcVecUtils.Dot(s, planes[plane + 2]) - planes[plane + 2][3];
if (v < 0.0f) if (v < 0.0f)
{ {
return false; return false;
@ -749,7 +749,7 @@ namespace DotRecast.Recast
return false; return false;
} }
y = s.Y; y = s[1];
return true; return true;
} }

View File

@ -1,7 +1,7 @@
/* /*
Copyright (c) 2009-2010 Mikko Mononen memon@inside.org Copyright (c) 2009-2010 Mikko Mononen memon@inside.org
recast4j copyright (c) 2015-2019 Piotr Piastucki piotr@jtilia.org recast4j copyright (c) 2015-2019 Piotr Piastucki piotr@jtilia.org
DotRecast Copyright (c) 2023-2024 Choi Ikpil ikpil@naver.com DotRecast Copyright (c) 2023 Choi Ikpil ikpil@naver.com
This software is provided 'as-is', without any express or implied This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages warranty. In no event will the authors be held liable for any damages
@ -23,223 +23,177 @@ using DotRecast.Core;
namespace DotRecast.Recast namespace DotRecast.Recast
{ {
using static RcConstants;
using static RcRecast; using static RcCommons;
public static class RcFilters public static class RcFilters
{ {
/// Marks non-walkable spans as walkable if their maximum is within @p walkableClimb of the span below them. /// @par
/// ///
/// 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. /// Allows the formation of walkable regions that will flow over low lying
/// This removes small obstacles and rasterization artifacts that the agent would be able to walk over /// objects such as curbs, and up structures such as stairways.
/// such as curbs. It also allows agents to move up terraced structures like stairs.
/// ///
/// Obstacle spans are marked walkable if: <tt>obstacleSpan.smax - walkableSpan.smax < walkableClimb</tt> /// Two neighboring spans are walkable if: <tt>RcAbs(currentSpan.smax - neighborSpan.smax) < walkableClimb</tt>
/// ///
/// @warning Will override the effect of #rcFilterLedgeSpans. If both filters are used, call #rcFilterLedgeSpans only after applying this filter. /// @warning Will override the effect of #rcFilterLedgeSpans. So if both filters are used, call
/// #rcFilterLedgeSpans after calling this filter.
/// ///
/// @see rcHeightfield, rcConfig /// @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 = context.ScopedTimer(RcTimerLabel.RC_TIMER_FILTER_LOW_OBSTACLES); using var timer = ctx.ScopedTimer(RcTimerLabel.RC_TIMER_FILTER_LOW_OBSTACLES);
int xSize = heightfield.width; int w = solid.width;
int zSize = heightfield.height; int h = solid.height;
for (int z = 0; z < zSize; ++z) for (int y = 0; y < h; ++y)
{ {
for (int x = 0; x < xSize; ++x) for (int x = 0; x < w; ++x)
{ {
RcSpan previousSpan = null; RcSpan ps = null;
bool previousWasWalkable = false; bool previousWalkable = false;
int previousAreaID = RC_NULL_AREA; int previousArea = RC_NULL_AREA;
// For each span in the column... for (RcSpan s = solid.spans[x + y * w]; s != null; ps = s, s = s.next)
for (RcSpan span = heightfield.spans[x + z * xSize]; span != null; previousSpan = span, span = span.next)
{ {
bool walkable = span.area != RC_NULL_AREA; bool walkable = s.area != RC_NULL_AREA;
// If current span is not walkable, but there is walkable span just below it and the height difference // If current span is not walkable, but there is walkable
// is small enough for the agent to walk over, mark the current span as walkable too. // span just below it, mark the span above it walkable too.
if (!walkable && previousWasWalkable && span.smax - previousSpan.smax <= walkableClimb) if (!walkable && previousWalkable)
{ {
span.area = previousAreaID; if (MathF.Abs(s.smax - ps.smax) <= walkableClimb)
s.area = previousArea;
} }
// Copy the original walkable value regardless of whether we changed it. // Copy walkable flag so that it cannot propagate
// This prevents multiple consecutive non-walkable spans from being erroneously marked as walkable. // past multiple non-walkable objects.
previousWasWalkable = walkable; previousWalkable = walkable;
previousAreaID = span.area; previousArea = s.area;
} }
} }
} }
} }
/// Marks spans that are ledges as not-walkable. /// @par
/// ///
/// A ledge is a span with one or more neighbors whose maximum is further away than @p walkableClimb /// A ledge is a span with one or more neighbors whose maximum is further away than @p walkableClimb
/// from the current span's maximum. /// from the current span's maximum.
/// This method removes the impact of the overestimation of conservative voxelization /// 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. /// 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 /// @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 = context.ScopedTimer(RcTimerLabel.RC_TIMER_FILTER_BORDER); using var timer = ctx.ScopedTimer(RcTimerLabel.RC_TIMER_FILTER_BORDER);
int xSize = heightfield.width; int w = solid.width;
int zSize = heightfield.height; int h = solid.height;
// Mark spans that are adjacent to a ledge as unwalkable.. // Mark border spans.
for (int z = 0; z < zSize; ++z) for (int y = 0; y < h; ++y)
{ {
for (int x = 0; x < xSize; ++x) for (int x = 0; x < w; ++x)
{ {
for (RcSpan span = heightfield.spans[x + z * xSize]; span != null; span = span.next) for (RcSpan s = solid.spans[x + y * w]; s != null; s = s.next)
{
// Skip non-walkable spans.
if (span.area == RC_NULL_AREA)
{ {
// Skip non walkable spans.
if (s.area == RC_NULL_AREA)
continue; continue;
}
int floor = (span.smax); int bot = (s.smax);
int ceiling = span.next != null ? span.next.smin : RC_SPAN_MAX_HEIGHT; int top = s.next != null ? s.next.smin : SPAN_MAX_HEIGHT;
// The difference between this walkable area and the lowest neighbor walkable area. // Find neighbours minimum height.
// This is the difference between the current span and all neighbor spans that have int minh = SPAN_MAX_HEIGHT;
// 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. // Min and max height of accessible neighbours.
int lowestTraversableNeighborFloor = span.smax; int asmin = s.smax;
int highestTraversableNeighborFloor = span.smax; int asmax = s.smax;
for (int direction = 0; direction < 4; ++direction) for (int dir = 0; dir < 4; ++dir)
{ {
int neighborX = x + GetDirOffsetX(direction); int dx = x + GetDirOffsetX(dir);
int neighborZ = z + GetDirOffsetY(direction); int dy = y + GetDirOffsetY(dir);
// Skip neighbours which are out of bounds. // Skip neighbours which are out of bounds.
if (neighborX < 0 || neighborZ < 0 || neighborX >= xSize || neighborZ >= zSize) if (dx < 0 || dy < 0 || dx >= w || dy >= h)
{ {
lowestNeighborFloorDifference = (-walkableClimb - 1); minh = Math.Min(minh, -walkableClimb - bot);
break;
}
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(ceiling, neighborCeiling) - floor >= walkableHeight)
{
lowestNeighborFloorDifference = (-walkableClimb - 1);
break;
}
// 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; continue;
} }
int neighborFloorDifference = neighborFloor - floor; // From minus infinity to the first span.
lowestNeighborFloorDifference = Math.Min(lowestNeighborFloorDifference, neighborFloorDifference); RcSpan ns = solid.spans[dx + dy * w];
int nbot = -walkableClimb;
int ntop = ns != null ? ns.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);
// Find min/max accessible neighbor height. // Rest of the spans.
// Only consider neighbors that are at most walkableClimb away. for (ns = solid.spans[dx + dy * w]; ns != null; ns = ns.next)
if (MathF.Abs(neighborFloorDifference) <= walkableClimb)
{ {
// There is space to move to the neighbor cell and the slope isn't too much. nbot = ns.smax;
lowestTraversableNeighborFloor = Math.Min(lowestTraversableNeighborFloor, neighborFloor); ntop = ns.next != null ? ns.next.smin : SPAN_MAX_HEIGHT;
highestTraversableNeighborFloor = Math.Max(highestTraversableNeighborFloor, neighborFloor); // 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);
// Find min/max accessible neighbour height.
if (MathF.Abs(nbot - bot) <= walkableClimb)
{
if (nbot < asmin)
asmin = nbot;
if (nbot > asmax)
asmax = nbot;
} }
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 magnitude of the drop to any neighbour span is greater than the walkableClimb distance. // The current span is close to a ledge if the drop to any
// 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. // neighbour span is less than the walkableClimb.
// (If this is the case, then biggestNeighborStepDown will be negative, so compare against the negative walkableClimb as a means of checking if (minh < -walkableClimb)
// the magnitude of the delta) s.area = RC_NULL_AREA;
if (lowestNeighborFloorDifference < -walkableClimb)
// If the difference between all neighbours is too large,
// we are at steep slope, mark the span as ledge.
if ((asmax - asmin) > walkableClimb)
{ {
span.area = RC_NULL_AREA; s.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;
} }
} }
} }
} }
} }
/// Marks walkable spans as not walkable if the clearance above the span is less than the specified walkableHeight. /// @par
/// ///
/// For this filter, the clearance above the span is the distance from the span's /// For this filter, the clearance above the span is the distance from the span's
/// maximum to the minimum of the next higher span in the same column. /// maximum to the next higher span's minimum. (Same grid 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 /// @see rcHeightfield, rcConfig
/// @ingroup recast public static void FilterWalkableLowHeightSpans(RcTelemetry ctx, int walkableHeight, RcHeightfield solid)
///
/// @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 = context.ScopedTimer(RcTimerLabel.RC_TIMER_FILTER_WALKABLE); using var timer = ctx.ScopedTimer(RcTimerLabel.RC_TIMER_FILTER_WALKABLE);
int xSize = heightfield.width; int w = solid.width;
int zSize = heightfield.height; int h = solid.height;
// Remove walkable flag from spans which do not have enough // Remove walkable flag from spans which do not have enough
// space above them for the agent to stand there. // space above them for the agent to stand there.
for (int z = 0; z < zSize; ++z) for (int y = 0; y < h; ++y)
{ {
for (int x = 0; x < xSize; ++x) for (int x = 0; x < w; ++x)
{ {
for (RcSpan span = heightfield.spans[x + z * xSize]; span != null; span = span.next) for (RcSpan s = solid.spans[x + y * w]; s != null; s = s.next)
{ {
int floor = (span.smax); int bot = (s.smax);
int ceiling = span.next != null ? span.next.smin : RC_SPAN_MAX_HEIGHT; int top = s.next != null ? s.next.smin : SPAN_MAX_HEIGHT;
if ((ceiling - floor) < walkableHeight) if ((top - bot) < walkableHeight)
{ s.area = RC_NULL_AREA;
span.area = RC_NULL_AREA;
}
} }
} }
} }

View File

@ -1,4 +1,4 @@
namespace DotRecast.Recast namespace DotRecast.Recast
{ {
public class RcHeightPatch public class RcHeightPatch
{ {

View File

@ -1,7 +1,7 @@
/* /*
Copyright (c) 2009-2010 Mikko Mononen memon@inside.org Copyright (c) 2009-2010 Mikko Mononen memon@inside.org
recast4j copyright (c) 2015-2019 Piotr Piastucki piotr@jtilia.org recast4j copyright (c) 2015-2019 Piotr Piastucki piotr@jtilia.org
DotRecast Copyright (c) 2023-2024 Choi Ikpil ikpil@naver.com DotRecast Copyright (c) 2023 Choi Ikpil ikpil@naver.com
This software is provided 'as-is', without any express or implied This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages warranty. In no event will the authors be held liable for any damages
@ -22,21 +22,29 @@ using DotRecast.Core.Numerics;
namespace DotRecast.Recast namespace DotRecast.Recast
{ {
/// A dynamic heightfield representing obstructed space. /** Represents a heightfield layer within a layer set. */
/// @ingroup recast
public class RcHeightfield public class RcHeightfield
{ {
public readonly int width; //< The width of the heightfield. (Along the x-axis in cell units.) /** The width of the heightfield. (Along the x-axis in cell units.) */
public readonly int height; //< The height of the heightfield. (Along the z-axis in cell units.) public readonly int width;
public readonly RcVec3f bmin; //< The minimum bounds in world space. [(x, y, z)]
public RcVec3f bmax; //< The maximum bounds in world space. [(x, y, z)]
public readonly float cs; //< The size of each cell. (On the xz-plane.)
public readonly float ch; //< The height of each cell. (The minimum increment along the y-axis.)
public readonly RcSpan[] spans; //< Heightfield of spans (width*height).
// memory pool for rcSpan instances. /** The height of the heightfield. (Along the z-axis in cell units.) */
public RcSpanPool pools; //< Linked list of span pools. public readonly int height;
public RcSpan freelist; //< The next free span.
/** The minimum bounds in world space. [(x, y, z)] */
public readonly RcVec3f bmin;
/** The maximum bounds in world space. [(x, y, z)] */
public RcVec3f bmax;
/** The size of each cell. (On the xz-plane.) */
public readonly float cs;
/** The height of each cell. (The minimum increment along the y-axis.) */
public readonly float ch;
/** Heightfield of spans (width*height). */
public readonly RcSpan[] spans;
/** Border size in cell units */ /** Border size in cell units */
public readonly int borderSize; public readonly int borderSize;

View File

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

@ -1,7 +1,7 @@
/* /*
Copyright (c) 2009-2010 Mikko Mononen memon@inside.org Copyright (c) 2009-2010 Mikko Mononen memon@inside.org
recast4j copyright (c) 2015-2019 Piotr Piastucki piotr@jtilia.org recast4j copyright (c) 2015-2019 Piotr Piastucki piotr@jtilia.org
DotRecast Copyright (c) 2023-2024 Choi Ikpil ikpil@naver.com DotRecast Copyright (c) 2023 Choi Ikpil ikpil@naver.com
This software is provided 'as-is', without any express or implied This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages warranty. In no event will the authors be held liable for any damages
@ -18,6 +18,7 @@ freely, subject to the following restrictions:
3. This notice may not be removed or altered from any source distribution. 3. This notice may not be removed or altered from any source distribution.
*/ */
using DotRecast.Core.Numerics;
namespace DotRecast.Recast namespace DotRecast.Recast
{ {

View File

@ -1,23 +1,23 @@
using System.Collections.Generic; using System.Collections.Generic;
namespace DotRecast.Recast namespace DotRecast.Recast
{ {
public class RcLayerRegion public class RcLayerRegion
{ {
public readonly int index; public int id;
public int layerId;
public bool @base;
public int ymin, ymax;
public List<int> layers; public List<int> layers;
public List<int> neis; public List<int> neis;
public int ymin, ymax;
public byte layerId; // Layer ID
public bool @base; // Flag indicating if the region is the base of merged regions.
public RcLayerRegion(int i) public RcLayerRegion(int i)
{ {
index = i; id = i;
layers = new List<int>();
neis = new List<int>();
ymin = 0xFFFF; ymin = 0xFFFF;
layerId = 0xff; layerId = 0xff;
layers = new List<int>();
neis = new List<int>();
} }
}; };
} }

View File

@ -1,9 +0,0 @@
namespace DotRecast.Recast
{
public class RcLayerSweepSpan
{
public int ns; // number samples
public byte id; // region id
public byte nei; // neighbour id
};
}

View File

@ -1,7 +1,7 @@
/* /*
Copyright (c) 2009-2010 Mikko Mononen memon@inside.org Copyright (c) 2009-2010 Mikko Mononen memon@inside.org
recast4j copyright (c) 2015-2019 Piotr Piastucki piotr@jtilia.org recast4j copyright (c) 2015-2019 Piotr Piastucki piotr@jtilia.org
DotRecast Copyright (c) 2023-2024 Choi Ikpil ikpil@naver.com DotRecast Copyright (c) 2023 Choi Ikpil ikpil@naver.com
This software is provided 'as-is', without any express or implied This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages warranty. In no event will the authors be held liable for any damages
@ -25,7 +25,8 @@ using DotRecast.Core.Numerics;
namespace DotRecast.Recast namespace DotRecast.Recast
{ {
using static RcRecast; using static RcConstants;
using static RcCommons;
public static class RcLayers public static class RcLayers
{ {
@ -47,72 +48,45 @@ namespace DotRecast.Recast
return (amin > bmax || amax < bmin) ? false : true; return (amin > bmax || amax < bmin) ? false : true;
} }
/// @par public static RcHeightfieldLayerSet BuildHeightfieldLayers(RcTelemetry ctx, RcCompactHeightfield chf, int walkableHeight)
///
/// 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); using var timer = ctx.ScopedTimer(RcTimerLabel.RC_TIMER_BUILD_LAYERS);
int w = chf.width; int w = chf.width;
int h = chf.height; int h = chf.height;
int borderSize = chf.borderSize;
Span<byte> srcReg = stackalloc byte[chf.spanCount]; int[] srcReg = new int[chf.spanCount];
srcReg.Fill(0xFF); Array.Fill(srcReg, 0xFF);
int nsweeps = chf.width; // Math.Max(chf.width, chf.height);
int nsweeps = chf.width; RcSweepSpan[] sweeps = new RcSweepSpan[nsweeps];
RcLayerSweepSpan[] sweeps = new RcLayerSweepSpan[nsweeps];
for (int i = 0; i < sweeps.Length; i++)
{
sweeps[i] = new RcLayerSweepSpan();
}
// Partition walkable area into monotone regions. // Partition walkable area into monotone regions.
Span<int> prevCount = stackalloc int[256]; int[] prevCount = new int[256];
byte regId = 0; int regId = 0;
// Sweep one line at a time. // Sweep one line at a time.
for (int y = borderSize; y < h - borderSize; ++y) for (int y = borderSize; y < h - borderSize; ++y)
{ {
// Collect spans from this row. // Collect spans from this row.
prevCount.Fill(0); Array.Fill(prevCount, 0, 0, (regId) - (0));
byte sweepId = 0; int sweepId = 0;
for (int x = borderSize; x < w - borderSize; ++x) for (int x = borderSize; x < w - borderSize; ++x)
{ {
ref RcCompactCell c = ref chf.cells[x + y * w]; RcCompactCell c = chf.cells[x + y * w];
for (int i = c.index, ni = c.index + c.count; i < ni; ++i) for (int i = c.index, ni = c.index + c.count; i < ni; ++i)
{ {
ref RcCompactSpan s = ref chf.spans[i]; RcCompactSpan s = chf.spans[i];
if (chf.areas[i] == RC_NULL_AREA) if (chf.areas[i] == RC_NULL_AREA)
continue; continue;
int sid = 0xFF;
byte sid = 0xFF;
// -x // -x
if (GetCon(ref s, 0) != RC_NOT_CONNECTED)
if (GetCon(s, 0) != RC_NOT_CONNECTED)
{ {
int ax = x + GetDirOffsetX(0); int ax = x + GetDirOffsetX(0);
int ay = y + GetDirOffsetY(0); int ay = y + GetDirOffsetY(0);
int ai = chf.cells[ax + ay * w].index + GetCon(ref s, 0); int ai = chf.cells[ax + ay * w].index + GetCon(s, 0);
if (chf.areas[ai] != RC_NULL_AREA && srcReg[ai] != 0xff) if (chf.areas[ai] != RC_NULL_AREA && srcReg[ai] != 0xff)
sid = srcReg[ai]; sid = srcReg[ai];
} }
@ -125,15 +99,16 @@ namespace DotRecast.Recast
} }
// -y // -y
if (GetCon(ref s, 3) != RC_NOT_CONNECTED) if (GetCon(s, 3) != RC_NOT_CONNECTED)
{ {
int ax = x + GetDirOffsetX(3); int ax = x + GetDirOffsetX(3);
int ay = y + GetDirOffsetY(3); int ay = y + GetDirOffsetY(3);
int ai = chf.cells[ax + ay * w].index + GetCon(ref s, 3); int ai = chf.cells[ax + ay * w].index + GetCon(s, 3);
byte nr = srcReg[ai]; int nr = srcReg[ai];
if (nr != 0xff) if (nr != 0xff)
{ {
// Set neighbour when first valid neighbour is encoutered. // Set neighbour when first valid neighbour is
// encoutered.
if (sweeps[sid].ns == 0) if (sweeps[sid].ns == 0)
sweeps[sid].nei = nr; sweeps[sid].nei = nr;
@ -145,7 +120,8 @@ namespace DotRecast.Recast
} }
else else
{ {
// This is hit if there is nore than one neighbour. // This is hit if there is nore than one
// neighbour.
// Invalidate the neighbour. // Invalidate the neighbour.
sweeps[sid].nei = 0xff; sweeps[sid].nei = 0xff;
} }
@ -159,8 +135,10 @@ namespace DotRecast.Recast
// Create unique ID. // Create unique ID.
for (int i = 0; i < sweepId; ++i) for (int i = 0; i < sweepId; ++i)
{ {
// If the neighbour is set and there is only one continuous connection to it, // If the neighbour is set and there is only one continuous
// the sweep will be merged with the previous one, else new region is created. // 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) if (sweeps[i].nei != 0xff && prevCount[sweeps[i].nei] == sweeps[i].ns)
{ {
sweeps[i].id = sweeps[i].nei; sweeps[i].id = sweeps[i].nei;
@ -170,7 +148,6 @@ namespace DotRecast.Recast
if (regId == 255) if (regId == 255)
{ {
throw new Exception("rcBuildHeightfieldLayers: Region ID overflow."); throw new Exception("rcBuildHeightfieldLayers: Region ID overflow.");
return false;
} }
sweeps[i].id = regId++; sweeps[i].id = regId++;
@ -180,7 +157,7 @@ namespace DotRecast.Recast
// Remap local sweep ids to region ids. // Remap local sweep ids to region ids.
for (int x = borderSize; x < w - borderSize; ++x) for (int x = borderSize; x < w - borderSize; ++x)
{ {
ref RcCompactCell c = ref chf.cells[x + y * w]; RcCompactCell c = chf.cells[x + y * w];
for (int i = c.index, ni = c.index + c.count; i < ni; ++i) for (int i = c.index, ni = c.index + c.count; i < ni; ++i)
{ {
if (srcReg[i] != 0xff) if (srcReg[i] != 0xff)
@ -189,7 +166,6 @@ namespace DotRecast.Recast
} }
} }
// Allocate and init layer regions.
int nregs = regId; int nregs = regId;
RcLayerRegion[] regs = new RcLayerRegion[nregs]; RcLayerRegion[] regs = new RcLayerRegion[nregs];
@ -205,13 +181,13 @@ namespace DotRecast.Recast
{ {
for (int x = 0; x < w; ++x) for (int x = 0; x < w; ++x)
{ {
ref RcCompactCell c = ref chf.cells[x + y * w]; RcCompactCell c = chf.cells[x + y * w];
lregs.Clear(); lregs.Clear();
for (int i = c.index, ni = c.index + c.count; i < ni; ++i) for (int i = c.index, ni = c.index + c.count; i < ni; ++i)
{ {
ref RcCompactSpan s = ref chf.spans[i]; RcCompactSpan s = chf.spans[i];
int ri = srcReg[i]; int ri = srcReg[i];
if (ri == 0xff) if (ri == 0xff)
continue; continue;
@ -225,22 +201,17 @@ namespace DotRecast.Recast
// Update neighbours // Update neighbours
for (int dir = 0; dir < 4; ++dir) for (int dir = 0; dir < 4; ++dir)
{ {
if (GetCon(ref s, dir) != RC_NOT_CONNECTED) if (GetCon(s, dir) != RC_NOT_CONNECTED)
{ {
int ax = x + GetDirOffsetX(dir); int ax = x + GetDirOffsetX(dir);
int ay = y + GetDirOffsetY(dir); int ay = y + GetDirOffsetY(dir);
int ai = chf.cells[ax + ay * w].index + GetCon(ref s, dir); int ai = chf.cells[ax + ay * w].index + GetCon(s, dir);
int rai = srcReg[ai]; int rai = srcReg[ai];
if (rai != 0xff && rai != ri) 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); AddUnique(regs[ri].neis, rai);
} }
} }
} }
}
// Update overlapping regions. // Update overlapping regions.
for (int i = 0; i < lregs.Count - 1; ++i) for (int i = 0; i < lregs.Count - 1; ++i)
@ -260,11 +231,9 @@ namespace DotRecast.Recast
} }
// Create 2D layers from regions. // Create 2D layers from regions.
byte layerId = 0; int layerId = 0;
const int MAX_STACK = 64; List<int> stack = new List<int>();
Span<byte> stack = stackalloc byte[MAX_STACK];
int nstack = 0;
for (int i = 0; i < nregs; ++i) for (int i = 0; i < nregs; ++i)
{ {
@ -277,16 +246,14 @@ namespace DotRecast.Recast
root.layerId = layerId; root.layerId = layerId;
root.@base = true; root.@base = true;
nstack = 0; stack.Add(i);
stack[nstack++] = ((byte)i);
while (0 != nstack) while (stack.Count != 0)
{ {
// Pop front // Pop front
RcLayerRegion reg = regs[stack[0]]; int pop = stack[0]; // TODO : 여기에 stack 처럼 작동하게 했는데, 스택인지는 모르겠음
nstack--; stack.RemoveAt(0);
for (int j = 0; j < nstack; ++j) RcLayerRegion reg = regs[pop];
stack[j] = stack[j + 1];
foreach (int nei in reg.neis) foreach (int nei in reg.neis)
{ {
@ -294,35 +261,27 @@ namespace DotRecast.Recast
// Skip already visited. // Skip already visited.
if (regn.layerId != 0xff) if (regn.layerId != 0xff)
continue; continue;
// Skip if the neighbour is overlapping root region. // Skip if the neighbour is overlapping root region.
if (Contains(root.layers, nei)) if (Contains(root.layers, nei))
continue; continue;
// Skip if the height range would become too large. // Skip if the height range would become too large.
int ymin = Math.Min(root.ymin, regn.ymin); int ymin = Math.Min(root.ymin, regn.ymin);
int ymax = Math.Max(root.ymax, regn.ymax); int ymax = Math.Max(root.ymax, regn.ymax);
if ((ymax - ymin) >= 255) if ((ymax - ymin) >= 255)
continue; continue;
if (nstack < MAX_STACK)
{
// Deepen // Deepen
stack[nstack++] = (byte)nei; stack.Add(nei);
// Mark layer id // Mark layer id
regn.layerId = layerId; regn.layerId = layerId;
// Merge current layers to root. // Merge current layers to root.
foreach (int layer in regn.layers) foreach (int layer in regn.layers)
{
AddUnique(root.layers, layer); AddUnique(root.layers, layer);
}
root.ymin = Math.Min(root.ymin, regn.ymin); root.ymin = Math.Min(root.ymin, regn.ymin);
root.ymax = Math.Max(root.ymax, regn.ymax); root.ymax = Math.Max(root.ymax, regn.ymax);
} }
} }
}
layerId++; layerId++;
} }
@ -336,9 +295,9 @@ namespace DotRecast.Recast
if (!ri.@base) if (!ri.@base)
continue; continue;
byte newId = ri.layerId; int newId = ri.layerId;
for (;;) while (true)
{ {
int oldId = 0xff; int oldId = 0xff;
@ -359,9 +318,11 @@ namespace DotRecast.Recast
if ((ymax - ymin) >= 255) if ((ymax - ymin) >= 255)
continue; 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; 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) for (int k = 0; k < nregs; ++k)
{ {
if (regs[k].layerId != rj.layerId) if (regs[k].layerId != rj.layerId)
@ -399,10 +360,7 @@ namespace DotRecast.Recast
rj.layerId = newId; rj.layerId = newId;
// Add overlaid layers from 'rj' to 'ri'. // Add overlaid layers from 'rj' to 'ri'.
foreach (int layer in rj.layers) foreach (int layer in rj.layers)
{
AddUnique(ri.layers, layer); AddUnique(ri.layers, layer);
}
// Update height bounds. // Update height bounds.
ri.ymin = Math.Min(ri.ymin, rj.ymin); ri.ymin = Math.Min(ri.ymin, rj.ymin);
ri.ymax = Math.Max(ri.ymax, rj.ymax); ri.ymax = Math.Max(ri.ymax, rj.ymax);
@ -412,7 +370,7 @@ namespace DotRecast.Recast
} }
// Compact layerIds // Compact layerIds
Span<byte> remap = stackalloc byte[256]; int[] remap = new int[256];
// Find number of unique layers. // Find number of unique layers.
layerId = 0; layerId = 0;
@ -428,14 +386,13 @@ namespace DotRecast.Recast
// Remap ids. // Remap ids.
for (int i = 0; i < nregs; ++i) for (int i = 0; i < nregs; ++i)
{
regs[i].layerId = remap[regs[i].layerId]; regs[i].layerId = remap[regs[i].layerId];
}
// No layers, return empty. // No layers, return empty.
if (layerId == 0) if (layerId == 0)
{ {
return true; // ctx.Stop(RC_TIMER_BUILD_LAYERS);
return null;
} }
// Create layers. // Create layers.
@ -452,7 +409,7 @@ namespace DotRecast.Recast
bmax.X -= borderSize * chf.cs; bmax.X -= borderSize * chf.cs;
bmax.Z -= borderSize * chf.cs; bmax.Z -= borderSize * chf.cs;
lset = new RcHeightfieldLayerSet(); RcHeightfieldLayerSet lset = new RcHeightfieldLayerSet();
lset.layers = new RcHeightfieldLayer[layerId]; lset.layers = new RcHeightfieldLayer[layerId];
for (int i = 0; i < lset.layers.Length; i++) for (int i = 0; i < lset.layers.Length; i++)
{ {
@ -510,10 +467,10 @@ namespace DotRecast.Recast
{ {
int cx = borderSize + x; int cx = borderSize + x;
int cy = borderSize + y; int cy = borderSize + y;
ref RcCompactCell c = ref chf.cells[cx + cy * w]; RcCompactCell c = chf.cells[cx + cy * w];
for (int j = c.index, nj = c.index + c.count; j < nj; ++j) for (int j = c.index, nj = c.index + c.count; j < nj; ++j)
{ {
ref RcCompactSpan s = ref chf.spans[j]; RcCompactSpan s = chf.spans[j];
// Skip unassigned regions. // Skip unassigned regions.
if (srcReg[j] == 0xff) if (srcReg[j] == 0xff)
continue; continue;
@ -538,18 +495,19 @@ namespace DotRecast.Recast
char con = (char)0; char con = (char)0;
for (int dir = 0; dir < 4; ++dir) for (int dir = 0; dir < 4; ++dir)
{ {
if (GetCon(ref s, dir) != RC_NOT_CONNECTED) if (GetCon(s, dir) != RC_NOT_CONNECTED)
{ {
int ax = cx + GetDirOffsetX(dir); int ax = cx + GetDirOffsetX(dir);
int ay = cy + GetDirOffsetY(dir); int ay = cy + GetDirOffsetY(dir);
int ai = chf.cells[ax + ay * w].index + GetCon(ref s, dir); int ai = chf.cells[ax + ay * w].index + GetCon(s, dir);
int alid = srcReg[ai] != 0xff ? regs[srcReg[ai]].layerId : 0xff; int alid = srcReg[ai] != 0xff ? regs[srcReg[ai]].layerId : 0xff;
// Portal mask // Portal mask
if (chf.areas[ai] != RC_NULL_AREA && lid != alid) if (chf.areas[ai] != RC_NULL_AREA && lid != alid)
{ {
portal |= (char)(1 << dir); portal |= (char)(1 << dir);
// Update height so that it matches on both sides of the portal. // Update height so that it matches on both
ref RcCompactSpan @as = ref chf.spans[ai]; // sides of the portal.
RcCompactSpan @as = chf.spans[ai];
if (@as.y > hmin) if (@as.y > hmin)
layer.heights[idx] = Math.Max(layer.heights[idx], (char)(@as.y - hmin)); layer.heights[idx] = Math.Max(layer.heights[idx], (char)(@as.y - hmin));
} }
@ -576,7 +534,8 @@ namespace DotRecast.Recast
layer.miny = layer.maxy = 0; layer.miny = layer.maxy = 0;
} }
return true; // ctx->StopTimer(RC_TIMER_BUILD_LAYERS);
return lset;
} }
} }
} }

View File

@ -1,16 +0,0 @@
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

@ -1,7 +1,7 @@
/* /*
Copyright (c) 2009-2010 Mikko Mononen memon@inside.org Copyright (c) 2009-2010 Mikko Mononen memon@inside.org
recast4j copyright (c) 2015-2019 Piotr Piastucki piotr@jtilia.org recast4j copyright (c) 2015-2019 Piotr Piastucki piotr@jtilia.org
DotRecast Copyright (c) 2023-2024 Choi Ikpil ikpil@naver.com DotRecast Copyright (c) 2023 Choi Ikpil ikpil@naver.com
This software is provided 'as-is', without any express or implied This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages warranty. In no event will the authors be held liable for any damages
@ -22,57 +22,178 @@ using System;
using System.Collections.Generic; using System.Collections.Generic;
using DotRecast.Core; using DotRecast.Core;
using DotRecast.Core.Numerics; using DotRecast.Core.Numerics;
using static DotRecast.Recast.RcConstants;
namespace DotRecast.Recast namespace DotRecast.Recast
{ {
using static RcRecast; using static RcCommons;
using static RcVec;
using static EdgeValues;
public static class RcMeshDetails public static class RcMeshDetails
{ {
public const int RC_UNSET_HEIGHT = RC_SPAN_MAX_HEIGHT; public const int MAX_VERTS = 127;
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 EV_UNDEF = -1;
public const int EV_HULL = -2;
public static bool CircumCircle(RcVec3f p1, RcVec3f p2, RcVec3f p3, ref RcVec3f c, out float r) private static float Vdot2(float[] a, float[] b)
{
return a[0] * b[0] + a[2] * b[2];
}
private static float Vdot2(RcVec3f a, RcVec3f b)
{
return a.X * b.X + a.Z * b.Z;
}
private static float VdistSq2(float[] verts, int p, int q)
{
float dx = verts[q + 0] - verts[p + 0];
float dy = verts[q + 2] - verts[p + 2];
return dx * dx + dy * dy;
}
private static float Vdist2(float[] verts, int p, int q)
{
return MathF.Sqrt(VdistSq2(verts, p, q));
}
private static float VdistSq2(float[] p, float[] q)
{
float dx = q[0] - p[0];
float dy = q[2] - p[2];
return dx * dx + dy * dy;
}
private static float VdistSq2(float[] p, RcVec3f q)
{
float dx = q.X - p[0];
float dy = q.Z - p[2];
return dx * dx + dy * dy;
}
private static float VdistSq2(RcVec3f p, RcVec3f q)
{
float dx = q.X - p.X;
float dy = q.Z - p.Z;
return dx * dx + dy * dy;
}
private static float Vdist2(float[] p, float[] q)
{
return MathF.Sqrt(VdistSq2(p, q));
}
private static float Vdist2(RcVec3f p, RcVec3f q)
{
return MathF.Sqrt(VdistSq2(p, q));
}
private static float Vdist2(float[] p, RcVec3f q)
{
return MathF.Sqrt(VdistSq2(p, q));
}
private static float VdistSq2(float[] p, float[] verts, int q)
{
float dx = verts[q + 0] - p[0];
float dy = verts[q + 2] - p[2];
return dx * dx + dy * dy;
}
private static float VdistSq2(RcVec3f p, float[] verts, int q)
{
float dx = verts[q + 0] - p.X;
float dy = verts[q + 2] - p.Z;
return dx * dx + dy * dy;
}
private static float Vdist2(float[] p, float[] verts, int q)
{
return MathF.Sqrt(VdistSq2(p, verts, q));
}
private static float Vdist2(RcVec3f p, float[] verts, int q)
{
return MathF.Sqrt(VdistSq2(p, verts, q));
}
private static float Vcross2(float[] verts, int p1, int p2, int p3)
{
float u1 = verts[p2 + 0] - verts[p1 + 0];
float v1 = verts[p2 + 2] - verts[p1 + 2];
float u2 = verts[p3 + 0] - verts[p1 + 0];
float v2 = verts[p3 + 2] - verts[p1 + 2];
return u1 * v2 - v1 * u2;
}
private static float Vcross2(float[] p1, float[] p2, float[] p3)
{
float u1 = p2[0] - p1[0];
float v1 = p2[2] - p1[2];
float u2 = p3[0] - p1[0];
float v2 = p3[2] - p1[2];
return u1 * v2 - v1 * u2;
}
private static float Vcross2(RcVec3f p1, RcVec3f p2, RcVec3f p3)
{
float u1 = p2.X - p1.X;
float v1 = p2.Z - p1.Z;
float u2 = p3.X - p1.X;
float v2 = p3.Z - p1.Z;
return u1 * v2 - v1 * u2;
}
private static bool CircumCircle(float[] verts, int p1, int p2, int p3, ref RcVec3f c, RcAtomicFloat r)
{ {
const float EPS = 1e-6f; const float EPS = 1e-6f;
// Calculate the circle relative to p1, to avoid some precision issues. // Calculate the circle relative to p1, to avoid some precision issues.
var v1 = new RcVec3f(); var v1 = new RcVec3f();
var v2 = p2 - p1; var v2 = RcVecUtils.Subtract(verts, p2, p1);
var v3 = p3 - p1; var v3 = RcVecUtils.Subtract(verts, p3, p1);
float cp = Cross2(v1, v2, v3); float cp = Vcross2(v1, v2, v3);
if (MathF.Abs(cp) > EPS) if (MathF.Abs(cp) > EPS)
{ {
float v1Sq = Dot2(v1, v1); float v1Sq = Vdot2(v1, v1);
float v2Sq = Dot2(v2, v2); float v2Sq = Vdot2(v2, v2);
float v3Sq = Dot2(v3, v3); float v3Sq = Vdot2(v3, v3);
c.X = (v1Sq * (v2.Z - v3.Z) + v2Sq * (v3.Z - v1.Z) + v3Sq * (v1.Z - v2.Z)) / (2 * cp); c.X = (v1Sq * (v2.Z - v3.Z) + v2Sq * (v3.Z - v1.Z) + v3Sq * (v1.Z - v2.Z)) / (2 * cp);
c.Y = 0; c.Y = 0;
c.Z = (v1Sq * (v3.X - v2.X) + v2Sq * (v1.X - v3.X) + v3Sq * (v2.X - v1.X)) / (2 * cp); c.Z = (v1Sq * (v3.X - v2.X) + v2Sq * (v1.X - v3.X) + v3Sq * (v2.X - v1.X)) / (2 * cp);
r = Dist2(c, v1); r.Exchange(Vdist2(c, v1));
c = c + p1; c = RcVecUtils.Add(c, verts, p1);
return true; return true;
} }
c = p1; c = RcVecUtils.Create(verts, p1);
r = 0f; r.Exchange(0f);
return false; return false;
} }
public static float DistPtTri(RcVec3f p, RcVec3f a, RcVec3f b, RcVec3f c) private static float DistPtTri(RcVec3f p, float[] verts, int a, int b, int c)
{ {
var v0 = c - a; var v0 = RcVecUtils.Subtract(verts, c, a);
var v1 = b - a; var v1 = RcVecUtils.Subtract(verts, b, a);
var v2 = p - a; var v2 = RcVecUtils.Subtract(p, verts, a);
float dot00 = Dot2(v0, v0); float dot00 = Vdot2(v0, v0);
float dot01 = Dot2(v0, v1); float dot01 = Vdot2(v0, v1);
float dot02 = Dot2(v0, v2); float dot02 = Vdot2(v0, v2);
float dot11 = Dot2(v1, v1); float dot11 = Vdot2(v1, v1);
float dot12 = Dot2(v1, v2); float dot12 = Vdot2(v1, v2);
// Compute barycentric coordinates // Compute barycentric coordinates
float invDenom = 1.0f / (dot00 * dot11 - dot01 * dot01); float invDenom = 1.0f / (dot00 * dot11 - dot01 * dot01);
@ -83,14 +204,14 @@ namespace DotRecast.Recast
const float EPS = 1e-4f; const float EPS = 1e-4f;
if (u >= -EPS && v >= -EPS && (u + v) <= 1 + EPS) if (u >= -EPS && v >= -EPS && (u + v) <= 1 + EPS)
{ {
float y = a.Y + v0.Y * u + v1.Y * v; float y = verts[a + 1] + v0.Y * u + v1.Y * v;
return MathF.Abs(y - p.Y); return MathF.Abs(y - p.Y);
} }
return float.MaxValue; return float.MaxValue;
} }
public static float DistancePtSeg(float[] verts, int pt, int p, int q) private static float DistancePtSeg(float[] verts, int pt, int p, int q)
{ {
float pqx = verts[q + 0] - verts[p + 0]; float pqx = verts[q + 0] - verts[p + 0];
float pqy = verts[q + 1] - verts[p + 1]; float pqy = verts[q + 1] - verts[p + 1];
@ -121,7 +242,7 @@ namespace DotRecast.Recast
return dx * dx + dy * dy + dz * dz; return dx * dx + dy * dy + dz * dz;
} }
public static float DistancePtSeg2d(RcVec3f verts, float[] poly, int p, int q) private static float DistancePtSeg2d(RcVec3f verts, float[] poly, int p, int q)
{ {
float pqx = poly[q + 0] - poly[p + 0]; float pqx = poly[q + 0] - poly[p + 0];
float pqz = poly[q + 2] - poly[p + 2]; float pqz = poly[q + 2] - poly[p + 2];
@ -149,7 +270,7 @@ namespace DotRecast.Recast
return dx * dx + dz * dz; return dx * dx + dz * dz;
} }
public static float DistancePtSeg2d(float[] verts, int pt, float[] poly, int p, int q) private static float DistancePtSeg2d(float[] verts, int pt, float[] poly, int p, int q)
{ {
float pqx = poly[q + 0] - poly[p + 0]; float pqx = poly[q + 0] - poly[p + 0];
float pqz = poly[q + 2] - poly[p + 2]; float pqz = poly[q + 2] - poly[p + 2];
@ -177,15 +298,15 @@ namespace DotRecast.Recast
return dx * dx + dz * dz; return dx * dx + dz * dz;
} }
public static float DistToTriMesh(RcVec3f p, float[] verts, int nverts, List<int> tris, int ntris) private static float DistToTriMesh(RcVec3f p, float[] verts, int nverts, List<int> tris, int ntris)
{ {
float dmin = float.MaxValue; float dmin = float.MaxValue;
for (int i = 0; i < ntris; ++i) for (int i = 0; i < ntris; ++i)
{ {
RcVec3f va = RcVec.Create(verts, tris[i * 4 + 0] * 3); int va = tris[i * 4 + 0] * 3;
RcVec3f vb = RcVec.Create(verts, tris[i * 4 + 1] * 3); int vb = tris[i * 4 + 1] * 3;
RcVec3f vc = RcVec.Create(verts, tris[i * 4 + 2] * 3); int vc = tris[i * 4 + 2] * 3;
float d = DistPtTri(p, va, vb, vc); float d = DistPtTri(p, verts, va, vb, vc);
if (d < dmin) if (d < dmin)
{ {
dmin = d; dmin = d;
@ -200,7 +321,7 @@ namespace DotRecast.Recast
return dmin; return dmin;
} }
public static float DistToPoly(int nvert, float[] verts, RcVec3f p) private static float DistToPoly(int nvert, float[] verts, RcVec3f p)
{ {
float dmin = float.MaxValue; float dmin = float.MaxValue;
int i, j; int i, j;
@ -221,7 +342,7 @@ namespace DotRecast.Recast
return c ? -dmin : dmin; return c ? -dmin : dmin;
} }
public static int GetHeight(float fx, float fy, float fz, float cs, float ics, float ch, int radius, private static int GetHeight(float fx, float fy, float fz, float cs, float ics, float ch, int radius,
RcHeightPatch hp) RcHeightPatch hp)
{ {
int ix = (int)MathF.Floor(fx * ics + 0.01f); int ix = (int)MathF.Floor(fx * ics + 0.01f);
@ -304,7 +425,7 @@ namespace DotRecast.Recast
return h; return h;
} }
public static int FindEdge(List<int> edges, int s, int t) private static int FindEdge(List<int> edges, int s, int t)
{ {
for (int i = 0; i < edges.Count / 4; i++) for (int i = 0; i < edges.Count / 4; i++)
{ {
@ -318,7 +439,7 @@ namespace DotRecast.Recast
return EV_UNDEF; return EV_UNDEF;
} }
public static void AddEdge(RcContext ctx, List<int> edges, int maxEdges, int s, int t, int l, int r) private static void AddEdge(RcTelemetry ctx, List<int> edges, int maxEdges, int s, int t, int l, int r)
{ {
if (edges.Count / 4 >= maxEdges) if (edges.Count / 4 >= maxEdges)
{ {
@ -336,7 +457,7 @@ namespace DotRecast.Recast
} }
} }
public static void UpdateLeftFace(List<int> edges, int e, int s, int t, int f) private static void UpdateLeftFace(List<int> edges, int e, int s, int t, int f)
{ {
if (edges[e + 0] == s && edges[e + 1] == t && edges[e + 2] == EV_UNDEF) if (edges[e + 0] == s && edges[e + 1] == t && edges[e + 2] == EV_UNDEF)
{ {
@ -348,13 +469,13 @@ namespace DotRecast.Recast
} }
} }
public static bool OverlapSegSeg2d(float[] verts, int a, int b, int c, int d) private static bool OverlapSegSeg2d(float[] verts, int a, int b, int c, int d)
{ {
float a1 = Cross2(verts, a, b, d); float a1 = Vcross2(verts, a, b, d);
float a2 = Cross2(verts, a, b, c); float a2 = Vcross2(verts, a, b, c);
if (a1 * a2 < 0.0f) if (a1 * a2 < 0.0f)
{ {
float a3 = Cross2(verts, c, d, a); float a3 = Vcross2(verts, c, d, a);
float a4 = a3 + a2 - a1; float a4 = a3 + a2 - a1;
if (a3 * a4 < 0.0f) if (a3 * a4 < 0.0f)
{ {
@ -365,7 +486,7 @@ namespace DotRecast.Recast
return false; return false;
} }
public static bool OverlapEdges(float[] pts, List<int> edges, int s1, int t1) private static bool OverlapEdges(float[] pts, List<int> edges, int s1, int t1)
{ {
for (int i = 0; i < edges.Count / 4; ++i) for (int i = 0; i < edges.Count / 4; ++i)
{ {
@ -386,7 +507,7 @@ namespace DotRecast.Recast
return false; return false;
} }
public static int CompleteFacet(RcContext ctx, float[] pts, int npts, List<int> edges, int maxEdges, int nfaces, int e) static int CompleteFacet(RcTelemetry ctx, float[] pts, int npts, List<int> edges, int maxEdges, int nfaces, int e)
{ {
const float EPS = 1e-5f; const float EPS = 1e-5f;
@ -413,7 +534,7 @@ namespace DotRecast.Recast
// Find best point on left of edge. // Find best point on left of edge.
int pt = npts; int pt = npts;
RcVec3f c = new RcVec3f(); RcVec3f c = new RcVec3f();
float r = -1f; RcAtomicFloat r = new RcAtomicFloat(-1f);
for (int u = 0; u < npts; ++u) for (int u = 0; u < npts; ++u)
{ {
if (u == s || u == t) if (u == s || u == t)
@ -421,32 +542,28 @@ namespace DotRecast.Recast
continue; continue;
} }
RcVec3f vs = RcVec.Create(pts, s * 3); if (Vcross2(pts, s * 3, t * 3, u * 3) > EPS)
RcVec3f vt = RcVec.Create(pts, t * 3);
RcVec3f vu = RcVec.Create(pts, u * 3);
if (Cross2(vs, vt, vu) > EPS)
{ {
if (r < 0) if (r.Get() < 0)
{ {
// The circle is not updated yet, do it now. // The circle is not updated yet, do it now.
pt = u; pt = u;
CircumCircle(vs, vt, vu, ref c, out r); CircumCircle(pts, s * 3, t * 3, u * 3, ref c, r);
continue; continue;
} }
float d = Dist2(c, vu); float d = Vdist2(c, pts, u * 3);
float tol = 0.001f; float tol = 0.001f;
if (d > r * (1 + tol)) if (d > r.Get() * (1 + tol))
{ {
// Outside current circumcircle, skip. // Outside current circumcircle, skip.
continue; continue;
} }
else if (d < r * (1 - tol)) else if (d < r.Get() * (1 - tol))
{ {
// Inside safe circumcircle, update circle. // Inside safe circumcircle, update circle.
pt = u; pt = u;
CircumCircle(vs, vt, vu, ref c, out r); CircumCircle(pts, s * 3, t * 3, u * 3, ref c, r);
} }
else else
{ {
@ -464,7 +581,7 @@ namespace DotRecast.Recast
// Edge is valid. // Edge is valid.
pt = u; pt = u;
CircumCircle(vs, vt, vu, ref c, out r); CircumCircle(pts, s * 3, t * 3, u * 3, ref c, r);
} }
} }
} }
@ -507,7 +624,7 @@ namespace DotRecast.Recast
return nfaces; return nfaces;
} }
public static void DelaunayHull(RcContext ctx, int npts, float[] pts, int nhull, int[] hull, List<int> tris) private static void DelaunayHull(RcTelemetry ctx, int npts, float[] pts, int nhull, int[] hull, List<int> tris)
{ {
int nfaces = 0; int nfaces = 0;
int maxEdges = npts * 10; int maxEdges = npts * 10;
@ -603,7 +720,7 @@ namespace DotRecast.Recast
} }
// Calculate minimum extend of the polygon. // Calculate minimum extend of the polygon.
public static float PolyMinExtent(float[] verts, int nverts) private static float PolyMinExtent(float[] verts, int nverts)
{ {
float minDist = float.MaxValue; float minDist = float.MaxValue;
for (int i = 0; i < nverts; i++) for (int i = 0; i < nverts; i++)
@ -629,7 +746,7 @@ namespace DotRecast.Recast
return MathF.Sqrt(minDist); return MathF.Sqrt(minDist);
} }
public static void TriangulateHull(int nverts, float[] verts, int nhull, int[] hull, int nin, List<int> tris) private static void TriangulateHull(int nverts, float[] verts, int nhull, int[] hull, int nin, List<int> tris)
{ {
int start = 0, left = 1, right = nhull - 1; int start = 0, left = 1, right = nhull - 1;
@ -649,7 +766,7 @@ namespace DotRecast.Recast
int pv = hull[pi] * 3; int pv = hull[pi] * 3;
int cv = hull[i] * 3; int cv = hull[i] * 3;
int nv = hull[ni] * 3; int nv = hull[ni] * 3;
float d = Dist2(verts, pv, cv) + Dist2(verts, cv, nv) + Dist2(verts, nv, pv); float d = Vdist2(verts, pv, cv) + Vdist2(verts, cv, nv) + Vdist2(verts, nv, pv);
if (d < dmin) if (d < dmin)
{ {
start = i; start = i;
@ -679,8 +796,8 @@ namespace DotRecast.Recast
int nvleft = hull[nleft] * 3; int nvleft = hull[nleft] * 3;
int cvright = hull[right] * 3; int cvright = hull[right] * 3;
int nvright = hull[nright] * 3; int nvright = hull[nright] * 3;
float dleft = Dist2(verts, cvleft, nvleft) + Dist2(verts, nvleft, cvright); float dleft = Vdist2(verts, cvleft, nvleft) + Vdist2(verts, nvleft, cvright);
float dright = Dist2(verts, cvright, nvright) + Dist2(verts, cvleft, nvright); float dright = Vdist2(verts, cvright, nvright) + Vdist2(verts, cvleft, nvright);
if (dleft < dright) if (dleft < dright)
{ {
@ -701,37 +818,33 @@ namespace DotRecast.Recast
} }
} }
public static float GetJitterX(int i) private static float GetJitterX(int i)
{ {
return (((i * 0x8da6b343) & 0xffff) / 65535.0f * 2.0f) - 1.0f; return (((i * 0x8da6b343) & 0xffff) / 65535.0f * 2.0f) - 1.0f;
} }
public static float GetJitterY(int i) private static float GetJitterY(int i)
{ {
return (((i * 0xd8163841) & 0xffff) / 65535.0f * 2.0f) - 1.0f; return (((i * 0xd8163841) & 0xffff) / 65535.0f * 2.0f) - 1.0f;
} }
public static int BuildPolyDetail(RcContext ctx, float[] @in, int nin, static int BuildPolyDetail(RcTelemetry ctx, float[] @in, int nin, float sampleDist, float sampleMaxError,
float sampleDist, float sampleMaxError, int heightSearchRadius, RcCompactHeightfield chf, RcHeightPatch hp, float[] verts, List<int> tris)
int heightSearchRadius, RcCompactHeightfield chf,
RcHeightPatch hp, float[] verts,
ref List<int> tris, ref List<int> edges, ref List<int> samples)
{ {
const int MAX_VERTS = 127; List<int> samples = new List<int>(512);
const int MAX_TRIS = 255; // Max tris for delaunay is 2n-2-k (n=num verts, k=num hull verts).
const int MAX_VERTS_PER_EDGE = 32; int nverts = 0;
float[] edge = new float[(MAX_VERTS_PER_EDGE + 1) * 3]; float[] edge = new float[(MAX_VERTS_PER_EDGE + 1) * 3];
int[] hull = new int[MAX_VERTS]; int[] hull = new int[MAX_VERTS];
int nhull = 0; int nhull = 0;
int nverts = nin; nverts = nin;
for (int i = 0; i < nin; ++i) for (int i = 0; i < nin; ++i)
{ {
RcVec.Copy(verts, i * 3, @in, i * 3); RcVecUtils.Copy(verts, i * 3, @in, i * 3);
} }
edges.Clear();
tris.Clear(); tris.Clear();
float cs = chf.cs; float cs = chf.cs;
@ -756,7 +869,9 @@ namespace DotRecast.Recast
{ {
if (@in[vj + 2] > @in[vi + 2]) if (@in[vj + 2] > @in[vi + 2])
{ {
(vi, vj) = (vj, vi); int temp = vi;
vi = vj;
vj = temp;
swapped = true; swapped = true;
} }
} }
@ -764,7 +879,9 @@ namespace DotRecast.Recast
{ {
if (@in[vj + 0] > @in[vi + 0]) if (@in[vj + 0] > @in[vi + 0])
{ {
(vi, vj) = (vj, vi); int temp = vi;
vi = vj;
vj = temp;
swapped = true; swapped = true;
} }
} }
@ -844,7 +961,7 @@ namespace DotRecast.Recast
{ {
for (int k = nidx - 2; k > 0; --k) for (int k = nidx - 2; k > 0; --k)
{ {
RcVec.Copy(verts, nverts * 3, edge, idx[k] * 3); RcVecUtils.Copy(verts, nverts * 3, edge, idx[k] * 3);
hull[nhull++] = nverts; hull[nhull++] = nverts;
nverts++; nverts++;
} }
@ -853,7 +970,7 @@ namespace DotRecast.Recast
{ {
for (int k = 1; k < nidx - 1; ++k) for (int k = 1; k < nidx - 1; ++k)
{ {
RcVec.Copy(verts, nverts * 3, edge, idx[k] * 3); RcVecUtils.Copy(verts, nverts * 3, edge, idx[k] * 3);
hull[nhull++] = nverts; hull[nhull++] = nverts;
nverts++; nverts++;
} }
@ -865,7 +982,6 @@ namespace DotRecast.Recast
if (minExtent < sampleDist * 2) if (minExtent < sampleDist * 2)
{ {
TriangulateHull(nverts, verts, nhull, hull, nin, tris); TriangulateHull(nverts, verts, nhull, hull, nin, tris);
SetTriFlags(tris, nhull, hull);
return nverts; return nverts;
} }
@ -884,12 +1000,12 @@ namespace DotRecast.Recast
if (sampleDist > 0) if (sampleDist > 0)
{ {
// Create sample locations in a grid. // Create sample locations in a grid.
RcVec3f bmin = new RcVec3f(@in); RcVec3f bmin = RcVecUtils.Create(@in);
RcVec3f bmax = new RcVec3f(@in); RcVec3f bmax = RcVecUtils.Create(@in);
for (int i = 1; i < nin; ++i) for (int i = 1; i < nin; ++i)
{ {
bmin = RcVec3f.Min(bmin, RcVec.Create(@in, i * 3)); bmin = RcVecUtils.Min(bmin, @in, i * 3);
bmax = RcVec3f.Max(bmax, RcVec.Create(@in, i * 3)); bmax = RcVecUtils.Max(bmax, @in, i * 3);
} }
int x0 = (int)MathF.Floor(bmin.X / sampleDist); int x0 = (int)MathF.Floor(bmin.X / sampleDist);
@ -985,49 +1101,14 @@ namespace DotRecast.Recast
List<int> subList = tris.GetRange(0, MAX_TRIS * 4); List<int> subList = tris.GetRange(0, MAX_TRIS * 4);
tris.Clear(); tris.Clear();
tris.AddRange(subList); 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; return nverts;
} }
public static bool OnHull(int a, int b, int nhull, int[] hull) static void SeedArrayWithPolyCenter(RcTelemetry ctx, RcCompactHeightfield chf, int[] meshpoly, int poly, int npoly,
{
// 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.
public 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;
}
}
public static void SeedArrayWithPolyCenter(RcContext ctx, RcCompactHeightfield chf, int[] meshpoly, int poly, int npoly,
int[] verts, int bs, RcHeightPatch hp, List<int> array) int[] verts, int bs, RcHeightPatch hp, List<int> array)
{ {
// Note: Reads to the compact heightfield are offset by border size (bs) // Note: Reads to the compact heightfield are offset by border size (bs)
@ -1050,10 +1131,10 @@ namespace DotRecast.Recast
continue; continue;
} }
ref RcCompactCell c = ref chf.cells[(ax + bs) + (az + bs) * chf.width]; RcCompactCell c = chf.cells[(ax + bs) + (az + bs) * chf.width];
for (int i = c.index, ni = c.index + c.count; i < ni && dmin > 0; ++i) for (int i = c.index, ni = c.index + c.count; i < ni && dmin > 0; ++i)
{ {
ref RcCompactSpan s = ref chf.spans[i]; RcCompactSpan s = chf.spans[i];
int d = Math.Abs(ay - s.y); int d = Math.Abs(ay - s.y);
if (d < dmin) if (d < dmin)
{ {
@ -1129,12 +1210,12 @@ namespace DotRecast.Recast
dirs[3] = dirs[directDir]; dirs[3] = dirs[directDir];
dirs[directDir] = tmp; dirs[directDir] = tmp;
ref RcCompactSpan cs = ref chf.spans[ci]; RcCompactSpan cs = chf.spans[ci];
for (int i = 0; i < 4; ++i) for (int i = 0; i < 4; ++i)
{ {
int dir = dirs[i]; int dir = dirs[i];
if (GetCon(ref cs, dir) == RC_NOT_CONNECTED) if (GetCon(cs, dir) == RC_NOT_CONNECTED)
{ {
continue; continue;
} }
@ -1158,7 +1239,7 @@ namespace DotRecast.Recast
array.Add(newX); array.Add(newX);
array.Add(newY); array.Add(newY);
array.Add(chf.cells[(newX + bs) + (newY + bs) * chf.width].index + GetCon(ref cs, dir)); array.Add(chf.cells[(newX + bs) + (newY + bs) * chf.width].index + GetCon(cs, dir));
} }
tmp = dirs[3]; tmp = dirs[3];
@ -1172,29 +1253,26 @@ namespace DotRecast.Recast
array.Add(cy + bs); array.Add(cy + bs);
array.Add(ci); array.Add(ci);
Array.Fill(hp.data, RC_UNSET_HEIGHT, 0, (hp.width * hp.height) - (0)); Array.Fill(hp.data, RC_UNSET_HEIGHT, 0, (hp.width * hp.height) - (0));
ref RcCompactSpan cs2 = ref chf.spans[ci]; RcCompactSpan cs2 = chf.spans[ci];
hp.data[cx - hp.xmin + (cy - hp.ymin) * hp.width] = cs2.y; hp.data[cx - hp.xmin + (cy - hp.ymin) * hp.width] = cs2.y;
} }
const int RETRACT_SIZE = 256;
public static void Push3(List<int> queue, int v1, int v2, int v3) static void Push3(List<int> queue, int v1, int v2, int v3)
{ {
queue.Add(v1); queue.Add(v1);
queue.Add(v2); queue.Add(v2);
queue.Add(v3); queue.Add(v3);
} }
public static void GetHeightData(RcContext ctx, RcCompactHeightfield chf, static void GetHeightData(RcTelemetry ctx, RcCompactHeightfield chf, int[] meshpolys, int poly, int npoly, int[] verts,
int[] meshpolys, int poly, int npoly, int bs, RcHeightPatch hp, int region)
int[] verts, int bs,
ref RcHeightPatch hp, ref List<int> queue,
int region)
{ {
// Note: Reads to the compact heightfield are offset by border size (bs) // Note: Reads to the compact heightfield are offset by border size (bs)
// since border size offset is already removed from the polymesh vertices. // since border size offset is already removed from the polymesh vertices.
queue.Clear(); List<int> queue = new List<int>(512);
// Set all heights to RC_UNSET_HEIGHT.
Array.Fill(hp.data, RC_UNSET_HEIGHT, 0, (hp.width * hp.height) - (0)); Array.Fill(hp.data, RC_UNSET_HEIGHT, 0, (hp.width * hp.height) - (0));
bool empty = true; bool empty = true;
@ -1212,10 +1290,10 @@ namespace DotRecast.Recast
for (int hx = 0; hx < hp.width; hx++) for (int hx = 0; hx < hp.width; hx++)
{ {
int x = hp.xmin + hx + bs; int x = hp.xmin + hx + bs;
ref RcCompactCell c = ref chf.cells[x + y * chf.width]; RcCompactCell c = chf.cells[x + y * chf.width];
for (int i = c.index, ni = c.index + c.count; i < ni; ++i) for (int i = c.index, ni = c.index + c.count; i < ni; ++i)
{ {
ref RcCompactSpan s = ref chf.spans[i]; RcCompactSpan s = chf.spans[i];
if (s.reg == region) if (s.reg == region)
{ {
// Store height // Store height
@ -1226,12 +1304,12 @@ namespace DotRecast.Recast
bool border = false; bool border = false;
for (int dir = 0; dir < 4; ++dir) for (int dir = 0; dir < 4; ++dir)
{ {
if (GetCon(ref s, dir) != RC_NOT_CONNECTED) if (GetCon(s, dir) != RC_NOT_CONNECTED)
{ {
int ax = x + GetDirOffsetX(dir); int ax = x + GetDirOffsetX(dir);
int ay = y + GetDirOffsetY(dir); int ay = y + GetDirOffsetY(dir);
int ai = chf.cells[ax + ay * chf.width].index + GetCon(ref s, dir); int ai = chf.cells[ax + ay * chf.width].index + GetCon(s, dir);
ref RcCompactSpan @as = ref chf.spans[ai]; RcCompactSpan @as = chf.spans[ai];
if (@as.reg != region) if (@as.reg != region)
{ {
border = true; border = true;
@ -1260,7 +1338,6 @@ namespace DotRecast.Recast
SeedArrayWithPolyCenter(ctx, chf, meshpolys, poly, npoly, verts, bs, hp, queue); SeedArrayWithPolyCenter(ctx, chf, meshpolys, poly, npoly, verts, bs, hp, queue);
} }
const int RETRACT_SIZE = 256;
int head = 0; int head = 0;
// We assume the seed is centered in the polygon, so a BFS to collect // We assume the seed is centered in the polygon, so a BFS to collect
@ -1278,10 +1355,10 @@ namespace DotRecast.Recast
queue = queue.GetRange(RETRACT_SIZE * 3, queue.Count - (RETRACT_SIZE * 3)); queue = queue.GetRange(RETRACT_SIZE * 3, queue.Count - (RETRACT_SIZE * 3));
} }
ref RcCompactSpan cs = ref chf.spans[ci]; RcCompactSpan cs = chf.spans[ci];
for (int dir = 0; dir < 4; ++dir) for (int dir = 0; dir < 4; ++dir)
{ {
if (GetCon(ref cs, dir) == RC_NOT_CONNECTED) if (GetCon(cs, dir) == RC_NOT_CONNECTED)
{ {
continue; continue;
} }
@ -1301,8 +1378,8 @@ namespace DotRecast.Recast
continue; continue;
} }
int ai = chf.cells[ax + ay * chf.width].index + GetCon(ref cs, dir); int ai = chf.cells[ax + ay * chf.width].index + GetCon(cs, dir);
ref RcCompactSpan @as = ref chf.spans[ai]; RcCompactSpan @as = chf.spans[ai];
hp.data[hx + hy * hp.width] = @as.y; hp.data[hx + hy * hp.width] = @as.y;
Push3(queue, ax, ay, ai); Push3(queue, ax, ay, ai);
@ -1310,12 +1387,38 @@ 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 /// @par
/// ///
/// See the #rcConfig documentation for more information on the configuration parameters. /// See the #rcConfig documentation for more information on the configuration parameters.
/// ///
/// @see rcAllocPolyMeshDetail, rcPolyMesh, rcCompactHeightfield, rcPolyMeshDetail, rcConfig /// @see rcAllocPolyMeshDetail, rcPolyMesh, rcCompactHeightfield, rcPolyMeshDetail, rcConfig
public static RcPolyMeshDetail BuildPolyMeshDetail(RcContext ctx, RcPolyMesh mesh, RcCompactHeightfield chf, public static RcPolyMeshDetail BuildPolyMeshDetail(RcTelemetry ctx, RcPolyMesh mesh, RcCompactHeightfield chf,
float sampleDist, float sampleMaxError) float sampleDist, float sampleMaxError)
{ {
using var timer = ctx.ScopedTimer(RcTimerLabel.RC_TIMER_BUILD_POLYMESHDETAIL); using var timer = ctx.ScopedTimer(RcTimerLabel.RC_TIMER_BUILD_POLYMESHDETAIL);
@ -1332,10 +1435,7 @@ namespace DotRecast.Recast
int borderSize = mesh.borderSize; int borderSize = mesh.borderSize;
int heightSearchRadius = (int)Math.Max(1, MathF.Ceiling(mesh.maxEdgeError)); int heightSearchRadius = (int)Math.Max(1, MathF.Ceiling(mesh.maxEdgeError));
List<int> edges = new List<int>(64);
List<int> tris = new List<int>(512); List<int> tris = new List<int>(512);
List<int> arr = new List<int>(512);
List<int> samples = new List<int>(512);
float[] verts = new float[256 * 3]; float[] verts = new float[256 * 3];
RcHeightPatch hp = new RcHeightPatch(); RcHeightPatch hp = new RcHeightPatch();
int nPolyVerts = 0; int nPolyVerts = 0;
@ -1420,20 +1520,18 @@ namespace DotRecast.Recast
hp.ymin = bounds[i * 4 + 2]; hp.ymin = bounds[i * 4 + 2];
hp.width = bounds[i * 4 + 1] - bounds[i * 4 + 0]; hp.width = bounds[i * 4 + 1] - bounds[i * 4 + 0];
hp.height = bounds[i * 4 + 3] - bounds[i * 4 + 2]; hp.height = bounds[i * 4 + 3] - bounds[i * 4 + 2];
GetHeightData(ctx, chf, mesh.polys, p, npoly, mesh.verts, borderSize, ref hp, ref arr, mesh.regs[i]); GetHeightData(ctx, chf, mesh.polys, p, npoly, mesh.verts, borderSize, hp, mesh.regs[i]);
// Build detail mesh. // Build detail mesh.
int nverts = BuildPolyDetail(ctx, poly, npoly, int nverts = BuildPolyDetail(ctx, poly, npoly, sampleDist, sampleMaxError, heightSearchRadius, chf, hp,
sampleDist, sampleMaxError, verts, tris);
heightSearchRadius, chf, hp,
verts, ref tris,
ref edges, ref samples);
// Move detail verts to world space. // Move detail verts to world space.
for (int j = 0; j < nverts; ++j) for (int j = 0; j < nverts; ++j)
{ {
verts[j * 3 + 0] += orig.X; verts[j * 3 + 0] += orig.X;
verts[j * 3 + 1] += orig.Y + chf.ch; // Is this offset necessary? See verts[j * 3 + 1] += orig.Y + chf.ch; // Is this offset necessary? See
// https://groups.google.com/d/msg/recastnavigation/UQFN6BGCcV0/-1Ny4koOBpkJ
verts[j * 3 + 2] += orig.Z; verts[j * 3 + 2] += orig.Z;
} }
@ -1464,7 +1562,7 @@ namespace DotRecast.Recast
float[] newv = new float[vcap * 3]; float[] newv = new float[vcap * 3];
if (dmesh.nverts != 0) if (dmesh.nverts != 0)
{ {
RcArrays.Copy(dmesh.verts, 0, newv, 0, 3 * dmesh.nverts); Array.Copy(dmesh.verts, 0, newv, 0, 3 * dmesh.nverts);
} }
dmesh.verts = newv; dmesh.verts = newv;
@ -1489,7 +1587,7 @@ namespace DotRecast.Recast
int[] newt = new int[tcap * 4]; int[] newt = new int[tcap * 4];
if (dmesh.ntris != 0) if (dmesh.ntris != 0)
{ {
RcArrays.Copy(dmesh.tris, 0, newt, 0, 4 * dmesh.ntris); Array.Copy(dmesh.tris, 0, newt, 0, 4 * dmesh.ntris);
} }
dmesh.tris = newt; dmesh.tris = newt;
@ -1501,7 +1599,8 @@ namespace DotRecast.Recast
dmesh.tris[dmesh.ntris * 4 + 0] = tris[t + 0]; dmesh.tris[dmesh.ntris * 4 + 0] = tris[t + 0];
dmesh.tris[dmesh.ntris * 4 + 1] = tris[t + 1]; dmesh.tris[dmesh.ntris * 4 + 1] = tris[t + 1];
dmesh.tris[dmesh.ntris * 4 + 2] = tris[t + 2]; dmesh.tris[dmesh.ntris * 4 + 2] = tris[t + 2];
dmesh.tris[dmesh.ntris * 4 + 3] = tris[t + 3]; dmesh.tris[dmesh.ntris * 4 + 3] = GetTriFlags(verts, tris[t + 0] * 3, tris[t + 1] * 3,
tris[t + 2] * 3, poly, npoly);
dmesh.ntris++; dmesh.ntris++;
} }
} }
@ -1510,7 +1609,7 @@ namespace DotRecast.Recast
} }
/// @see rcAllocPolyMeshDetail, rcPolyMeshDetail /// @see rcAllocPolyMeshDetail, rcPolyMeshDetail
public static RcPolyMeshDetail MergePolyMeshDetails(RcContext ctx, RcPolyMeshDetail[] meshes, int nmeshes) private static RcPolyMeshDetail MergePolyMeshDetails(RcTelemetry ctx, RcPolyMeshDetail[] meshes, int nmeshes)
{ {
using var timer = ctx.ScopedTimer(RcTimerLabel.RC_TIMER_MERGE_POLYMESHDETAIL); using var timer = ctx.ScopedTimer(RcTimerLabel.RC_TIMER_MERGE_POLYMESHDETAIL);
@ -1561,7 +1660,7 @@ namespace DotRecast.Recast
for (int k = 0; k < dm.nverts; ++k) for (int k = 0; k < dm.nverts; ++k)
{ {
RcVec.Copy(mesh.verts, mesh.nverts * 3, dm.verts, k * 3); RcVecUtils.Copy(mesh.verts, mesh.nverts * 3, dm.verts, k * 3);
mesh.nverts++; mesh.nverts++;
} }

View File

@ -1,7 +1,7 @@
/* /*
Copyright (c) 2009-2010 Mikko Mononen memon@inside.org Copyright (c) 2009-2010 Mikko Mononen memon@inside.org
recast4j copyright (c) 2015-2019 Piotr Piastucki piotr@jtilia.org recast4j copyright (c) 2015-2019 Piotr Piastucki piotr@jtilia.org
DotRecast Copyright (c) 2023-2024 Choi Ikpil ikpil@naver.com DotRecast Copyright (c) 2023 Choi Ikpil ikpil@naver.com
This software is provided 'as-is', without any express or implied This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages warranty. In no event will the authors be held liable for any damages
@ -24,7 +24,7 @@ using DotRecast.Core.Numerics;
namespace DotRecast.Recast namespace DotRecast.Recast
{ {
using static RcRecast; using static RcConstants;
public static class RcMeshs public static class RcMeshs
{ {
@ -558,7 +558,7 @@ namespace DotRecast.Recast
n++; n++;
} }
RcArrays.Copy(polys, tmp, polys, pa, nvp); Array.Copy(polys, tmp, polys, pa, nvp);
} }
private static int PushFront(int v, int[] arr, int an) private static int PushFront(int v, int[] arr, int an)
@ -577,7 +577,7 @@ namespace DotRecast.Recast
return an; return an;
} }
private static bool CanRemoveVertex(RcContext ctx, RcPolyMesh mesh, int rem) private static bool CanRemoveVertex(RcTelemetry ctx, RcPolyMesh mesh, int rem)
{ {
int nvp = mesh.nvp; int nvp = mesh.nvp;
@ -680,7 +680,7 @@ namespace DotRecast.Recast
return true; return true;
} }
private static void RemoveVertex(RcContext ctx, RcPolyMesh mesh, int rem, int maxTris) private static void RemoveVertex(RcTelemetry ctx, RcPolyMesh mesh, int rem, int maxTris)
{ {
int nvp = mesh.nvp; int nvp = mesh.nvp;
@ -715,13 +715,8 @@ namespace DotRecast.Recast
int nv = CountPolyVerts(mesh.polys, p, nvp); int nv = CountPolyVerts(mesh.polys, p, nvp);
bool hasRem = false; bool hasRem = false;
for (int j = 0; j < nv; ++j) for (int j = 0; j < nv; ++j)
{
if (mesh.polys[p + j] == rem) if (mesh.polys[p + j] == rem)
{
hasRem = true; hasRem = true;
}
}
if (hasRem) if (hasRem)
{ {
// Collect edges which does not touch the removed vertex. // Collect edges which does not touch the removed vertex.
@ -742,7 +737,7 @@ namespace DotRecast.Recast
int p2 = (mesh.npolys - 1) * nvp * 2; int p2 = (mesh.npolys - 1) * nvp * 2;
if (p != p2) if (p != p2)
{ {
RcArrays.Copy(mesh.polys, p2, mesh.polys, p, nvp); Array.Copy(mesh.polys, p2, mesh.polys, p, nvp);
} }
Array.Fill(mesh.polys, RC_MESH_NULL_IDX, p + nvp, (p + nvp + nvp) - (p + nvp)); Array.Fill(mesh.polys, RC_MESH_NULL_IDX, p + nvp, (p + nvp + nvp) - (p + nvp));
@ -932,7 +927,7 @@ namespace DotRecast.Recast
int last = (npolys - 1) * nvp; int last = (npolys - 1) * nvp;
if (pb != last) if (pb != last)
{ {
RcArrays.Copy(polys, last, polys, pb, nvp); Array.Copy(polys, last, polys, pb, nvp);
} }
pregs[bestPb] = pregs[npolys - 1]; pregs[bestPb] = pregs[npolys - 1];
@ -961,7 +956,7 @@ namespace DotRecast.Recast
mesh.npolys++; mesh.npolys++;
if (mesh.npolys > maxTris) if (mesh.npolys > maxTris)
{ {
throw new Exception($"removeVertex: Too many polygons {mesh.npolys} max:({maxTris})."); throw new Exception("removeVertex: Too many polygons " + mesh.npolys + " (max:" + maxTris + ".");
} }
} }
} }
@ -972,7 +967,7 @@ namespace DotRecast.Recast
/// limit must be restricted to <= #DT_VERTS_PER_POLYGON. /// limit must be restricted to <= #DT_VERTS_PER_POLYGON.
/// ///
/// @see rcAllocPolyMesh, rcContourSet, rcPolyMesh, rcConfig /// @see rcAllocPolyMesh, rcContourSet, rcPolyMesh, rcConfig
public static RcPolyMesh BuildPolyMesh(RcContext ctx, RcContourSet cset, int nvp) public static RcPolyMesh BuildPolyMesh(RcTelemetry ctx, RcContourSet cset, int nvp)
{ {
using var timer = ctx.ScopedTimer(RcTimerLabel.RC_TIMER_BUILD_POLYMESH); using var timer = ctx.ScopedTimer(RcTimerLabel.RC_TIMER_BUILD_POLYMESH);
@ -1038,23 +1033,11 @@ namespace DotRecast.Recast
// Triangulate contour // Triangulate contour
for (int j = 0; j < cont.nverts; ++j) for (int j = 0; j < cont.nverts; ++j)
indices[j] = j; indices[j] = j;
int ntris = Triangulate(cont.nverts, cont.verts, indices, tris); int ntris = Triangulate(cont.nverts, cont.verts, indices, tris);
if (ntris <= 0) if (ntris <= 0)
{ {
// Bad triangulation, should not happen. // Bad triangulation, should not happen.
/* ctx.Warn("buildPolyMesh: Bad triangulation Contour " + i + ".");
printf("\tconst float bmin[3] = {%ff,%ff,%ff};\n", cset.bmin[0], cset.bmin[1], cset.bmin[2]);
printf("\tconst float cs = %ff;\n", cset.cs);
printf("\tconst float ch = %ff;\n", cset.ch);
printf("\tconst int verts[] = {\n");
for (int k = 0; k < cont.nverts; ++k)
{
const int* v = &cont.verts[k*4];
printf("\t\t%d,%d,%d,%d,\n", v[0], v[1], v[2], v[3]);
}
printf("\t};\n\tconst int nverts = sizeof(verts)/(sizeof(int)*4);\n");*/
ctx.Warn($"BuildPolyMesh: Bad triangulation Contour {i}.");
ntris = -ntris; ntris = -ntris;
} }
@ -1126,7 +1109,7 @@ namespace DotRecast.Recast
int lastPoly = (npolys - 1) * nvp; int lastPoly = (npolys - 1) * nvp;
if (pb != lastPoly) if (pb != lastPoly)
{ {
RcArrays.Copy(polys, lastPoly, polys, pb, nvp); Array.Copy(polys, lastPoly, polys, pb, nvp);
} }
npolys--; npolys--;
@ -1215,19 +1198,21 @@ namespace DotRecast.Recast
if (mesh.nverts > MAX_MESH_VERTS_POLY) if (mesh.nverts > MAX_MESH_VERTS_POLY)
{ {
throw new Exception($"BuildPolyMesh: The resulting mesh has too many vertices {mesh.nverts} (max {MAX_MESH_VERTS_POLY}). Data can be corrupted."); throw new Exception("rcBuildPolyMesh: The resulting mesh has too many vertices " + mesh.nverts
+ " (max " + MAX_MESH_VERTS_POLY + "). Data can be corrupted.");
} }
if (mesh.npolys > MAX_MESH_VERTS_POLY) if (mesh.npolys > MAX_MESH_VERTS_POLY)
{ {
throw new Exception($"BuildPolyMesh: The resulting mesh has too many polygons {mesh.npolys} (max {MAX_MESH_VERTS_POLY}). Data can be corrupted."); throw new Exception("rcBuildPolyMesh: The resulting mesh has too many polygons " + mesh.npolys
+ " (max " + MAX_MESH_VERTS_POLY + "). Data can be corrupted.");
} }
return mesh; return mesh;
} }
/// @see rcAllocPolyMesh, rcPolyMesh /// @see rcAllocPolyMesh, rcPolyMesh
public static RcPolyMesh MergePolyMeshes(RcContext ctx, RcPolyMesh[] meshes, int nmeshes) public static RcPolyMesh MergePolyMeshes(RcTelemetry ctx, RcPolyMesh[] meshes, int nmeshes)
{ {
if (nmeshes == 0 || meshes == null) if (nmeshes == 0 || meshes == null)
return null; return null;
@ -1355,7 +1340,7 @@ namespace DotRecast.Recast
return mesh; return mesh;
} }
public static RcPolyMesh CopyPolyMesh(RcContext ctx, RcPolyMesh src) public static RcPolyMesh CopyPolyMesh(RcTelemetry ctx, RcPolyMesh src)
{ {
RcPolyMesh dst = new RcPolyMesh(); RcPolyMesh dst = new RcPolyMesh();
@ -1371,15 +1356,15 @@ namespace DotRecast.Recast
dst.maxEdgeError = src.maxEdgeError; dst.maxEdgeError = src.maxEdgeError;
dst.verts = new int[src.nverts * 3]; dst.verts = new int[src.nverts * 3];
RcArrays.Copy(src.verts, 0, dst.verts, 0, dst.verts.Length); Array.Copy(src.verts, 0, dst.verts, 0, dst.verts.Length);
dst.polys = new int[src.npolys * 2 * src.nvp]; dst.polys = new int[src.npolys * 2 * src.nvp];
RcArrays.Copy(src.polys, 0, dst.polys, 0, dst.polys.Length); Array.Copy(src.polys, 0, dst.polys, 0, dst.polys.Length);
dst.regs = new int[src.npolys]; dst.regs = new int[src.npolys];
RcArrays.Copy(src.regs, 0, dst.regs, 0, dst.regs.Length); Array.Copy(src.regs, 0, dst.regs, 0, dst.regs.Length);
dst.areas = new int[src.npolys]; dst.areas = new int[src.npolys];
RcArrays.Copy(src.areas, 0, dst.areas, 0, dst.areas.Length); Array.Copy(src.areas, 0, dst.areas, 0, dst.areas.Length);
dst.flags = new int[src.npolys]; dst.flags = new int[src.npolys];
RcArrays.Copy(src.flags, 0, dst.flags, 0, dst.flags.Length); Array.Copy(src.flags, 0, dst.flags, 0, dst.flags.Length);
return dst; return dst;
} }
} }

View File

@ -1,4 +1,4 @@
namespace DotRecast.Recast namespace DotRecast.Recast
{ {
public enum RcPartition public enum RcPartition
{ {

View File

@ -1,4 +1,4 @@
using System.Linq; using System.Linq;
namespace DotRecast.Recast namespace DotRecast.Recast
{ {

View File

@ -1,7 +1,7 @@
/* /*
Copyright (c) 2009-2010 Mikko Mononen memon@inside.org Copyright (c) 2009-2010 Mikko Mononen memon@inside.org
Recast4J Copyright (c) 2015 Piotr Piastucki piotr@jtilia.org Recast4J Copyright (c) 2015 Piotr Piastucki piotr@jtilia.org
DotRecast Copyright (c) 2023-2024 Choi Ikpil ikpil@naver.com DotRecast Copyright (c) 2023 Choi Ikpil ikpil@naver.com
This software is provided 'as-is', without any express or implied This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages warranty. In no event will the authors be held liable for any damages
@ -22,26 +22,52 @@ using DotRecast.Core.Numerics;
namespace DotRecast.Recast 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 public class RcPolyMesh
{ {
public int[] verts; // The mesh vertices. [Form: (x, y, z) coordinates * #nverts] /** The mesh vertices. [Form: (x, y, z) coordinates * #nverts] */
public int[] polys; // Polygon and neighbor data. [Length: #maxpolys * 2 * #nvp] public int[] verts;
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)]
public float cs; // The size of each cell. (On the xz-plane.) /** Polygon and neighbor data. [Length: #maxpolys * 2 * #nvp] */
public float ch; // The height of each cell. (The minimum increment along the y-axis.) public int[] polys;
public int borderSize; // The AABB border size used to generate the source data from which the mesh was derived. /** The region id assigned to each polygon. [Length: #maxpolys] */
public float maxEdgeError; // The max error of the polygon edges in the mesh. 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;
} }
} }

View File

@ -1,7 +1,7 @@
/* /*
Copyright (c) 2009-2010 Mikko Mononen memon@inside.org Copyright (c) 2009-2010 Mikko Mononen memon@inside.org
recast4j copyright (c) 2015-2019 Piotr Piastucki piotr@jtilia.org recast4j copyright (c) 2015-2019 Piotr Piastucki piotr@jtilia.org
DotRecast Copyright (c) 2023-2024 Choi Ikpil ikpil@naver.com DotRecast Copyright (c) 2023 Choi Ikpil ikpil@naver.com
This software is provided 'as-is', without any express or implied This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages warranty. In no event will the authors be held liable for any damages
@ -20,16 +20,28 @@ freely, subject to the following restrictions:
namespace DotRecast.Recast 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
/// @ingroup recast * mesh object.
*/
public class RcPolyMeshDetail public class RcPolyMeshDetail
{ {
public int[] meshes; //< The sub-mesh data. [Size: 4*#nmeshes] /** The sub-mesh data. [Size: 4*#nmeshes] */
public float[] verts; //< The mesh vertices. [Size: 3*#nverts] public int[] meshes;
public int[] tris; //< The mesh triangles. [Size: 4*#ntris]
public int nmeshes; //< The number of sub-meshes defined by #meshes. /** The mesh vertices. [Size: 3*#nverts] */
public int nverts; //< The number of vertices in #verts. public float[] verts;
public int ntris; //< The number of triangles in #tris.
/** 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;
} }
} }

View File

@ -1,6 +1,6 @@
/* /*
recast4j copyright (c) 2021 Piotr Piastucki piotr@jtilia.org recast4j copyright (c) 2021 Piotr Piastucki piotr@jtilia.org
DotRecast Copyright (c) 2023-2024 Choi Ikpil ikpil@naver.com DotRecast Copyright (c) 2023 Choi Ikpil ikpil@naver.com
This software is provided 'as-is', without any express or implied This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages warranty. In no event will the authors be held liable for any damages
@ -17,7 +17,6 @@ freely, subject to the following restrictions:
3. This notice may not be removed or altered from any source distribution. 3. This notice may not be removed or altered from any source distribution.
*/ */
using System;
using System.Collections.Generic; using System.Collections.Generic;
using DotRecast.Core; using DotRecast.Core;
using DotRecast.Core.Numerics; using DotRecast.Core.Numerics;
@ -31,9 +30,9 @@ namespace DotRecast.Recast
hitTime = 0.0f; hitTime = 0.0f;
foreach (RcBuilderResult result in results) foreach (RcBuilderResult result in results)
{ {
if (result.MeshDetail != null) if (result.GetMeshDetail() != null)
{ {
if (Raycast(result.Mesh, result.MeshDetail, src, dst, out hitTime)) if (Raycast(result.GetMesh(), result.GetMeshDetail(), src, dst, out hitTime))
{ {
return true; return true;
} }
@ -46,7 +45,6 @@ namespace DotRecast.Recast
private static bool Raycast(RcPolyMesh poly, RcPolyMeshDetail meshDetail, RcVec3f sp, RcVec3f sq, out float hitTime) private static bool Raycast(RcPolyMesh poly, RcPolyMeshDetail meshDetail, RcVec3f sp, RcVec3f sq, out float hitTime)
{ {
hitTime = 0; hitTime = 0;
Span<RcVec3f> tempVs = stackalloc RcVec3f[3];
if (meshDetail != null) if (meshDetail != null)
{ {
for (int i = 0; i < meshDetail.nmeshes; ++i) for (int i = 0; i < meshDetail.nmeshes; ++i)
@ -59,7 +57,7 @@ namespace DotRecast.Recast
int tris = btris * 4; int tris = btris * 4;
for (int j = 0; j < ntris; ++j) for (int j = 0; j < ntris; ++j)
{ {
Span<RcVec3f> vs = tempVs; RcVec3f[] vs = new RcVec3f[3];
for (int k = 0; k < 3; ++k) for (int k = 0; k < 3; ++k)
{ {
vs[k].X = meshDetail.verts[verts + meshDetail.tris[tris + j * 4 + k] * 3]; vs[k].X = meshDetail.verts[verts + meshDetail.tris[tris + j * 4 + k] * 3];

View File

@ -1,4 +1,4 @@
using System.Collections.Generic; using System.Collections.Generic;
namespace DotRecast.Recast namespace DotRecast.Recast
{ {

View File

@ -1,7 +1,7 @@
/* /*
Copyright (c) 2009-2010 Mikko Mononen memon@inside.org Copyright (c) 2009-2010 Mikko Mononen memon@inside.org
recast4j copyright (c) 2015-2019 Piotr Piastucki piotr@jtilia.org recast4j copyright (c) 2015-2019 Piotr Piastucki piotr@jtilia.org
DotRecast Copyright (c) 2023-2024 Choi Ikpil ikpil@naver.com DotRecast Copyright (c) 2023 Choi Ikpil ikpil@naver.com
This software is provided 'as-is', without any express or implied This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages warranty. In no event will the authors be held liable for any damages
@ -21,84 +21,41 @@ freely, subject to the following restrictions:
using System; using System;
using DotRecast.Core; using DotRecast.Core;
using DotRecast.Core.Numerics; using DotRecast.Core.Numerics;
using static DotRecast.Recast.RcConstants;
namespace DotRecast.Recast namespace DotRecast.Recast
{ {
using static RcRecast;
public static class RcRasterizations public static class RcRasterizations
{ {
/// Check whether two bounding boxes overlap /**
/// * 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 amin
/// @param[in] bMin Min axis extents of bounding box B * Min axis extents of bounding box A
/// @param[in] bMax Max axis extents of bounding box B * @param amax
/// @returns true if the two bounding boxes overlap. False otherwise. * Max axis extents of bounding box A
private static bool OverlapBounds(RcVec3f aMin, RcVec3f aMax, RcVec3f bMin, RcVec3f bMax) * @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)
{ {
return bool overlap = true;
aMin.X <= bMax.X && aMax.X >= bMin.X && overlap = (amin[0] > bmax[0] || amax[0] < bmin[0]) ? false : overlap;
aMin.Y <= bMax.Y && aMax.Y >= bMin.Y && overlap = (amin[1] > bmax[1] || amax[1] < bmin[1]) ? false : overlap;
aMin.Z <= bMax.Z && aMax.Z >= bMin.Z; overlap = (amin[2] > bmax[2] || amax[2] < bmin[2]) ? false : overlap;
return overlap;
} }
/// Allocates a new span in the heightfield. private static bool OverlapBounds(RcVec3f amin, RcVec3f amax, RcVec3f bmin, RcVec3f bmax)
/// Use a memory pool and free list to minimize actual allocations.
///
/// @param[in] heightfield The heightfield
/// @returns A pointer to the allocated or re-used span memory.
private static RcSpan AllocSpan(RcHeightfield heightfield)
{ {
// If necessary, allocate new page and update the freelist. bool overlap = true;
if (heightfield.freelist == null || heightfield.freelist.next == null) overlap = (amin.X > bmax.X || amax.X < bmin.X) ? false : overlap;
{ overlap = (amin.Y > bmax.Y || amax.Y < bmin.Y) ? false : overlap;
// Create new page. overlap = (amin.Z > bmax.Z || amax.Z < bmin.Z) ? false : overlap;
// Allocate memory for the new pool. return overlap;
RcSpanPool spanPool = new RcSpanPool();
if (spanPool == null)
{
return null;
}
// Add the pool into the list of pools.
spanPool.next = heightfield.pools;
heightfield.pools = spanPool;
// Add new spans to the free list.
RcSpan freeList = heightfield.freelist;
int head = 0;
int it = RC_SPANS_PER_POOL;
do
{
--it;
spanPool.items[it].next = freeList;
freeList = spanPool.items[it];
} while (it != head);
heightfield.freelist = spanPool.items[it];
}
// Pop item from the front of the free list.
RcSpan newSpan = heightfield.freelist;
heightfield.freelist = heightfield.freelist.next;
return newSpan;
}
/// Releases the memory used by the span back to the heightfield, so it can be re-used for new spans.
/// @param[in] heightfield The heightfield.
/// @param[in] span A pointer to the span to free
private static void FreeSpan(RcHeightfield heightfield, RcSpan span)
{
if (span == null)
{
return;
}
// Add the span to the front of the free list.
span.next = heightfield.freelist;
heightfield.freelist = span;
} }
@ -112,89 +69,72 @@ namespace DotRecast.Recast
/// @param[in] max The new span's maximum cell index /// @param[in] max The new span's maximum cell index
/// @param[in] areaID The new span's area type ID /// @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 /// @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 z, int min, int max, int areaID, int flagMergeThreshold) public static void AddSpan(RcHeightfield heightfield, int x, int y, int spanMin, int spanMax, int areaId, int flagMergeThreshold)
{ {
// Create the new span. int idx = x + y * heightfield.width;
RcSpan newSpan = new RcSpan();
newSpan.smin = min;
newSpan.smax = max;
newSpan.area = areaID;
newSpan.next = null;
int columnIndex = x + z * heightfield.width; RcSpan s = new RcSpan();
s.smin = spanMin;
s.smax = spanMax;
s.area = areaId;
s.next = null;
// Empty cell, add the first span. // Empty cell, add the first span.
if (heightfield.spans[columnIndex] == null) if (heightfield.spans[idx] == null)
{ {
heightfield.spans[columnIndex] = newSpan; heightfield.spans[idx] = s;
return; return;
} }
RcSpan previousSpan = null; RcSpan prev = null;
RcSpan currentSpan = heightfield.spans[columnIndex]; RcSpan cur = heightfield.spans[idx];
// Insert the new span, possibly merging it with existing spans. // Insert and merge spans.
while (currentSpan != null) while (cur != null)
{ {
if (currentSpan.smin > newSpan.smax) if (cur.smin > s.smax)
{ {
// Current span is further than the new span, break. // Current span is further than the new span, break.
break; break;
} }
else if (cur.smax < s.smin)
if (currentSpan.smax < newSpan.smin)
{ {
// Current span is completely before the new span. Keep going. // Current span is before the new span advance.
previousSpan = currentSpan; prev = cur;
currentSpan = currentSpan.next; cur = cur.next;
} }
else else
{ {
// The new span overlaps with an existing span. Merge them. // Merge spans.
if (currentSpan.smin < newSpan.smin) if (cur.smin < s.smin)
{ s.smin = cur.smin;
newSpan.smin = currentSpan.smin; if (cur.smax > s.smax)
} s.smax = cur.smax;
if (currentSpan.smax > newSpan.smax)
{
newSpan.smax = currentSpan.smax;
}
// Merge flags. // Merge flags.
if (MathF.Abs(newSpan.smax - currentSpan.smax) <= flagMergeThreshold) if (MathF.Abs(s.smax - cur.smax) <= flagMergeThreshold)
{ s.area = Math.Max(s.area, cur.area);
// 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;
else
heightfield.spans[idx] = next;
cur = next;
}
} }
// Remove the current span since it's now merged with newSpan. // Insert new span.
// Keep going because there might be other overlapping spans that also need to be merged. if (prev != null)
RcSpan next = currentSpan.next;
if (previousSpan != null)
{ {
previousSpan.next = next; s.next = prev.next;
prev.next = s;
} }
else else
{ {
heightfield.spans[columnIndex] = next; s.next = heightfield.spans[idx];
} heightfield.spans[idx] = s;
currentSpan = next;
}
}
// Insert new span after prev
if (previousSpan != null)
{
newSpan.next = previousSpan.next;
previousSpan.next = newSpan;
}
else
{
// This span should go before the others in the list
newSpan.next = heightfield.spans[columnIndex];
heightfield.spans[columnIndex] = newSpan;
} }
} }
@ -209,61 +149,62 @@ namespace DotRecast.Recast
/// @param[out] outVerts2Count The number of resulting polygon 2 vertices /// @param[out] outVerts2Count The number of resulting polygon 2 vertices
/// @param[in] axisOffset THe offset along the specified axis /// @param[in] axisOffset THe offset along the specified axis
/// @param[in] axis The separating axis /// @param[in] axis The separating axis
private static void DividePoly(Span<float> inVerts, int inVertsOffset, int inVertsCount, private static void DividePoly(float[] inVerts, int inVertsOffset, int inVertsCount,
int outVerts1, out int outVerts1Count, int outVerts1, out int outVerts1Count,
int outVerts2, out int outVerts2Count, int outVerts2, out int outVerts2Count,
float axisOffset, int axis) float axisOffset, int axis)
{ {
float[] d = new float[12];
// How far positive or negative away from the separating axis is each vertex. // How far positive or negative away from the separating axis is each vertex.
Span<float> inVertAxisDelta = stackalloc float[12];
for (int inVert = 0; inVert < inVertsCount; ++inVert) for (int inVert = 0; inVert < inVertsCount; ++inVert)
{ {
inVertAxisDelta[inVert] = axisOffset - inVerts[inVertsOffset + inVert * 3 + axis]; d[inVert] = axisOffset - inVerts[inVertsOffset + inVert * 3 + axis];
} }
int poly1Vert = 0; int poly1Vert = 0;
int poly2Vert = 0; int poly2Vert = 0;
for (int inVertA = 0, inVertB = inVertsCount - 1; inVertA < inVertsCount; inVertB = inVertA, ++inVertA) for (int inVertA = 0, inVertB = inVertsCount - 1; inVertA < inVertsCount; inVertB = inVertA, ++inVertA)
{ {
// If the two vertices are on the same side of the separating axis bool ina = d[inVertB] >= 0;
bool sameSide = (inVertAxisDelta[inVertA] >= 0) == (inVertAxisDelta[inVertB] >= 0); bool inb = d[inVertA] >= 0;
if (!sameSide) if (ina != inb)
{ {
float s = inVertAxisDelta[inVertB] / (inVertAxisDelta[inVertB] - inVertAxisDelta[inVertA]); 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 + 0] = inVerts[inVertsOffset + inVertB * 3 + 0] +
inVerts[outVerts1 + poly1Vert * 3 + 1] = inVerts[inVertsOffset + inVertB * 3 + 1] + (inVerts[inVertsOffset + inVertA * 3 + 1] - inVerts[inVertsOffset + inVertB * 3 + 1]) * s; (inVerts[inVertsOffset + inVertA * 3 + 0] - inVerts[inVertsOffset + inVertB * 3 + 0]) * s;
inVerts[outVerts1 + poly1Vert * 3 + 2] = inVerts[inVertsOffset + inVertB * 3 + 2] + (inVerts[inVertsOffset + inVertA * 3 + 2] - inVerts[inVertsOffset + inVertB * 3 + 2]) * s; inVerts[outVerts1 + poly1Vert * 3 + 1] = inVerts[inVertsOffset + inVertB * 3 + 1] +
RcVec.Copy(inVerts, outVerts2 + poly2Vert * 3, inVerts, outVerts1 + poly1Vert * 3); (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++; poly1Vert++;
poly2Vert++; poly2Vert++;
// add the i'th point to the right polygon. Do NOT add points that are on the dividing line // 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 // since these were already added above
if (inVertAxisDelta[inVertA] > 0) if (d[inVertA] > 0)
{ {
RcVec.Copy(inVerts, outVerts1 + poly1Vert * 3, inVerts, inVertsOffset + inVertA * 3); RcVecUtils.Copy(inVerts, outVerts1 + poly1Vert * 3, inVerts, inVertsOffset + inVertA * 3);
poly1Vert++; poly1Vert++;
} }
else if (inVertAxisDelta[inVertA] < 0) else if (d[inVertA] < 0)
{ {
RcVec.Copy(inVerts, outVerts2 + poly2Vert * 3, inVerts, inVertsOffset + inVertA * 3); RcVecUtils.Copy(inVerts, outVerts2 + poly2Vert * 3, inVerts, inVertsOffset + inVertA * 3);
poly2Vert++; poly2Vert++;
} }
} }
else else // same side
{ {
// add the i'th point to the right polygon. Addition is done even for points on the dividing line // add the i'th point to the right polygon. Addition is done even for points on the dividing line
if (inVertAxisDelta[inVertA] >= 0) if (d[inVertA] >= 0)
{ {
RcVec.Copy(inVerts, outVerts1 + poly1Vert * 3, inVerts, inVertsOffset + inVertA * 3); RcVecUtils.Copy(inVerts, outVerts1 + poly1Vert * 3, inVerts, inVertsOffset + inVertA * 3);
poly1Vert++; poly1Vert++;
if (inVertAxisDelta[inVertA] != 0) if (d[inVertA] != 0)
{
continue; continue;
} }
}
RcVec.Copy(inVerts, outVerts2 + poly2Vert * 3, inVerts, inVertsOffset + inVertA * 3); RcVecUtils.Copy(inVerts, outVerts2 + poly2Vert * 3, inVerts, inVertsOffset + inVertA * 3);
poly2Vert++; poly2Vert++;
} }
} }
@ -288,72 +229,64 @@ namespace DotRecast.Recast
/// @param[in] inverseCellHeight 1 / cellHeight /// @param[in] inverseCellHeight 1 / cellHeight
/// @param[in] flagMergeThreshold The threshold in which area flags will be merged /// @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. /// @returns true if the operation completes successfully. false if there was an error adding spans to the heightfield.
private static bool RasterizeTri(float[] verts, int v0, int v1, int v2, private static void RasterizeTri(float[] verts, int v0, int v1, int v2, int area, RcHeightfield heightfield,
int areaID, RcHeightfield heightfield,
RcVec3f heightfieldBBMin, RcVec3f heightfieldBBMax, RcVec3f heightfieldBBMin, RcVec3f heightfieldBBMax,
float cellSize, float inverseCellSize, float inverseCellHeight, float cellSize, float inverseCellSize, float inverseCellHeight,
int flagMergeThreshold) int flagMergeThreshold)
{ {
float by = heightfieldBBMax.Y - heightfieldBBMin.Y;
// Calculate the bounding box of the triangle. // Calculate the bounding box of the triangle.
RcVec3f triBBMin = RcVec.Create(verts, v0 * 3); RcVec3f tmin = RcVecUtils.Create(verts, v0 * 3);
triBBMin = RcVec3f.Min(triBBMin, RcVec.Create(verts, v1 * 3)); RcVec3f tmax = RcVecUtils.Create(verts, v0 * 3);
triBBMin = RcVec3f.Min(triBBMin, RcVec.Create(verts, v2 * 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 triBBMax = RcVec.Create(verts, v0 * 3); // If the triangle does not touch the bbox of the heightfield, skip the triagle.
triBBMax = RcVec3f.Max(triBBMax, RcVec.Create(verts, v1 * 3)); if (!OverlapBounds(heightfieldBBMin, heightfieldBBMax, tmin, tmax))
triBBMax = RcVec3f.Max(triBBMax, RcVec.Create(verts, v2 * 3)); return;
// If the triangle does not touch the bounding box of the heightfield, skip the triangle. // Calculate the footprint of the triangle on the grid's y-axis
if (!OverlapBounds(triBBMin, triBBMax, heightfieldBBMin, heightfieldBBMax)) int z0 = (int)((tmin.Z - heightfieldBBMin.Z) * inverseCellSize);
{ int z1 = (int)((tmax.Z - heightfieldBBMin.Z) * inverseCellSize);
return true;
}
int w = heightfield.width; int w = heightfield.width;
int h = heightfield.height; 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 // use -1 rather than 0 to cut the polygon properly at the start of the tile
z0 = Math.Clamp(z0, -1, h - 1); z0 = Math.Clamp(z0, -1, h - 1);
z1 = Math.Clamp(z1, 0, h - 1); z1 = Math.Clamp(z1, 0, h - 1);
// Clip the triangle into all grid cells it touches. // Clip the triangle into all grid cells it touches.
Span<float> buf = stackalloc float[7 * 3 * 4]; float[] buf = new float[7 * 3 * 4];
int @in = 0; int @in = 0;
int inRow = 7 * 3; int inRow = 7 * 3;
int p1 = inRow + 7 * 3; int p1 = inRow + 7 * 3;
int p2 = p1 + 7 * 3; int p2 = p1 + 7 * 3;
RcVec.Copy(buf, 0, verts, v0 * 3); RcVecUtils.Copy(buf, 0, verts, v0 * 3);
RcVec.Copy(buf, 3, verts, v1 * 3); RcVecUtils.Copy(buf, 3, verts, v1 * 3);
RcVec.Copy(buf, 6, verts, v2 * 3); RcVecUtils.Copy(buf, 6, verts, v2 * 3);
int nvRow; int nvRow, nvIn = 3;
int nvIn = 3;
for (int z = z0; z <= z1; ++z) for (int z = z0; z <= z1; ++z)
{ {
// Clip polygon to row. Store the remaining polygon as well // Clip polygon to row. Store the remaining polygon as well
float cellZ = heightfieldBBMin.Z + z * cellSize; float cellZ = heightfieldBBMin.Z + z * cellSize;
DividePoly(buf, @in, nvIn, inRow, out nvRow, p1, out nvIn, cellZ + cellSize, RcAxis.RC_AXIS_Z); DividePoly(buf, @in, nvIn, inRow, out nvRow, p1, out nvIn, cellZ + cellSize, 2);
(@in, p1) = (p1, @in); (@in, p1) = (p1, @in);
if (nvRow < 3) if (nvRow < 3)
{
continue; continue;
}
if (z < 0) if (z < 0)
{ {
continue; continue;
} }
// find X-axis bounds of the row // find the horizontal bounds in the row
float minX = buf[inRow]; float minX = buf[inRow], maxX = buf[inRow];
float maxX = buf[inRow];
for (int i = 1; i < nvRow; ++i) for (int i = 1; i < nvRow; ++i)
{ {
float v = buf[inRow + i * 3]; float v = buf[inRow + i * 3];
@ -371,19 +304,16 @@ namespace DotRecast.Recast
x0 = Math.Clamp(x0, -1, w - 1); x0 = Math.Clamp(x0, -1, w - 1);
x1 = Math.Clamp(x1, 0, w - 1); x1 = Math.Clamp(x1, 0, w - 1);
int nv; int nv, nv2 = nvRow;
int nv2 = nvRow;
for (int x = x0; x <= x1; ++x) for (int x = x0; x <= x1; ++x)
{ {
// Clip polygon to column. store the remaining polygon as well // Clip polygon to column. store the remaining polygon as well
float cx = heightfieldBBMin.X + x * cellSize; float cx = heightfieldBBMin.X + x * cellSize;
DividePoly(buf, inRow, nv2, p1, out nv, p2, out nv2, cx + cellSize, RcAxis.RC_AXIS_X); DividePoly(buf, inRow, nv2, p1, out nv, p2, out nv2, cx + cellSize, 0);
(inRow, p2) = (p2, inRow); (inRow, p2) = (p2, inRow);
if (nv < 3) if (nv < 3)
{
continue; continue;
}
if (x < 0) if (x < 0)
{ {
@ -401,89 +331,80 @@ namespace DotRecast.Recast
spanMin -= heightfieldBBMin.Y; spanMin -= heightfieldBBMin.Y;
spanMax -= heightfieldBBMin.Y; spanMax -= heightfieldBBMin.Y;
// Skip the span if it is outside the heightfield bbox // Skip the span if it is outside the heightfield bbox
if (spanMax < 0.0f) if (spanMax < 0.0f)
{
continue; continue;
}
if (spanMin > by) if (spanMin > by)
{
continue; continue;
}
// Clamp the span to the heightfield bbox. // Clamp the span to the heightfield bbox.
if (spanMin < 0.0f) if (spanMin < 0.0f)
{
spanMin = 0; spanMin = 0;
}
if (spanMax > by) if (spanMax > by)
{
spanMax = by; spanMax = by;
}
// Snap the span to the heightfield height grid. // Snap the span to the heightfield height grid.
int spanMinCellIndex = Math.Clamp((int)MathF.Floor(spanMin * inverseCellHeight), 0, RC_SPAN_MAX_HEIGHT); int spanMinCellIndex = Math.Clamp((int)MathF.Floor(spanMin * inverseCellHeight), 0, SPAN_MAX_HEIGHT);
int spanMaxCellIndex = Math.Clamp((int)MathF.Ceiling(spanMax * inverseCellHeight), spanMinCellIndex + 1, RC_SPAN_MAX_HEIGHT); int spanMaxCellIndex = Math.Clamp((int)MathF.Ceiling(spanMax * inverseCellHeight), spanMinCellIndex + 1, SPAN_MAX_HEIGHT);
AddSpan(heightfield, x, z, spanMinCellIndex, spanMaxCellIndex, areaID, flagMergeThreshold); AddSpan(heightfield, x, z, spanMinCellIndex, spanMaxCellIndex, area, 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
/// Rasterizes a single triangle into the specified heightfield. * heightfield grid.
/// *
/// Calling this for each triangle in a mesh is less efficient than calling rcRasterizeTriangles * @param heightfield
/// * An initialized heightfield.
/// No spans will be added if the triangle does not overlap the heightfield grid. * @param verts
/// * An array with vertex coordinates [(x, y, z) * N]
/// @see rcHeightfield * @param v0
/// @ingroup recast * Index of triangle vertex 0, will be multiplied by 3 to get vertex coordinates
/// @param[in,out] context The build context to use during the operation. * @param v1
/// @param[in] v0 Triangle vertex 0 [(x, y, z)] * Triangle vertex 1 index
/// @param[in] v1 Triangle vertex 1 [(x, y, z)] * @param v2
/// @param[in] v2 Triangle vertex 2 [(x, y, z)] * Triangle vertex 2 index
/// @param[in] areaID The area id of the triangle. [Limit: <= #RC_WALKABLE_AREA] * @param areaId
/// @param[in,out] heightfield An initialized heightfield. * The area id of the triangle. [Limit: <= WALKABLE_AREA)
/// @param[in] flagMergeThreshold The distance where the walkable flag is favored over the non-walkable flag. * @param flagMergeThreshold
/// [Limit: >= 0] [Units: vx] * The distance where the walkable flag is favored over the non-walkable flag. [Limit: >= 0] [Units: vx]
/// @returns True if the operation completed successfully. * @see Heightfield
public static void RasterizeTriangle(RcContext context, float[] verts, int v0, int v1, int v2, int areaID, */
RcHeightfield heightfield, int flagMergeThreshold) public static void RasterizeTriangle(RcHeightfield heightfield, float[] verts, int v0, int v1, int v2, int area,
int flagMergeThreshold, RcTelemetry ctx)
{ {
using var timer = context.ScopedTimer(RcTimerLabel.RC_TIMER_RASTERIZE_TRIANGLES); using var timer = ctx.ScopedTimer(RcTimerLabel.RC_TIMER_RASTERIZE_TRIANGLES);
// Rasterize the single triangle.
float inverseCellSize = 1.0f / heightfield.cs; float inverseCellSize = 1.0f / heightfield.cs;
float inverseCellHeight = 1.0f / heightfield.ch; float inverseCellHeight = 1.0f / heightfield.ch;
RasterizeTri(verts, v0, v1, v2, areaID, heightfield, heightfield.bmin, heightfield.bmax, heightfield.cs, inverseCellSize, RasterizeTri(verts, v0, v1, v2, area, heightfield, heightfield.bmin, heightfield.bmax, heightfield.cs, inverseCellSize,
inverseCellHeight, flagMergeThreshold); inverseCellHeight, flagMergeThreshold);
} }
/// Rasterizes an indexed triangle mesh into the specified heightfield. /**
/// * Rasterizes an indexed triangle mesh into the specified heightfield. Spans will only be added for triangles that
/// Spans will only be added for triangles that overlap the heightfield grid. * overlap the heightfield grid.
/// *
/// @see rcHeightfield * @param heightfield
/// @ingroup recast * An initialized heightfield.
/// @param[in,out] context The build context to use during the operation. * @param verts
/// @param[in] verts The vertices. [(x, y, z) * @p nv] * The vertices. [(x, y, z) * N]
/// @param[in] numVerts The number of vertices. (unused) TODO (graham): Remove in next major release * @param tris
/// @param[in] tris The triangle indices. [(vertA, vertB, vertC) * @p nt] * The triangle indices. [(vertA, vertB, vertC) * nt]
/// @param[in] triAreaIDs The area id's of the triangles. [Limit: <= #RC_WALKABLE_AREA] [Size: @p nt] * @param areaIds
/// @param[in] numTris The number of triangles. * The area id's of the triangles. [Limit: <= WALKABLE_AREA] [Size: numTris]
/// @param[in,out] heightfield An initialized heightfield. * @param numTris
/// @param[in] flagMergeThreshold The distance where the walkable flag is favored over the non-walkable flag. * The number of triangles.
/// [Limit: >= 0] [Units: vx] * @param flagMergeThreshold
/// @returns True if the operation completed successfully. * The distance where the walkable flag is favored over the non-walkable flag. [Limit: >= 0] [Units: vx]
public static void RasterizeTriangles(RcContext context, float[] verts, int[] tris, int[] triAreaIDs, int numTris, * @see Heightfield
RcHeightfield heightfield, int flagMergeThreshold) */
public static void RasterizeTriangles(RcHeightfield heightfield, float[] verts, int[] tris, int[] areaIds, int numTris,
int flagMergeThreshold, RcTelemetry ctx)
{ {
using var timer = context.ScopedTimer(RcTimerLabel.RC_TIMER_RASTERIZE_TRIANGLES); using var timer = ctx.ScopedTimer(RcTimerLabel.RC_TIMER_RASTERIZE_TRIANGLES);
float inverseCellSize = 1.0f / heightfield.cs; float inverseCellSize = 1.0f / heightfield.cs;
float inverseCellHeight = 1.0f / heightfield.ch; float inverseCellHeight = 1.0f / heightfield.ch;
@ -492,30 +413,33 @@ namespace DotRecast.Recast
int v0 = tris[triIndex * 3 + 0]; int v0 = tris[triIndex * 3 + 0];
int v1 = tris[triIndex * 3 + 1]; int v1 = tris[triIndex * 3 + 1];
int v2 = tris[triIndex * 3 + 2]; int v2 = tris[triIndex * 3 + 2];
RasterizeTri(verts, v0, v1, v2, triAreaIDs[triIndex], heightfield, heightfield.bmin, heightfield.bmax, heightfield.cs, RasterizeTri(verts, v0, v1, v2, areaIds[triIndex], heightfield, heightfield.bmin, heightfield.bmax, heightfield.cs,
inverseCellSize, inverseCellHeight, flagMergeThreshold); inverseCellSize, inverseCellHeight, flagMergeThreshold);
} }
} }
/// Rasterizes a triangle list into the specified heightfield. /**
/// * Rasterizes a triangle list into the specified heightfield. Expects each triangle to be specified as three
/// Expects each triangle to be specified as three sequential vertices of 3 floats. * sequential vertices of 3 floats. Spans will only be added for triangles that overlap the heightfield grid.
/// *
/// Spans will only be added for triangles that overlap the heightfield grid. * @param heightfield
/// * An initialized heightfield.
/// @see rcHeightfield * @param verts
/// @ingroup recast * The vertices. [(x, y, z) * numVerts]
/// @param[in,out] context The build context to use during the operation. * @param areaIds
/// @param[in] verts The triangle vertices. [(ax, ay, az, bx, by, bz, cx, by, cx) * @p nt] * The area id's of the triangles. [Limit: <= WALKABLE_AREA] [Size: numTris]
/// @param[in] triAreaIDs The area id's of the triangles. [Limit: <= #RC_WALKABLE_AREA] [Size: @p nt] * @param tris
/// @param[in] numTris The number of triangles. * The triangle indices. [(vertA, vertB, vertC) * nt]
/// @param[in,out] heightfield An initialized heightfield. * @param numTris
/// @param[in] flagMergeThreshold The distance where the walkable flag is favored over the non-walkable flag. * The number of triangles.
/// [Limit: >= 0] [Units: vx] * @param flagMergeThreshold
/// @returns True if the operation completed successfully. * The distance where the walkable flag is favored over the non-walkable flag. [Limit: >= 0] [Units: vx]
public static void RasterizeTriangles(RcContext context, float[] verts, int[] triAreaIDs, int numTris, RcHeightfield heightfield, int flagMergeThreshold) * @see Heightfield
*/
public static void RasterizeTriangles(RcHeightfield heightfield, float[] verts, int[] areaIds, int numTris,
int flagMergeThreshold, RcTelemetry ctx)
{ {
using var timer = context.ScopedTimer(RcTimerLabel.RC_TIMER_RASTERIZE_TRIANGLES); using var timer = ctx.ScopedTimer(RcTimerLabel.RC_TIMER_RASTERIZE_TRIANGLES);
float inverseCellSize = 1.0f / heightfield.cs; float inverseCellSize = 1.0f / heightfield.cs;
float inverseCellHeight = 1.0f / heightfield.ch; float inverseCellHeight = 1.0f / heightfield.ch;
@ -524,7 +448,7 @@ namespace DotRecast.Recast
int v0 = (triIndex * 3 + 0); int v0 = (triIndex * 3 + 0);
int v1 = (triIndex * 3 + 1); int v1 = (triIndex * 3 + 1);
int v2 = (triIndex * 3 + 2); int v2 = (triIndex * 3 + 2);
RasterizeTri(verts, v0, v1, v2, triAreaIDs[triIndex], heightfield, heightfield.bmin, heightfield.bmax, heightfield.cs, RasterizeTri(verts, v0, v1, v2, areaIds[triIndex], heightfield, heightfield.bmin, heightfield.bmax, heightfield.cs,
inverseCellSize, inverseCellHeight, flagMergeThreshold); inverseCellSize, inverseCellHeight, flagMergeThreshold);
} }
} }

View File

@ -1,4 +1,4 @@
using System.Collections.Generic; using System.Collections.Generic;
namespace DotRecast.Recast namespace DotRecast.Recast
{ {

File diff suppressed because it is too large Load Diff

View File

@ -1,7 +1,7 @@
/* /*
Copyright (c) 2009-2010 Mikko Mononen memon@inside.org Copyright (c) 2009-2010 Mikko Mononen memon@inside.org
recast4j copyright (c) 2015-2019 Piotr Piastucki piotr@jtilia.org recast4j copyright (c) 2015-2019 Piotr Piastucki piotr@jtilia.org
DotRecast Copyright (c) 2023-2024 Choi Ikpil ikpil@naver.com DotRecast Copyright (c) 2023 Choi Ikpil ikpil@naver.com
This software is provided 'as-is', without any express or implied This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages warranty. In no event will the authors be held liable for any damages
@ -20,13 +20,19 @@ freely, subject to the following restrictions:
namespace DotRecast.Recast namespace DotRecast.Recast
{ {
/// Represents a span in a heightfield. /** Represents a span in a heightfield. */
/// @see rcHeightfield
public class RcSpan public class RcSpan
{ {
public int smin; //< The lower limit of the span. [Limit: < #smax] /** The lower limit of the span. [Limit: &lt; smax] */
public int smax; //< The upper limit of the span. [Limit: <= #RC_SPAN_MAX_HEIGHT] public int smin;
public int area; //< The area id assigned to the span.
public RcSpan next; //< The next span higher up in column. /** The upper limit of the span. [Limit: &lt;= SPAN_MAX_HEIGHT] */
public int smax;
/** The area id assigned to the span. */
public int area;
/** The next span higher up in column. */
public RcSpan next;
} }
} }

View File

@ -1,38 +0,0 @@
/*
Copyright (c) 2009-2010 Mikko Mononen memon@inside.org
DotRecast Copyright (c) 2023-2024 Choi Ikpil ikpil@naver.com
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
*/
namespace DotRecast.Recast
{
/// A memory pool used for quick allocation of spans within a heightfield.
/// @see rcHeightfield
public class RcSpanPool
{
public RcSpanPool next; //< The next span pool.
public readonly RcSpan[] items; //< Array of spans in the pool.
public RcSpanPool()
{
items = new RcSpan[RcRecast.RC_SPANS_PER_POOL];
for (int i = 0; i < items.Length; ++i)
{
items[i] = new RcSpan();
}
}
}
}

View File

@ -1,6 +1,6 @@
namespace DotRecast.Recast namespace DotRecast.Recast
{ {
public class RcSweepSpan public struct RcSweepSpan
{ {
public int rid; // row id public int rid; // row id
public int id; // region id public int id; // region id

View File

@ -1,6 +1,6 @@
/* /*
recast4j copyright (c) 2021 Piotr Piastucki piotr@jtilia.org recast4j copyright (c) 2021 Piotr Piastucki piotr@jtilia.org
DotRecast Copyright (c) 2023-2024 Choi Ikpil ikpil@naver.com DotRecast Copyright (c) 2023 Choi Ikpil ikpil@naver.com
This software is provided 'as-is', without any express or implied This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages warranty. In no event will the authors be held liable for any damages
@ -19,14 +19,13 @@ freely, subject to the following restrictions:
using System.Collections.Generic; using System.Collections.Generic;
using DotRecast.Core; using DotRecast.Core;
using DotRecast.Core.Numerics;
using DotRecast.Recast.Geom; using DotRecast.Recast.Geom;
namespace DotRecast.Recast namespace DotRecast.Recast
{ {
public static class RcVoxelizations public static class RcVoxelizations
{ {
public static RcHeightfield BuildSolidHeightfield(RcContext ctx, IInputGeomProvider geomProvider, RcBuilderConfig builderCfg) public static RcHeightfield BuildSolidHeightfield(IInputGeomProvider geomProvider, RcBuilderConfig builderCfg, RcTelemetry ctx)
{ {
RcConfig cfg = builderCfg.cfg; RcConfig cfg = builderCfg.cfg;
@ -35,37 +34,40 @@ namespace DotRecast.Recast
// Allocate array that can hold triangle area types. // Allocate array that can hold triangle area types.
// If you have multiple meshes you need to process, allocate // 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. // Find triangles which are walkable based on their slope and rasterize
// If your input data is multiple meshes, you can transform them here, calculate // 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. // the are type for each of the meshes and rasterize them.
foreach (RcTriMesh geom in geomProvider.Meshes()) foreach (RcTriMesh geom in geomProvider.Meshes())
{ {
float[] verts = geom.GetVerts(); float[] verts = geom.GetVerts();
if (cfg.UseTiles) if (cfg.UseTiles)
{ {
RcVec2f tbmin; float[] tbmin = new float[2];
RcVec2f tbmax; float[] tbmax = new float[2];
tbmin.X = builderCfg.bmin.X; tbmin[0] = builderCfg.bmin.X;
tbmin.Y = builderCfg.bmin.Z; tbmin[1] = builderCfg.bmin.Z;
tbmax.X = builderCfg.bmax.X; tbmax[0] = builderCfg.bmax.X;
tbmax.Y = builderCfg.bmax.Z; tbmax[1] = builderCfg.bmax.Z;
List<RcChunkyTriMeshNode> nodes = geom.GetChunksOverlappingRect(tbmin, tbmax); List<RcChunkyTriMeshNode> nodes = geom.GetChunksOverlappingRect(tbmin, tbmax);
foreach (RcChunkyTriMeshNode node in nodes) foreach (RcChunkyTriMeshNode node in nodes)
{ {
int[] tris = node.tris; int[] tris = node.tris;
int ntris = tris.Length / 3; int ntris = tris.Length / 3;
int[] m_triareas = RcRecast.MarkWalkableTriangles(ctx, cfg.WalkableSlopeAngle, verts, tris, ntris, cfg.WalkableAreaMod); int[] m_triareas = RcCommons.MarkWalkableTriangles(ctx, cfg.WalkableSlopeAngle, verts, tris, ntris, cfg.WalkableAreaMod);
RcRasterizations.RasterizeTriangles(ctx, verts, tris, m_triareas, ntris, solid, cfg.WalkableClimb); RcRasterizations.RasterizeTriangles(solid, verts, tris, m_triareas, ntris, cfg.WalkableClimb, ctx);
} }
} }
else else
{ {
int[] tris = geom.GetTris(); int[] tris = geom.GetTris();
int ntris = tris.Length / 3; int ntris = tris.Length / 3;
int[] m_triareas = RcRecast.MarkWalkableTriangles(ctx, cfg.WalkableSlopeAngle, verts, tris, ntris, cfg.WalkableAreaMod); int[] m_triareas = RcCommons.MarkWalkableTriangles(ctx, cfg.WalkableSlopeAngle, verts, tris, ntris, cfg.WalkableAreaMod);
RcRasterizations.RasterizeTriangles(ctx, verts, tris, m_triareas, ntris, solid, cfg.WalkableClimb); RcRasterizations.RasterizeTriangles(solid, verts, tris, m_triareas, ntris, cfg.WalkableClimb, ctx);
} }
} }

View File

@ -1,5 +0,0 @@
{
"name": "com.rnd.dotrecast",
"displayName": "DotRecast",
"version": "0.4.1"
}

View File

@ -1,22 +0,0 @@
using System.Collections.Immutable;
using System.Linq;
using BenchmarkDotNet.Running;
using DotRecast.Benchmark.Benchmarks;
namespace DotRecast.Benchmark;
public static class BenchmarkProgram
{
public static int Main(string[] args)
{
var runs = ImmutableArray.Create(
BenchmarkConverter.TypeToBenchmarks(typeof(VectorBenchmarks)),
BenchmarkConverter.TypeToBenchmarks(typeof(PriorityQueueBenchmarks)),
BenchmarkConverter.TypeToBenchmarks(typeof(StackallocBenchmarks))
);
var summary = BenchmarkRunner.Run(runs.ToArray());
return 0;
}
}

View File

@ -1,278 +0,0 @@
using System;
using System.Collections.Generic;
using BenchmarkDotNet.Attributes;
using DotRecast.Core.Collections;
namespace DotRecast.Benchmark.Benchmarks;
/*
// * Summary *
BenchmarkDotNet v0.13.12, Windows 11 (10.0.22631.3958/23H2/2023Update/SunValley3)
AMD Ryzen 5 3600, 1 CPU, 12 logical and 6 physical cores
.NET SDK 8.0.101
[Host] : .NET 8.0.1 (8.0.123.58001), X64 RyuJIT AVX2
DefaultJob : .NET 8.0.1 (8.0.123.58001), X64 RyuJIT AVX2
| Method | Count | Mean | Error | StdDev |
|------------------------------- |------ |--------------------:|-----------------:|-----------------:|
| Enqueue_RcSortedQueue | 10 | 87.49 ns | 0.774 ns | 0.724 ns |
| Enqueue_RcBinaryMinHeap | 10 | 185.23 ns | 1.730 ns | 1.533 ns |
| Enqueue_PriorityQueue | 10 | 202.95 ns | 1.611 ns | 1.428 ns |
| DequeueAll_RcSortedQueue | 10 | 460.97 ns | 2.169 ns | 2.029 ns |
| DequeueAll_RcBinaryMinHeap | 10 | 573.17 ns | 2.542 ns | 2.378 ns |
| DequeueAll_PriorityQueue | 10 | 500.68 ns | 2.364 ns | 2.212 ns |
| EnqueueDequeue_RcSortedQueue | 10 | 525.43 ns | 1.842 ns | 1.632 ns |
| EnqueueDequeue_RcBinaryMinHeap | 10 | 455.65 ns | 2.410 ns | 2.254 ns |
| EnqueueDequeue_PriorityQueue | 10 | 381.82 ns | 6.036 ns | 5.646 ns |
| Enqueue_RcSortedQueue | 100 | 730.57 ns | 5.229 ns | 4.635 ns |
| Enqueue_RcBinaryMinHeap | 100 | 3,012.15 ns | 10.875 ns | 9.640 ns |
| Enqueue_PriorityQueue | 100 | 2,306.80 ns | 26.694 ns | 23.663 ns |
| DequeueAll_RcSortedQueue | 100 | 6,241.67 ns | 31.856 ns | 29.798 ns |
| DequeueAll_RcBinaryMinHeap | 100 | 13,692.29 ns | 38.829 ns | 34.421 ns |
| DequeueAll_PriorityQueue | 100 | 12,482.93 ns | 93.955 ns | 87.886 ns |
| EnqueueDequeue_RcSortedQueue | 100 | 64,002.79 ns | 316.081 ns | 280.197 ns |
| EnqueueDequeue_RcBinaryMinHeap | 100 | 8,655.79 ns | 23.703 ns | 22.172 ns |
| EnqueueDequeue_PriorityQueue | 100 | 7,806.20 ns | 105.801 ns | 98.967 ns |
| Enqueue_RcSortedQueue | 1000 | 7,566.23 ns | 149.010 ns | 218.418 ns |
| Enqueue_RcBinaryMinHeap | 1000 | 36,277.43 ns | 96.710 ns | 90.462 ns |
| Enqueue_PriorityQueue | 1000 | 28,564.19 ns | 186.866 ns | 174.795 ns |
| DequeueAll_RcSortedQueue | 1000 | 108,574.26 ns | 745.459 ns | 697.303 ns |
| DequeueAll_RcBinaryMinHeap | 1000 | 210,346.25 ns | 332.478 ns | 311.000 ns |
| DequeueAll_PriorityQueue | 1000 | 189,536.32 ns | 1,180.045 ns | 1,046.079 ns |
| EnqueueDequeue_RcSortedQueue | 1000 | 8,957,965.42 ns | 45,715.567 ns | 42,762.370 ns |
| EnqueueDequeue_RcBinaryMinHeap | 1000 | 131,615.02 ns | 394.216 ns | 368.750 ns |
| EnqueueDequeue_PriorityQueue | 1000 | 114,799.89 ns | 1,269.621 ns | 1,060.191 ns |
| Enqueue_RcSortedQueue | 10000 | 77,143.76 ns | 996.372 ns | 932.007 ns |
| Enqueue_RcBinaryMinHeap | 10000 | 417,620.57 ns | 853.343 ns | 756.466 ns |
| Enqueue_PriorityQueue | 10000 | 278,791.68 ns | 1,566.093 ns | 1,464.924 ns |
| DequeueAll_RcSortedQueue | 10000 | 1,435,539.99 ns | 9,329.910 ns | 8,727.204 ns |
| DequeueAll_RcBinaryMinHeap | 10000 | 2,956,366.90 ns | 6,344.030 ns | 5,934.210 ns |
| DequeueAll_PriorityQueue | 10000 | 2,642,186.54 ns | 9,482.374 ns | 8,869.819 ns |
| EnqueueDequeue_RcSortedQueue | 10000 | 1,318,277,320.00 ns | 6,725,701.525 ns | 6,291,225.379 ns |
| EnqueueDequeue_RcBinaryMinHeap | 10000 | 1,712,170.68 ns | 5,674.513 ns | 5,307.943 ns |
| EnqueueDequeue_PriorityQueue | 10000 | 1,466,910.77 ns | 4,394.686 ns | 4,110.792 ns |
*/
public class PriorityQueueBenchmarks
{
[Params(10, 100, 1000, 10000)] public int Count;
private RcSortedQueue<Node> _sq;
private RcBinaryMinHeap<Node> _bmHeap;
private PriorityQueue<Node, Node> _pq;
private float[] _priority;
class Node
{
public int id;
public float total;
}
[GlobalSetup]
public void Setup()
{
static int Comp(Node x, Node y)
{
var v = x.total.CompareTo(y.total);
if (v != 0)
return v;
return x.id.CompareTo(y.id);
}
_sq = new(Comp);
_bmHeap = new(Count, Comp);
_pq = new(Count, Comparer<Node>.Create(Comp));
_priority = new float[Count];
for (int i = 0; i < Count; i++)
{
_priority[i] = (float)Random.Shared.NextDouble() * 100f;
}
}
[Benchmark]
public void Enqueue_RcSortedQueue()
{
_sq.Clear();
for (int i = 0; i < Count; i++)
{
_sq.Enqueue(new Node
{
id = i,
total = _priority[i],
});
}
}
[Benchmark]
public void Enqueue_RcBinaryMinHeap()
{
_bmHeap.Clear();
for (int i = 0; i < Count; i++)
{
_bmHeap.Push(new Node
{
id = i,
total = _priority[i],
});
}
}
[Benchmark]
public void Enqueue_PriorityQueue()
{
_pq.Clear();
for (int i = 0; i < Count; i++)
{
var node = new Node
{
id = i,
total = _priority[i],
};
_pq.Enqueue(node, node);
}
}
[Benchmark]
public void DequeueAll_RcSortedQueue()
{
_sq.Clear();
for (int i = 0; i < Count; i++)
{
_sq.Enqueue(new Node
{
id = i,
total = _priority[i],
});
}
while (_sq.Count() > 0)
{
_sq.Dequeue();
}
}
[Benchmark]
public void DequeueAll_RcBinaryMinHeap()
{
_bmHeap.Clear();
for (int i = 0; i < Count; i++)
{
_bmHeap.Push(new Node
{
id = i,
total = _priority[i],
});
}
while (_bmHeap.Count > 0)
{
_bmHeap.Pop();
}
}
[Benchmark]
public void DequeueAll_PriorityQueue()
{
_pq.Clear();
for (int i = 0; i < Count; i++)
{
var node = new Node
{
id = i,
total = _priority[i],
};
_pq.Enqueue(node, node);
}
while (_pq.Count > 0)
{
_pq.Dequeue();
}
}
[Benchmark]
public void EnqueueDequeue_RcSortedQueue()
{
_sq.Clear();
int half = Count / 2;
for (int i = 0; i < half; i++)
{
_sq.Enqueue(new Node
{
id = i,
total = _priority[i],
});
}
for (int i = half; i < Count; i++)
{
_sq.Enqueue(new Node
{
id = i,
total = _priority[i],
});
_sq.Dequeue();
}
}
[Benchmark]
public void EnqueueDequeue_RcBinaryMinHeap()
{
_bmHeap.Clear();
int half = Count / 2;
for (int i = 0; i < half; i++)
{
_bmHeap.Push(new Node
{
id = i,
total = _priority[i],
});
}
for (int i = half; i < Count; i++)
{
_bmHeap.Push(new Node
{
id = i,
total = _priority[i],
});
_bmHeap.Pop();
}
}
[Benchmark]
public void EnqueueDequeue_PriorityQueue()
{
_pq.Clear();
int half = Count / 2;
for (int i = 0; i < half; i++)
{
var node = new Node
{
id = i,
total = _priority[i],
};
_pq.Enqueue(node, node);
}
for (int i = half; i < Count; i++)
{
var node = new Node
{
id = i,
total = _priority[i],
};
_pq.Enqueue(node, node);
_pq.Dequeue();
}
}
}

View File

@ -1,71 +0,0 @@
using System;
using System.Runtime.CompilerServices;
using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Engines;
namespace DotRecast.Benchmark.Benchmarks;
/*
| Method | TestArraySize | Mean | Error | StdDev | Median |
|------------------------------- |-------------- |-------------:|-----------:|------------:|-------------:|
| Stackalloc_Long | 16 | 3.016 ns | 0.0179 ns | 0.0149 ns | 3.017 ns |
| Stackalloc_Long_SkipLocalsInit | 16 | 2.265 ns | 0.0197 ns | 0.0184 ns | 2.258 ns |
| New_Long | 16 | 5.917 ns | 0.1964 ns | 0.5634 ns | 5.761 ns |
| New_Long_SkipLocalsInit | 16 | 5.703 ns | 0.1371 ns | 0.3935 ns | 5.661 ns |
| Stackalloc_Long | 256 | 39.418 ns | 0.2737 ns | 0.2285 ns | 39.410 ns |
| Stackalloc_Long_SkipLocalsInit | 256 | 2.274 ns | 0.0147 ns | 0.0131 ns | 2.274 ns |
| New_Long | 256 | 53.901 ns | 2.9999 ns | 8.4614 ns | 51.449 ns |
| New_Long_SkipLocalsInit | 256 | 53.480 ns | 1.8716 ns | 5.4298 ns | 51.858 ns |
| Stackalloc_Long | 1024 | 137.037 ns | 0.3652 ns | 0.3416 ns | 137.031 ns |
| Stackalloc_Long_SkipLocalsInit | 1024 | 3.669 ns | 0.0254 ns | 0.0226 ns | 3.668 ns |
| New_Long | 1024 | 197.324 ns | 9.2795 ns | 27.0687 ns | 186.588 ns |
| New_Long_SkipLocalsInit | 1024 | 210.996 ns | 10.0255 ns | 27.9471 ns | 206.110 ns |
| Stackalloc_Long | 8192 | 1,897.989 ns | 7.1814 ns | 5.9968 ns | 1,897.814 ns |
| Stackalloc_Long_SkipLocalsInit | 8192 | 20.598 ns | 0.2645 ns | 0.2344 ns | 20.572 ns |
| New_Long | 8192 | 1,324.061 ns | 39.8447 ns | 116.2288 ns | 1,298.794 ns |
| New_Long_SkipLocalsInit | 8192 | 1,305.211 ns | 35.1855 ns | 102.0796 ns | 1,295.539 ns |
*/
public class StackallocBenchmarks
{
private readonly Consumer _consumer = new();
[Params(1 << 4, 1 << 8, 1 << 10, 1 << 13)]
public int HashTableSize;
[Benchmark]
public void Stackalloc_Long()
{
Span<long> hashTable = stackalloc long[HashTableSize];
_consumer.Consume(hashTable[0]);
}
[Benchmark]
[SkipLocalsInit]
public void Stackalloc_Long_SkipLocalsInit()
{
Span<long> hashTable = stackalloc long[HashTableSize];
_consumer.Consume(hashTable[0]);
}
[Benchmark]
public void New_Long()
{
Span<long> hashTable = new long[HashTableSize];
_consumer.Consume(hashTable[0]);
}
[Benchmark]
[SkipLocalsInit]
public void New_Long_SkipLocalsInit()
{
Span<long> hashTable = new long[HashTableSize];
_consumer.Consume(hashTable[0]);
}
}

View File

@ -1,73 +0,0 @@
using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Engines;
using DotRecast.Core.Numerics;
namespace DotRecast.Benchmark.Benchmarks;
/*
| Method | Mean | Error | StdDev |
|------------------ |----------:|----------:|----------:|
| Dot_Vector3 | 0.6395 ns | 0.0125 ns | 0.0104 ns |
| Dot_RcVec3f | 0.2275 ns | 0.0281 ns | 0.0375 ns |
| Cross_Vector3 | 1.1652 ns | 0.0102 ns | 0.0085 ns |
| Cross_RcVec3f | 1.1687 ns | 0.0140 ns | 0.0124 ns |
| Normalize_Vector3 | 1.7964 ns | 0.0173 ns | 0.0162 ns |
| Normalize_RcVec3f | 1.2806 ns | 0.0088 ns | 0.0078 ns |
*/
public class VectorBenchmarks
{
private readonly Consumer _consumer = new();
[Benchmark]
public void Dot_Vector3()
{
var v1 = new System.Numerics.Vector3(1, 2, 3);
var v2 = new System.Numerics.Vector3(1, 2, 3);
var v = System.Numerics.Vector3.Dot(v1, v2);
_consumer.Consume(v);
}
[Benchmark]
public void Dot_RcVec3f()
{
var v1 = new RcVec3f(1, 2, 3);
var v2 = new RcVec3f(1, 2, 3);
var v = RcVec3f.Dot(v1, v2);
_consumer.Consume(v);
}
[Benchmark]
public void Cross_Vector3()
{
var v1 = new System.Numerics.Vector3(1, 2, 3);
var v2 = new System.Numerics.Vector3(1, 2, 3);
var v = System.Numerics.Vector3.Cross(v1, v2);
_consumer.Consume(v);
}
[Benchmark]
public void Cross_RcVec3f()
{
var v1 = new RcVec3f(1, 2, 3);
var v2 = new RcVec3f(1, 2, 3);
var v = RcVec3f.Cross(v1, v2);
_consumer.Consume(v);
}
[Benchmark]
public void Normalize_Vector3()
{
var v1 = new System.Numerics.Vector3(1, 2, 3);
var v = System.Numerics.Vector3.Normalize(v1);
_consumer.Consume(v);
}
[Benchmark]
public void Normalize_RcVec3f()
{
var v1 = new RcVec3f(1, 2, 3);
var v = RcVec3f.Normalize(v1);
_consumer.Consume(v);
}
}

View File

@ -1,18 +0,0 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFrameworks>net6.0;net7.0;net8.0</TargetFrameworks>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<IsPackable>false</IsPackable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="BenchmarkDotNet" Version="0.14.0" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\src\DotRecast.Core\DotRecast.Core.csproj" />
</ItemGroup>
</Project>

View File

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

View File

@ -1,157 +0,0 @@
using System;
using System.Buffers;
using System.Collections.Generic;
using System.Net.Sockets;
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 results = new List<(string title, long ticks)>();
results.Add(Bench("new int[len]", RoundForArray));
results.Add(Bench("ArrayPool<int>.Shared.Rent(len)", RoundForPureRentArray));
results.Add(Bench("RcRentedArray.Rent<int>(len)", RoundForRcRentedArray));
results.Add(Bench("new RcStackArray512<int>()", RoundForRcStackArray));
results.Add(Bench("stackalloc int[len]", RoundForStackalloc));
results.Sort((x, y) => x.ticks.CompareTo(y.ticks));
foreach (var t in results)
{
Console.WriteLine($"{t.title} {t.ticks / (double)TimeSpan.TicksPerMillisecond} ms");
}
}
[Test]
public void TestSpanVsArray()
{
var r = new RcRand();
var list = new List<(long[] src, long[] dest)>();
for (int i = 0; i < 14; ++i)
{
var s = new long[(int)Math.Pow(2, i + 1)];
var d = new long[(int)Math.Pow(2, i + 1)];
for (int ii = 0; ii < s.Length; ++ii)
{
s[ii] = r.NextInt32();
}
list.Add((s, d));
}
var results = new List<(string title, long ticks)>();
for (int i = 0; i < list.Count; ++i)
{
var seq = i;
Array.Fill(list[seq].dest, 0);
var resultLong = Bench($"long[{list[seq].src.Length}]", _ =>
{
var v = list[seq];
RcArrays.Copy(v.src, 0, v.dest, 0, v.src.Length);
});
Array.Fill(list[seq].dest, 0);
var resultSpan = Bench($"Span<long[], {list[seq].src.Length}>", _ =>
{
var v = list[seq];
RcSpans.Copy<long>(v.src, 0, v.dest, 0, v.src.Length);
});
results.Add(resultLong);
results.Add(resultSpan);
}
int newLine = 0;
foreach (var t in results)
{
Console.WriteLine($"{t.title}: {t.ticks / (double)TimeSpan.TicksPerMillisecond} ms");
newLine += 1;
if (0 == (newLine % 2))
{
Console.WriteLine("");
}
}
}
}

View File

@ -1,147 +0,0 @@
using DotRecast.Core.Collections;
using NUnit.Framework;
namespace DotRecast.Core.Test;
public class RcBinaryMinHeapTest
{
private static readonly RcAtomicLong Gen = new();
private class Node
{
public readonly long Id;
public long Value;
public Node(int value)
{
Id = Gen.IncrementAndGet();
Value = value;
}
}
[Test]
public void TestPush()
{
var minHeap = new RcBinaryMinHeap<Node>((x, y) => x.Value.CompareTo(y.Value));
minHeap.Push(new Node(5));
minHeap.Push(new Node(3));
minHeap.Push(new Node(7));
minHeap.Push(new Node(2));
minHeap.Push(new Node(4));
// Push 후 힙의 속성을 검증
AssertHeapProperty(minHeap.ToArray());
}
[Test]
public void TestPop()
{
var minHeap = new RcBinaryMinHeap<Node>((x, y) => x.Value.CompareTo(y.Value));
minHeap.Push(new Node(5));
minHeap.Push(new Node(3));
minHeap.Push(new Node(7));
minHeap.Push(new Node(2));
minHeap.Push(new Node(4));
// Pop을 통해 최소 값부터 순서대로 제거하면서 검증
Assert.That(minHeap.Pop().Value, Is.EqualTo(2));
Assert.That(minHeap.Pop().Value, Is.EqualTo(3));
Assert.That(minHeap.Pop().Value, Is.EqualTo(4));
Assert.That(minHeap.Pop().Value, Is.EqualTo(5));
Assert.That(minHeap.Pop().Value, Is.EqualTo(7));
// 모든 요소를 Pop한 후에는 비어있어야 함
Assert.That(minHeap.IsEmpty(), Is.True);
}
[Test]
public void TestTop()
{
var minHeap = new RcBinaryMinHeap<Node>((x, y) => x.Value.CompareTo(y.Value));
minHeap.Push(new Node(5));
minHeap.Push(new Node(3));
minHeap.Push(new Node(7));
Assert.That(minHeap.Top().Value, Is.EqualTo(3));
AssertHeapProperty(minHeap.ToArray());
}
[Test]
public void TestModify()
{
var minHeap = new RcBinaryMinHeap<Node>((x, y) => x.Value.CompareTo(y.Value));
var node7 = new Node(7);
minHeap.Push(new Node(5));
minHeap.Push(new Node(3));
minHeap.Push(node7);
minHeap.Push(new Node(2));
minHeap.Push(new Node(4));
node7.Value = 1;
var result = minHeap.Modify(node7); // Modify value 7 to 1
var result2 = minHeap.Modify(new Node(4));
Assert.That(result, Is.EqualTo(true));
Assert.That(result2, Is.EqualTo(false));
Assert.That(minHeap.Top().Value, Is.EqualTo(1));
AssertHeapProperty(minHeap.ToArray());
}
[Test]
public void TestCount()
{
var minHeap = new RcBinaryMinHeap<Node>((x, y) => x.Value.CompareTo(y.Value));
minHeap.Push(new Node(5));
minHeap.Push(new Node(3));
minHeap.Push(new Node(7));
Assert.That(minHeap.Count, Is.EqualTo(3));
minHeap.Pop();
Assert.That(minHeap.Count, Is.EqualTo(2));
minHeap.Clear();
Assert.That(minHeap.Count, Is.EqualTo(0));
}
[Test]
public void TestIsEmpty()
{
var minHeap = new RcBinaryMinHeap<Node>((x, y) => x.Value.CompareTo(y.Value));
Assert.That(minHeap.IsEmpty(), Is.True);
minHeap.Push(new Node(5));
Assert.That(minHeap.IsEmpty(), Is.False);
minHeap.Pop();
Assert.That(minHeap.IsEmpty(), Is.True);
}
private void AssertHeapProperty(Node[] array)
{
for (int i = 0; i < array.Length / 2; i++)
{
int leftChildIndex = 2 * i + 1;
int rightChildIndex = 2 * i + 2;
// 왼쪽 자식 노드가 있는지 확인하고 비교
if (leftChildIndex < array.Length)
Assert.That(array[i].Value, Is.LessThanOrEqualTo(array[leftChildIndex].Value));
// 오른쪽 자식 노드가 있는지 확인하고 비교
if (rightChildIndex < array.Length)
Assert.That(array[i].Value, Is.LessThanOrEqualTo(array[rightChildIndex].Value));
}
}
}

View File

@ -1,449 +0,0 @@
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

@ -1,40 +0,0 @@
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

@ -1,38 +0,0 @@
using System;
using System.IO;
using NUnit.Framework;
namespace DotRecast.Core.Test;
public class RcIoTests
{
[Test]
public void Test()
{
const long tileRef = 281474976710656L;
const int dataSize = 344;
byte[] actual;
{
using MemoryStream ms = new MemoryStream();
using BinaryWriter bw = new BinaryWriter(ms);
RcIO.Write(bw, tileRef, RcByteOrder.LITTLE_ENDIAN);
RcIO.Write(bw, dataSize, RcByteOrder.LITTLE_ENDIAN);
bw.Flush();
actual= ms.ToArray();
}
{
using MemoryStream ms = new MemoryStream(actual);
using BinaryReader br = new BinaryReader(ms);
var byteBuffer = RcIO.ToByteBuffer(br);
byteBuffer.Order(RcByteOrder.LITTLE_ENDIAN);
Assert.That(byteBuffer.GetLong(), Is.EqualTo(tileRef));
Assert.That(byteBuffer.GetInt(), Is.EqualTo(dataSize));
}
}
}

View File

@ -1,38 +0,0 @@
using System;
using NUnit.Framework;
namespace DotRecast.Core.Test;
public class RcMathTest
{
[Test]
public void TestSqr()
{
Assert.That(RcMath.Sqr(0), Is.EqualTo(0));
Assert.That(RcMath.Sqr(5), Is.EqualTo(25));
Assert.That(RcMath.Sqr(-5), Is.EqualTo(25));
Assert.That(RcMath.Sqr(float.PositiveInfinity), Is.EqualTo(float.PositiveInfinity));
Assert.That(RcMath.Sqr(float.NegativeInfinity), Is.EqualTo(float.PositiveInfinity));
Assert.That(RcMath.Sqr(float.NaN), Is.EqualTo(float.NaN));
}
[Test]
public void TestLerp()
{
//
Assert.That(RcMath.Lerp(-10, 10, 2f), Is.EqualTo(30));
Assert.That(RcMath.Lerp(-10, 10, 1f), Is.EqualTo(10));
Assert.That(RcMath.Lerp(-10, 10, 0.5f), Is.EqualTo(0));
Assert.That(RcMath.Lerp(-10, 10, 0.25f), Is.EqualTo(-5));
Assert.That(RcMath.Lerp(-10, 10, 0), Is.EqualTo(-10));
Assert.That(RcMath.Lerp(-10, 10, -0.5f), Is.EqualTo(-20));
Assert.That(RcMath.Lerp(-10, 10, -1f), Is.EqualTo(-30));
//
Assert.That(RcMath.Lerp(10, 10, 0.5f), Is.EqualTo(10));
Assert.That(RcMath.Lerp(10, 10, 0.8f), Is.EqualTo(10));
//
Assert.That(RcMath.Lerp(10, -10, 0.75f), Is.EqualTo(-5));
}
}

View File

@ -1,106 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
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);
}
}
[Test]
public void TestSame()
{
// not same
{
using var r1 = RcRentedArray.Rent<float>(1024);
using var r2 = RcRentedArray.Rent<float>(1024);
Assert.That(r2.AsArray() != r1.AsArray(), Is.EqualTo(true));
}
// same
{
// error case
float[] r1Array;
using (var r1 = RcRentedArray.Rent<float>(1024))
{
r1Array = r1.AsArray();
for (int i = 0; i < r1.Length; ++i)
{
r1[i] = 123;
}
}
using var r2 = RcRentedArray.Rent<float>(1024);
Assert.That(r2.AsArray() == r1Array, Is.EqualTo(true));
Assert.That(r2.AsArray().Sum(), Is.EqualTo(0));
}
}
[Test]
public void TestDispose()
{
var r1 = RcRentedArray.Rent<float>(1024);
for (int i = 0; i < r1.Length; ++i)
{
r1[i] = 123;
}
Assert.That(r1.IsDisposed, Is.EqualTo(false));
r1.Dispose();
Assert.That(r1.IsDisposed, Is.EqualTo(true));
Assert.That(r1.AsArray(), Is.Null);
}
}

View File

@ -1,114 +0,0 @@
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

@ -1,148 +0,0 @@
using System;
using System.Collections.Generic;
using NUnit.Framework;
namespace DotRecast.Core.Test;
public class RcSpanTest
{
[Test]
public void TestCopy()
{
// Test for copying all elements to the destination span.
{
Span<long> src = stackalloc long[] { 1, 2, 3 };
Span<long> dst = stackalloc long[] { 0, 0, 0 };
RcSpans.Copy(src, dst);
Assert.That(src.ToArray(), Is.EqualTo(dst.ToArray()));
}
// Test for successful copying when the destination Span has a larger size.
{
Span<long> src = stackalloc long[] { 1, 2 };
Span<long> dst = stackalloc long[] { 0, 0, 0, 0 };
RcSpans.Copy(src, dst);
Assert.That(src.ToArray(), Is.EqualTo(dst.Slice(0, src.Length).ToArray()));
}
// Test for an error when copying to a Span with a smaller size.
{
Assert.Throws<ArgumentException>(() =>
{
Span<long> src = stackalloc long[] { 1, 2, 3, 4, 5 };
Span<long> dst = stackalloc long[] { 0, 0, 0 };
RcSpans.Copy(src, dst);
});
}
// Test for copying a specific range of elements from the source span to a specific range in the destination span.
{
Span<long> src = stackalloc long[] { 1, 2, 3, 4, 5 };
Span<long> dst = stackalloc long[] { 0, 0, 0 };
RcSpans.Copy(src, 2, dst, 0, 2);
Assert.That(src.Slice(2, 2).ToArray(), Is.EqualTo(dst.Slice(0, 2).ToArray()));
}
// Test for copying a specific range of elements from the source span to a specific range in the destination span.
{
Span<long> src = stackalloc long[] { 5, 4, 3, 2, 1 };
Span<long> dst = stackalloc long[] { 0, 0, 0 };
RcSpans.Copy(src, 2, dst, 0, 3);
Assert.That(src.Slice(2, 3).ToArray(), Is.EqualTo(dst.ToArray()));
}
// Test for src (index + length) over
Assert.Throws<ArgumentOutOfRangeException>(() =>
{
Span<long> src = stackalloc long[] { 5, 4, 3, 2, 1 };
Span<long> dst = stackalloc long[] { 0, 0, 0 };
//
RcSpans.Copy(src, 3, dst, 0, 3);
});
// Test for src (index + length) over
Assert.Throws<ArgumentOutOfRangeException>(() =>
{
Span<long> src = stackalloc long[] { 5, 4, 3, 2, 1 };
Span<long> dst = stackalloc long[] { 0, 0, 0 };
//
RcSpans.Copy(src, 5, dst, 0, 1);
});
// Test for dst (idx + length) over
Assert.Throws<ArgumentException>(() =>
{
Span<long> src = stackalloc long[] { 5, 4, 3, 2, 1 };
Span<long> dst = stackalloc long[] { 0, 0, 0 };
//
RcSpans.Copy(src, 0, dst, 1, 3);
});
// Test for dst (idx + length) over
Assert.Throws<ArgumentException>(() =>
{
Span<long> src = stackalloc long[] { 5, 4, 3, 2, 1 };
Span<long> dst = stackalloc long[] { 0, 0, 0 };
//
RcSpans.Copy(src, 0, dst, 0, 4);
});
}
[Test]
public void TestMove()
{
// [3, 2, 1] -> [3, 1, 1]
{
var expected = new List<long>() { 3, 1, 1 };
Span<long> src = stackalloc long[] { 3, 2, 1 };
RcSpans.Move(src, 2, 1, 1);
Assert.That(src.ToArray(), Is.EqualTo(expected));
}
// [3, 2, 1] -> [2, 1, 1]
{
var expected = new List<long>() { 2, 1, 1 };
Span<long> src = stackalloc long[] { 3, 2, 1 };
RcSpans.Move(src, 1, 0, 2);
Assert.That(src.ToArray(), Is.EqualTo(expected));
}
// [3, 2, 1] -> [3, 2, 1]
{
var expected = new List<long>() { 3, 2, 1 };
Span<long> src = stackalloc long[] { 3, 2, 1 };
RcSpans.Move(src, 0, 0, 3);
Assert.That(src.ToArray(), Is.EqualTo(expected));
}
// length over
Assert.Throws<ArgumentOutOfRangeException>(() =>
{
Span<long> src = stackalloc long[] { 3, 2, 1 };
RcSpans.Move(src, 0, 0, 4);
});
// source index over
Assert.Throws<ArgumentOutOfRangeException>(() =>
{
Span<long> src = stackalloc long[] { 3, 2, 1 };
RcSpans.Move(src, 3, 0, 1);
});
// destination index over
Assert.Throws<ArgumentOutOfRangeException>(() =>
{
Span<long> src = stackalloc long[] { 3, 2, 1 };
RcSpans.Move(src, 0, 3, 1);
});
}
}

View File

@ -1,272 +0,0 @@
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

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

View File

@ -1,7 +1,7 @@
/* /*
Copyright (c) 2009-2010 Mikko Mononen memon@inside.org Copyright (c) 2009-2010 Mikko Mononen memon@inside.org
recast4j copyright (c) 2015-2019 Piotr Piastucki piotr@jtilia.org recast4j copyright (c) 2015-2019 Piotr Piastucki piotr@jtilia.org
DotRecast Copyright (c) 2023-2024 Choi Ikpil ikpil@naver.com DotRecast Copyright (c) 2023 Choi Ikpil ikpil@naver.com
This software is provided 'as-is', without any express or implied This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages warranty. In no event will the authors be held liable for any damages
@ -25,6 +25,7 @@ using NUnit.Framework;
namespace DotRecast.Detour.Crowd.Test; namespace DotRecast.Detour.Crowd.Test;
[Parallelizable]
public class AbstractCrowdTest public class AbstractCrowdTest
{ {
protected readonly long[] startRefs = protected readonly long[] startRefs =
@ -62,9 +63,8 @@ public class AbstractCrowdTest
[SetUp] [SetUp]
public void SetUp() public void SetUp()
{ {
nmd = TestMeshDataFactory.Create(); nmd = new RecastTestMeshBuilder().GetMeshData();
navmesh = new DtNavMesh(); navmesh = new DtNavMesh(nmd, 6, 0);
navmesh.Init(nmd, 6, 0);
query = new DtNavMeshQuery(navmesh); query = new DtNavMeshQuery(navmesh);
DtCrowdConfig config = new DtCrowdConfig(0.6f); DtCrowdConfig config = new DtCrowdConfig(0.6f);
crowd = new DtCrowd(config, navmesh); crowd = new DtCrowd(config, navmesh);
@ -153,7 +153,7 @@ public class AbstractCrowdTest
RcVec3f vel = RcVec3f.Subtract(tgt, pos); RcVec3f vel = RcVec3f.Subtract(tgt, pos);
vel.Y = 0.0f; vel.Y = 0.0f;
vel = RcVec3f.Normalize(vel); vel = RcVec3f.Normalize(vel);
vel = vel * speed; vel = vel.Scale(speed);
return vel; return vel;
} }

View File

@ -1,7 +1,7 @@
/* /*
Copyright (c) 2009-2010 Mikko Mononen memon@inside.org Copyright (c) 2009-2010 Mikko Mononen memon@inside.org
recast4j copyright (c) 2015-2019 Piotr Piastucki piotr@jtilia.org recast4j copyright (c) 2015-2019 Piotr Piastucki piotr@jtilia.org
DotRecast Copyright (c) 2023-2024 Choi Ikpil ikpil@naver.com DotRecast Copyright (c) 2023 Choi Ikpil ikpil@naver.com
This software is provided 'as-is', without any express or implied This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages warranty. In no event will the authors be held liable for any damages
@ -22,7 +22,7 @@ using NUnit.Framework;
namespace DotRecast.Detour.Crowd.Test; namespace DotRecast.Detour.Crowd.Test;
[Parallelizable]
public class Crowd1Test : AbstractCrowdTest public class Crowd1Test : AbstractCrowdTest
{ {
static readonly float[][] EXPECTED_A1Q0TVTA = static readonly float[][] EXPECTED_A1Q0TVTA =

View File

@ -1,7 +1,7 @@
/* /*
Copyright (c) 2009-2010 Mikko Mononen memon@inside.org Copyright (c) 2009-2010 Mikko Mononen memon@inside.org
recast4j copyright (c) 2015-2019 Piotr Piastucki piotr@jtilia.org recast4j copyright (c) 2015-2019 Piotr Piastucki piotr@jtilia.org
DotRecast Copyright (c) 2023-2024 Choi Ikpil ikpil@naver.com DotRecast Copyright (c) 2023 Choi Ikpil ikpil@naver.com
This software is provided 'as-is', without any express or implied This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages warranty. In no event will the authors be held liable for any damages
@ -24,7 +24,7 @@ using NUnit.Framework;
namespace DotRecast.Detour.Crowd.Test; namespace DotRecast.Detour.Crowd.Test;
[Parallelizable]
public class Crowd4Test : AbstractCrowdTest public class Crowd4Test : AbstractCrowdTest
{ {
static readonly float[][] EXPECTED_A1Q2TVTA = static readonly float[][] EXPECTED_A1Q2TVTA =

View File

@ -1,7 +1,7 @@
/* /*
Copyright (c) 2009-2010 Mikko Mononen memon@inside.org Copyright (c) 2009-2010 Mikko Mononen memon@inside.org
recast4j copyright (c) 2015-2019 Piotr Piastucki piotr@jtilia.org recast4j copyright (c) 2015-2019 Piotr Piastucki piotr@jtilia.org
DotRecast Copyright (c) 2023-2024 Choi Ikpil ikpil@naver.com DotRecast Copyright (c) 2023 Choi Ikpil ikpil@naver.com
This software is provided 'as-is', without any express or implied This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages warranty. In no event will the authors be held liable for any damages
@ -22,7 +22,7 @@ using NUnit.Framework;
namespace DotRecast.Detour.Crowd.Test; namespace DotRecast.Detour.Crowd.Test;
[Parallelizable]
public class Crowd4VelocityTest : AbstractCrowdTest public class Crowd4VelocityTest : AbstractCrowdTest
{ {
static readonly float[][] EXPECTED_A1Q3TVTA = static readonly float[][] EXPECTED_A1Q3TVTA =

View File

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

View File

@ -1,73 +0,0 @@
/*
recast4j copyright (c) 2021 Piotr Piastucki piotr@jtilia.org
DotRecast Copyright (c) 2023-2024 Choi Ikpil ikpil@naver.com
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
*/
using System;
using System.Collections.Generic;
using DotRecast.Core.Numerics;
using NUnit.Framework;
namespace DotRecast.Detour.Crowd.Test;
public class DtPathCorridorTest
{
private readonly DtPathCorridor corridor = new DtPathCorridor();
private readonly IDtQueryFilter filter = new DtQueryDefaultFilter();
[SetUp]
public void SetUp()
{
corridor.Init(256);
corridor.Reset(0, new RcVec3f(10, 20, 30));
}
[Test]
public void ShouldKeepOriginalPathInFindCornersWhenNothingCanBePruned()
{
var straightPath = new DtStraightPath[4];
straightPath[0] = new DtStraightPath(new RcVec3f(11, 20, 30.00001f), 0, 0);
straightPath[1] = new DtStraightPath(new RcVec3f(12, 20, 30.00002f), 0, 0);
straightPath[2] = new DtStraightPath(new RcVec3f(11f, 21, 32f), 0, 0);
straightPath[3] = new DtStraightPath(new RcVec3f(11f, 21, 32f), 0, 0);
var query = new DtNavMeshQueryMock(straightPath, DtStatus.DT_SUCCESS);
Span<DtStraightPath> path = stackalloc DtStraightPath[8];
var npath = corridor.FindCorners(path, 8, query, filter);
Assert.That(npath, Is.EqualTo(4));
Assert.That(path.Slice(0, npath).ToArray(), Is.EqualTo(straightPath));
}
[Test]
public void ShouldPrunePathInFindCorners()
{
DtStraightPath[] straightPath = new DtStraightPath[5];
straightPath[0] = (new DtStraightPath(new RcVec3f(10, 20, 30.00001f), 0, 0)); // too close
straightPath[1] = (new DtStraightPath(new RcVec3f(10, 20, 30.00002f), 0, 0)); // too close
straightPath[2] = (new DtStraightPath(new RcVec3f(11f, 21, 32f), 0, 0));
straightPath[3] = (new DtStraightPath(new RcVec3f(12f, 22, 33f), DtStraightPathFlags.DT_STRAIGHTPATH_OFFMESH_CONNECTION, 0)); // offmesh
straightPath[4] = (new DtStraightPath(new RcVec3f(11f, 21, 32f), DtStraightPathFlags.DT_STRAIGHTPATH_OFFMESH_CONNECTION, 0)); // offmesh
var query = new DtNavMeshQueryMock(straightPath, DtStatus.DT_SUCCESS);
Span<DtStraightPath> path = stackalloc DtStraightPath[8];
int npath = corridor.FindCorners(path, 8, query, filter);
Assert.That(npath, Is.EqualTo(2));
Assert.That(path.Slice(0, npath).ToArray(), Is.EqualTo(new DtStraightPath[] { straightPath[2], straightPath[3] }));
}
}

View File

@ -0,0 +1,100 @@
/*
recast4j copyright (c) 2021 Piotr Piastucki piotr@jtilia.org
DotRecast Copyright (c) 2023 Choi Ikpil ikpil@naver.com
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
*/
using System.Collections.Generic;
using DotRecast.Core.Numerics;
using Moq;
using NUnit.Framework;
namespace DotRecast.Detour.Crowd.Test;
[Parallelizable]
public class PathCorridorTest
{
private readonly DtPathCorridor corridor = new DtPathCorridor();
private readonly IDtQueryFilter filter = new DtQueryDefaultFilter();
[SetUp]
public void SetUp()
{
corridor.Reset(0, new RcVec3f(10, 20, 30));
}
[Test]
public void ShouldKeepOriginalPathInFindCornersWhenNothingCanBePruned()
{
List<DtStraightPath> straightPath = new();
straightPath.Add(new DtStraightPath(new RcVec3f(11, 20, 30.00001f), 0, 0));
straightPath.Add(new DtStraightPath(new RcVec3f(12, 20, 30.00002f), 0, 0));
straightPath.Add(new DtStraightPath(new RcVec3f(11f, 21, 32f), 0, 0));
straightPath.Add(new DtStraightPath(new RcVec3f(11f, 21, 32f), 0, 0));
var mockQuery = new Mock<DtNavMeshQuery>(It.IsAny<DtNavMesh>());
mockQuery.Setup(q => q.FindStraightPath(
It.IsAny<RcVec3f>(),
It.IsAny<RcVec3f>(),
It.IsAny<List<long>>(),
ref It.Ref<List<DtStraightPath>>.IsAny,
It.IsAny<int>(),
It.IsAny<int>())
)
.Callback((RcVec3f startPos, RcVec3f endPos, List<long> path,
ref List<DtStraightPath> refStraightPath, int maxStraightPath, int options) =>
{
refStraightPath = straightPath;
})
.Returns(() => DtStatus.DT_SUCCSESS);
var path = new List<DtStraightPath>();
corridor.FindCorners(ref path, int.MaxValue, mockQuery.Object, filter);
Assert.That(path.Count, Is.EqualTo(4));
Assert.That(path, Is.EqualTo(straightPath));
}
[Test]
public void ShouldPrunePathInFindCorners()
{
List<DtStraightPath> straightPath = new();
straightPath.Add(new DtStraightPath(new RcVec3f(10, 20, 30.00001f), 0, 0)); // too close
straightPath.Add(new DtStraightPath(new RcVec3f(10, 20, 30.00002f), 0, 0)); // too close
straightPath.Add(new DtStraightPath(new RcVec3f(11f, 21, 32f), 0, 0));
straightPath.Add(new DtStraightPath(new RcVec3f(12f, 22, 33f), DtStraightPathFlags.DT_STRAIGHTPATH_OFFMESH_CONNECTION, 0)); // offmesh
straightPath.Add(new DtStraightPath(new RcVec3f(11f, 21, 32f), DtStraightPathFlags.DT_STRAIGHTPATH_OFFMESH_CONNECTION, 0)); // offmesh
var mockQuery = new Mock<DtNavMeshQuery>(It.IsAny<DtNavMesh>());
mockQuery.Setup(q => q.FindStraightPath(
It.IsAny<RcVec3f>(),
It.IsAny<RcVec3f>(),
It.IsAny<List<long>>(),
ref It.Ref<List<DtStraightPath>>.IsAny,
It.IsAny<int>(),
It.IsAny<int>())
).Callback((RcVec3f startPos, RcVec3f endPos, List<long> path,
ref List<DtStraightPath> refStraightPath, int maxStraightPath, int options) =>
{
refStraightPath = straightPath;
})
.Returns(() => DtStatus.DT_SUCCSESS);
var path = new List<DtStraightPath>();
corridor.FindCorners(ref path, int.MaxValue, mockQuery.Object, filter);
Assert.That(path.Count, Is.EqualTo(2));
Assert.That(path, Is.EqualTo(new List<DtStraightPath> { straightPath[2], straightPath[3] }));
}
}

View File

@ -1,6 +1,5 @@
/* /*
recast4j Copyright (c) 2015-2019 Piotr Piastucki piotr@jtilia.org recast4j Copyright (c) 2015-2019 Piotr Piastucki piotr@jtilia.org
DotRecast Copyright (c) 2023-2024 Choi Ikpil ikpil@naver.com
This software is provided 'as-is', without any express or implied This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages warranty. In no event will the authors be held liable for any damages
@ -23,42 +22,46 @@ using DotRecast.Recast.Geom;
namespace DotRecast.Detour.Crowd.Test; namespace DotRecast.Detour.Crowd.Test;
public class TestMeshDataFactory public class RecastTestMeshBuilder
{ {
private const float m_cellSize = 0.3f; private readonly DtMeshData meshData;
private const float m_cellHeight = 0.2f; public const float m_cellSize = 0.3f;
private const float m_agentHeight = 2.0f; public const float m_cellHeight = 0.2f;
private const float m_agentRadius = 0.6f; public const float m_agentHeight = 2.0f;
private const float m_agentMaxClimb = 0.9f; public const float m_agentRadius = 0.6f;
private const float m_agentMaxSlope = 45.0f; public const float m_agentMaxClimb = 0.9f;
private const int m_regionMinSize = 8; public const float m_agentMaxSlope = 45.0f;
private const int m_regionMergeSize = 20; public const int m_regionMinSize = 8;
private const float m_edgeMaxLen = 12.0f; public const int m_regionMergeSize = 20;
private const float m_edgeMaxError = 1.3f; public const float m_edgeMaxLen = 12.0f;
private const int m_vertsPerPoly = 6; public const float m_edgeMaxError = 1.3f;
private const float m_detailSampleDist = 6.0f; public const int m_vertsPerPoly = 6;
private const float m_detailSampleMaxError = 1.0f; public const float m_detailSampleDist = 6.0f;
public const float m_detailSampleMaxError = 1.0f;
public static DtMeshData Create() public RecastTestMeshBuilder()
: this(SimpleInputGeomProvider.LoadFile("dungeon.obj"),
RcPartition.WATERSHED,
m_cellSize, m_cellHeight,
m_agentMaxSlope, m_agentHeight, m_agentRadius, m_agentMaxClimb,
m_regionMinSize, m_regionMergeSize,
m_edgeMaxLen, m_edgeMaxError,
m_vertsPerPoly,
m_detailSampleDist, m_detailSampleMaxError)
{ {
IInputGeomProvider geom = SimpleInputGeomProvider.LoadFile("dungeon.obj"); }
RcPartition partition = RcPartition.WATERSHED;
float cellSize = m_cellSize;
float cellHeight = m_cellHeight;
float agentMaxSlope = m_agentMaxSlope;
float agentHeight = m_agentHeight;
float agentRadius = m_agentRadius;
float agentMaxClimb = m_agentMaxClimb;
int regionMinSize = m_regionMinSize;
int regionMergeSize = m_regionMergeSize;
float edgeMaxLen = m_edgeMaxLen;
float edgeMaxError = m_edgeMaxError;
int vertsPerPoly = m_vertsPerPoly;
float detailSampleDist = m_detailSampleDist;
float detailSampleMaxError = m_detailSampleMaxError;
public RecastTestMeshBuilder(IInputGeomProvider geom,
RcPartition partitionType,
float cellSize, float cellHeight,
float agentMaxSlope, float agentHeight, float agentRadius, float agentMaxClimb,
int regionMinSize, int regionMergeSize,
float edgeMaxLen, float edgeMaxError,
int vertsPerPoly,
float detailSampleDist, float detailSampleMaxError)
{
RcConfig cfg = new RcConfig( RcConfig cfg = new RcConfig(
partition, partitionType,
cellSize, cellHeight, cellSize, cellHeight,
agentMaxSlope, agentHeight, agentRadius, agentMaxClimb, agentMaxSlope, agentHeight, agentRadius, agentMaxClimb,
regionMinSize, regionMergeSize, regionMinSize, regionMergeSize,
@ -69,32 +72,32 @@ public class TestMeshDataFactory
SampleAreaModifications.SAMPLE_AREAMOD_GROUND, true); SampleAreaModifications.SAMPLE_AREAMOD_GROUND, true);
RcBuilderConfig bcfg = new RcBuilderConfig(cfg, geom.GetMeshBoundsMin(), geom.GetMeshBoundsMax()); RcBuilderConfig bcfg = new RcBuilderConfig(cfg, geom.GetMeshBoundsMin(), geom.GetMeshBoundsMax());
RcBuilder rcBuilder = new RcBuilder(); RcBuilder rcBuilder = new RcBuilder();
RcBuilderResult rcResult = rcBuilder.Build(geom, bcfg, false); RcBuilderResult rcResult = rcBuilder.Build(geom, bcfg);
RcPolyMesh pmesh = rcResult.Mesh; RcPolyMesh m_pmesh = rcResult.GetMesh();
for (int i = 0; i < pmesh.npolys; ++i) for (int i = 0; i < m_pmesh.npolys; ++i)
{ {
pmesh.flags[i] = 1; m_pmesh.flags[i] = 1;
} }
RcPolyMeshDetail dmesh = rcResult.MeshDetail; RcPolyMeshDetail m_dmesh = rcResult.GetMeshDetail();
DtNavMeshCreateParams option = new DtNavMeshCreateParams(); DtNavMeshCreateParams option = new DtNavMeshCreateParams();
option.verts = pmesh.verts; option.verts = m_pmesh.verts;
option.vertCount = pmesh.nverts; option.vertCount = m_pmesh.nverts;
option.polys = pmesh.polys; option.polys = m_pmesh.polys;
option.polyAreas = pmesh.areas; option.polyAreas = m_pmesh.areas;
option.polyFlags = pmesh.flags; option.polyFlags = m_pmesh.flags;
option.polyCount = pmesh.npolys; option.polyCount = m_pmesh.npolys;
option.nvp = pmesh.nvp; option.nvp = m_pmesh.nvp;
option.detailMeshes = dmesh.meshes; option.detailMeshes = m_dmesh.meshes;
option.detailVerts = dmesh.verts; option.detailVerts = m_dmesh.verts;
option.detailVertsCount = dmesh.nverts; option.detailVertsCount = m_dmesh.nverts;
option.detailTris = dmesh.tris; option.detailTris = m_dmesh.tris;
option.detailTriCount = dmesh.ntris; option.detailTriCount = m_dmesh.ntris;
option.walkableHeight = agentHeight; option.walkableHeight = agentHeight;
option.walkableRadius = agentRadius; option.walkableRadius = agentRadius;
option.walkableClimb = agentMaxClimb; option.walkableClimb = agentMaxClimb;
option.bmin = pmesh.bmin; option.bmin = m_pmesh.bmin;
option.bmax = pmesh.bmax; option.bmax = m_pmesh.bmax;
option.cs = cellSize; option.cs = cellSize;
option.ch = cellHeight; option.ch = cellHeight;
option.buildBvTree = true; option.buildBvTree = true;
@ -117,8 +120,11 @@ public class TestMeshDataFactory
option.offMeshConUserID = new int[1]; option.offMeshConUserID = new int[1];
option.offMeshConUserID[0] = 0x4567; option.offMeshConUserID[0] = 0x4567;
option.offMeshConCount = 1; option.offMeshConCount = 1;
var meshData = DtNavMeshBuilder.CreateNavMeshData(option); meshData = DtNavMeshBuilder.CreateNavMeshData(option);
}
public DtMeshData GetMeshData()
{
return meshData; return meshData;
} }
} }

View File

@ -1,6 +1,5 @@
/* /*
recast4j Copyright (c) 2015-2019 Piotr Piastucki piotr@jtilia.org recast4j Copyright (c) 2015-2019 Piotr Piastucki piotr@jtilia.org
DotRecast Copyright (c) 2023-2024 Choi Ikpil ikpil@naver.com
This software is provided 'as-is', without any express or implied This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages warranty. In no event will the authors be held liable for any damages

View File

@ -1,25 +1,25 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<TargetFrameworks>net6.0;net7.0;net8.0</TargetFrameworks> <TargetFrameworks>net6.0;net7.0</TargetFrameworks>
<IsPackable>false</IsPackable> <IsPackable>false</IsPackable>
<IsTestProject>true</IsTestProject> <IsTestProject>true</IsTestProject>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.11.1" /> <PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.7.2" />
<PackageReference Include="Moq" Version="4.20.72" /> <PackageReference Include="Moq" Version="4.20.69" />
<PackageReference Include="NUnit" Version="4.2.2" /> <PackageReference Include="NUnit" Version="3.13.3"/>
<PackageReference Include="NUnit3TestAdapter" Version="4.6.0" /> <PackageReference Include="NUnit3TestAdapter" Version="4.5.0"/>
<PackageReference Include="NUnit.Analyzers" Version="4.3.0"> <PackageReference Include="NUnit.Analyzers" Version="3.9.0">
<PrivateAssets>all</PrivateAssets> <PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference> </PackageReference>
<PackageReference Include="coverlet.collector" Version="6.0.2"> <PackageReference Include="coverlet.collector" Version="6.0.0">
<PrivateAssets>all</PrivateAssets> <PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference> </PackageReference>
<PackageReference Include="K4os.Compression.LZ4" Version="1.3.8" /> <PackageReference Include="K4os.Compression.LZ4" Version="1.3.6" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>

View File

@ -1,4 +1,3 @@
using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;
using System.Threading.Tasks; using System.Threading.Tasks;
@ -7,11 +6,11 @@ using DotRecast.Core.Numerics;
using DotRecast.Detour.Dynamic.Colliders; using DotRecast.Detour.Dynamic.Colliders;
using DotRecast.Detour.Dynamic.Io; using DotRecast.Detour.Dynamic.Io;
using DotRecast.Detour.Dynamic.Test.Io; using DotRecast.Detour.Dynamic.Test.Io;
using DotRecast.Detour.Io;
using NUnit.Framework; using NUnit.Framework;
namespace DotRecast.Detour.Dynamic.Test; namespace DotRecast.Detour.Dynamic.Test;
[Parallelizable]
public class DynamicNavMeshTest public class DynamicNavMeshTest
{ {
private static readonly RcVec3f START_POS = new RcVec3f(70.87453f, 0.0010070801f, 86.69021f); private static readonly RcVec3f START_POS = new RcVec3f(70.87453f, 0.0010070801f, 86.69021f);
@ -23,7 +22,7 @@ public class DynamicNavMeshTest
[Test] [Test]
public void E2eTest() public void E2eTest()
{ {
byte[] bytes = RcIO.ReadFileIfFound("test_tiles.voxels"); byte[] bytes = RcResources.Load("test_tiles.voxels");
using var ms = new MemoryStream(bytes); using var ms = new MemoryStream(bytes);
using var br = new BinaryReader(ms); using var br = new BinaryReader(ms);
@ -33,7 +32,9 @@ public class DynamicNavMeshTest
// create dynamic navmesh // create dynamic navmesh
DtDynamicNavMesh mesh = new DtDynamicNavMesh(f); DtDynamicNavMesh mesh = new DtDynamicNavMesh(f);
// build navmesh asynchronously using multiple threads // build navmesh asynchronously using multiple threads
mesh.Build(Task.Factory); Task<bool> future = mesh.Build(Task.Factory);
// wait for build to complete
bool _ = future.Result;
// create new query // create new query
DtNavMeshQuery query = new DtNavMeshQuery(mesh.NavMesh()); DtNavMeshQuery query = new DtNavMeshQuery(mesh.NavMesh());
@ -53,8 +54,9 @@ public class DynamicNavMeshTest
long colliderId = mesh.AddCollider(colldier); long colliderId = mesh.AddCollider(colldier);
// update navmesh asynchronously // update navmesh asynchronously
mesh.Update(Task.Factory); future = mesh.Update(Task.Factory);
// wait for update to complete
_ = future.Result;
// create new query // create new query
query = new DtNavMeshQuery(mesh.NavMesh()); query = new DtNavMeshQuery(mesh.NavMesh());
@ -68,7 +70,9 @@ public class DynamicNavMeshTest
// remove obstacle // remove obstacle
mesh.RemoveCollider(colliderId); mesh.RemoveCollider(colliderId);
// update navmesh asynchronously // update navmesh asynchronously
mesh.Update(Task.Factory); future = mesh.Update(Task.Factory);
// wait for update to complete
_ = future.Result;
// create new query // create new query
query = new DtNavMeshQuery(mesh.NavMesh()); query = new DtNavMeshQuery(mesh.NavMesh());
@ -80,101 +84,4 @@ public class DynamicNavMeshTest
// path length should be back to the initial value // path length should be back to the initial value
Assert.That(path.Count, Is.EqualTo(16)); Assert.That(path.Count, Is.EqualTo(16));
} }
[Test]
public void ShouldSaveAndLoadDynamicNavMesh()
{
using var writerMs = new MemoryStream();
using var bw = new BinaryWriter(writerMs);
int maxVertsPerPoly = 6;
// load voxels from file
{
byte[] bytes = RcIO.ReadFileIfFound("test_tiles.voxels");
using var readMs = new MemoryStream(bytes);
using var br = new BinaryReader(readMs);
DtVoxelFileReader reader = new DtVoxelFileReader(DtVoxelTileLZ4ForTestCompressor.Shared);
DtVoxelFile f = reader.Read(br);
// create dynamic navmesh
DtDynamicNavMesh mesh = new DtDynamicNavMesh(f);
// build navmesh asynchronously using multiple threads
mesh.Build(Task.Factory);
// Save the resulting nav mesh and re-use it
new DtMeshSetWriter().Write(bw, mesh.NavMesh(), RcByteOrder.LITTLE_ENDIAN, true);
maxVertsPerPoly = mesh.NavMesh().GetMaxVertsPerPoly();
}
{
byte[] bytes = RcIO.ReadFileIfFound("test_tiles.voxels");
using var readMs = new MemoryStream(bytes);
using var br = new BinaryReader(readMs);
// load voxels from file
DtVoxelFileReader reader = new DtVoxelFileReader(DtVoxelTileLZ4ForTestCompressor.Shared);
DtVoxelFile f = reader.Read(br);
// create dynamic navmesh
DtDynamicNavMesh mesh = new DtDynamicNavMesh(f);
// use the saved nav mesh instead of building from scratch
DtNavMesh navMesh = new DtMeshSetReader().Read(new RcByteBuffer(writerMs.ToArray()), maxVertsPerPoly);
mesh.NavMesh(navMesh);
DtNavMeshQuery query = new DtNavMeshQuery(mesh.NavMesh());
IDtQueryFilter filter = new DtQueryDefaultFilter();
// find path
_ = query.FindNearestPoly(START_POS, EXTENT, filter, out var startNearestRef, out var startNearestPos, out var _);
_ = query.FindNearestPoly(END_POS, EXTENT, filter, out var endNearestRef, out var endNearestPos, out var _);
List<long> path = new List<long>();
query.FindPath(startNearestRef, endNearestRef, startNearestPos, endNearestPos, filter, ref path, DtFindPathOption.AnyAngle);
// check path length without any obstacles
Assert.That(path.Count, Is.EqualTo(16));
// place obstacle
DtCollider colldier = new DtSphereCollider(SPHERE_POS, 20, SampleAreaModifications.SAMPLE_POLYAREA_TYPE_GROUND, 0.1f);
long colliderId = mesh.AddCollider(colldier);
// update navmesh asynchronously
mesh.Update(Task.Factory);
// create new query
query = new DtNavMeshQuery(mesh.NavMesh());
// find path again
_ = query.FindNearestPoly(START_POS, EXTENT, filter, out startNearestRef, out startNearestPos, out var _);
_ = query.FindNearestPoly(END_POS, EXTENT, filter, out endNearestRef, out endNearestPos, out var _);
path = new List<long>();
query.FindPath(startNearestRef, endNearestRef, startNearestPos, endNearestPos, filter, ref path, DtFindPathOption.AnyAngle);
// check path length with obstacles
Assert.That(path.Count, Is.EqualTo(19));
// remove obstacle
mesh.RemoveCollider(colliderId);
// update navmesh asynchronously
mesh.Update(Task.Factory);
// create new query
query = new DtNavMeshQuery(mesh.NavMesh());
// find path one more time
_ = query.FindNearestPoly(START_POS, EXTENT, filter, out startNearestRef, out startNearestPos, out var _);
_ = query.FindNearestPoly(END_POS, EXTENT, filter, out endNearestRef, out endNearestPos, out var _);
path = new List<long>();
query.FindPath(startNearestRef, endNearestRef, startNearestPos, endNearestPos, filter, ref path, DtFindPathOption.AnyAngle);
// path length should be back to the initial value
Assert.That(path.Count, Is.EqualTo(16));
}
}
} }

View File

@ -1,6 +1,6 @@
/* /*
recast4j copyright (c) 2021 Piotr Piastucki piotr@jtilia.org recast4j copyright (c) 2021 Piotr Piastucki piotr@jtilia.org
DotRecast Copyright (c) 2023-2024 Choi Ikpil ikpil@naver.com DotRecast Copyright (c) 2023 Choi Ikpil ikpil@naver.com
This software is provided 'as-is', without any express or implied This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages warranty. In no event will the authors be held liable for any damages
@ -47,7 +47,7 @@ namespace DotRecast.Detour.Dynamic.Test.Io
byte[] compressed = LZ4Pickler.Pickle(data, LZ4Level.L12_MAX); byte[] compressed = LZ4Pickler.Pickle(data, LZ4Level.L12_MAX);
byte[] result = new byte[4 + compressed.Length]; byte[] result = new byte[4 + compressed.Length];
RcByteUtils.PutInt(compressed.Length, result, 0, RcByteOrder.BIG_ENDIAN); RcByteUtils.PutInt(compressed.Length, result, 0, RcByteOrder.BIG_ENDIAN);
RcArrays.Copy(compressed, 0, result, 4, compressed.Length); Array.Copy(compressed, 0, result, 4, compressed.Length);
return result; return result;
} }
} }

View File

@ -1,6 +1,6 @@
/* /*
recast4j copyright (c) 2021 Piotr Piastucki piotr@jtilia.org recast4j copyright (c) 2021 Piotr Piastucki piotr@jtilia.org
DotRecast Copyright (c) 2023-2024 Choi Ikpil ikpil@naver.com DotRecast Copyright (c) 2023 Choi Ikpil ikpil@naver.com
This software is provided 'as-is', without any express or implied This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages warranty. In no event will the authors be held liable for any damages
@ -25,13 +25,13 @@ using NUnit.Framework;
namespace DotRecast.Detour.Dynamic.Test.Io; namespace DotRecast.Detour.Dynamic.Test.Io;
[Parallelizable]
public class VoxelFileReaderTest public class VoxelFileReaderTest
{ {
[Test] [Test]
public void ShouldReadSingleTileFile() public void ShouldReadSingleTileFile()
{ {
byte[] bytes = RcIO.ReadFileIfFound("test.voxels"); byte[] bytes = RcResources.Load("test.voxels");
using var ms = new MemoryStream(bytes); using var ms = new MemoryStream(bytes);
using var br = new BinaryReader(ms); using var br = new BinaryReader(ms);
@ -57,7 +57,7 @@ public class VoxelFileReaderTest
[Test] [Test]
public void ShouldReadMultiTileFile() public void ShouldReadMultiTileFile()
{ {
byte[] bytes = RcIO.ReadFileIfFound("test_tiles.voxels"); byte[] bytes = RcResources.Load("test_tiles.voxels");
using var ms = new MemoryStream(bytes); using var ms = new MemoryStream(bytes);
using var br = new BinaryReader(ms); using var br = new BinaryReader(ms);

View File

@ -1,6 +1,6 @@
/* /*
recast4j copyright (c) 2021 Piotr Piastucki piotr@jtilia.org recast4j copyright (c) 2021 Piotr Piastucki piotr@jtilia.org
DotRecast Copyright (c) 2023-2024 Choi Ikpil ikpil@naver.com DotRecast Copyright (c) 2023 Choi Ikpil ikpil@naver.com
This software is provided 'as-is', without any express or implied This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages warranty. In no event will the authors be held liable for any damages
@ -25,14 +25,14 @@ using NUnit.Framework;
namespace DotRecast.Detour.Dynamic.Test.Io; namespace DotRecast.Detour.Dynamic.Test.Io;
[Parallelizable]
public class VoxelFileReaderWriterTest public class VoxelFileReaderWriterTest
{ {
[TestCase(false)] [TestCase(false)]
[TestCase(true)] [TestCase(true)]
public void ShouldReadSingleTileFile(bool compression) public void ShouldReadSingleTileFile(bool compression)
{ {
byte[] bytes = RcIO.ReadFileIfFound("test.voxels"); byte[] bytes = RcResources.Load("test.voxels");
using var ms = new MemoryStream(bytes); using var ms = new MemoryStream(bytes);
using var br = new BinaryReader(ms); using var br = new BinaryReader(ms);
@ -60,7 +60,7 @@ public class VoxelFileReaderWriterTest
[TestCase(true)] [TestCase(true)]
public void ShouldReadMultiTileFile(bool compression) public void ShouldReadMultiTileFile(bool compression)
{ {
byte[] bytes = RcIO.ReadFileIfFound("test_tiles.voxels"); byte[] bytes = RcResources.Load("test_tiles.voxels");
using var ms = new MemoryStream(bytes); using var ms = new MemoryStream(bytes);
using var br = new BinaryReader(ms); using var br = new BinaryReader(ms);

View File

@ -1,6 +1,5 @@
/* /*
recast4j Copyright (c) 2015-2019 Piotr Piastucki piotr@jtilia.org recast4j Copyright (c) 2015-2019 Piotr Piastucki piotr@jtilia.org
DotRecast Copyright (c) 2023-2024 Choi Ikpil ikpil@naver.com
This software is provided 'as-is', without any express or implied This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages warranty. In no event will the authors be held liable for any damages

View File

@ -1,6 +1,6 @@
/* /*
recast4j copyright (c) 2021 Piotr Piastucki piotr@jtilia.org recast4j copyright (c) 2021 Piotr Piastucki piotr@jtilia.org
DotRecast Copyright (c) 2023-2024 Choi Ikpil ikpil@naver.com DotRecast Copyright (c) 2023 Choi Ikpil ikpil@naver.com
This software is provided 'as-is', without any express or implied This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages warranty. In no event will the authors be held liable for any damages
@ -31,6 +31,7 @@ using NUnit.Framework;
namespace DotRecast.Detour.Dynamic.Test; namespace DotRecast.Detour.Dynamic.Test;
[Parallelizable]
public class VoxelQueryTest public class VoxelQueryTest
{ {
private const int TILE_WIDTH = 100; private const int TILE_WIDTH = 100;
@ -93,19 +94,19 @@ public class VoxelQueryTest
private DtDynamicNavMesh CreateDynaMesh() private DtDynamicNavMesh CreateDynaMesh()
{ {
var bytes = RcIO.ReadFileIfFound("test_tiles.voxels"); var bytes = RcResources.Load("test_tiles.voxels");
using var ms = new MemoryStream(bytes); using var ms = new MemoryStream(bytes);
using var br = new BinaryReader(ms); using var br = new BinaryReader(ms);
// load voxels from file // load voxels from file
DtVoxelFileReader reader = new DtVoxelFileReader(DtVoxelTileLZ4ForTestCompressor.Shared); DtVoxelFileReader reader = new DtVoxelFileReader(DtVoxelTileLZ4ForTestCompressor.Shared);
DtVoxelFile f = reader.Read(br); DtVoxelFile f = reader.Read(br);
// create dynamic navmesh // create dynamic navmesh
var mesh = new DtDynamicNavMesh(f); var mesh = new DtDynamicNavMesh(f);
// build navmesh asynchronously using multiple threads // build navmesh asynchronously using multiple threads
mesh.Build(Task.Factory); Task<bool> future = mesh.Build(Task.Factory);
// wait for build to complete
var _ = future.Result;
return mesh; return mesh;
} }
} }

View File

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

View File

@ -1,6 +1,5 @@
/* /*
recast4j Copyright (c) 2015-2019 Piotr Piastucki piotr@jtilia.org recast4j copyright (c) 2015-2019 Piotr Piastucki piotr@jtilia.org
DotRecast Copyright (c) 2023-2024 Choi Ikpil ikpil@naver.com
This software is provided 'as-is', without any express or implied This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages warranty. In no event will the authors be held liable for any damages
@ -27,7 +26,7 @@ using NUnit.Framework;
namespace DotRecast.Detour.Extras.Test.Unity.Astar; namespace DotRecast.Detour.Extras.Test.Unity.Astar;
[Parallelizable]
public class UnityAStarPathfindingImporterTest public class UnityAStarPathfindingImporterTest
{ {
[Test] [Test]
@ -38,7 +37,7 @@ public class UnityAStarPathfindingImporterTest
RcVec3f endPos = new RcVec3f(11.971109f, 0.000000f, 8.663261f); RcVec3f endPos = new RcVec3f(11.971109f, 0.000000f, 8.663261f);
var path = new List<long>(); var path = new List<long>();
var status = FindPath(mesh, startPos, endPos, ref path); var status = FindPath(mesh, startPos, endPos, ref path);
Assert.That(status, Is.EqualTo(DtStatus.DT_SUCCESS)); Assert.That(status, Is.EqualTo(DtStatus.DT_SUCCSESS));
Assert.That(path.Count, Is.EqualTo(57)); Assert.That(path.Count, Is.EqualTo(57));
SaveMesh(mesh, "v4_0_6"); SaveMesh(mesh, "v4_0_6");
} }

View File

@ -1,6 +1,5 @@
/* /*
recast4j Copyright (c) 2015-2019 Piotr Piastucki piotr@jtilia.org recast4j Copyright (c) 2015-2019 Piotr Piastucki piotr@jtilia.org
DotRecast Copyright (c) 2023-2024 Choi Ikpil ikpil@naver.com
This software is provided 'as-is', without any express or implied This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages warranty. In no event will the authors be held liable for any damages
@ -22,6 +21,7 @@ using NUnit.Framework;
namespace DotRecast.Detour.Test; namespace DotRecast.Detour.Test;
[Parallelizable]
public abstract class AbstractDetourTest public abstract class AbstractDetourTest
{ {
protected static readonly long[] startRefs = protected static readonly long[] startRefs =
@ -64,8 +64,6 @@ public abstract class AbstractDetourTest
protected DtNavMesh CreateNavMesh() protected DtNavMesh CreateNavMesh()
{ {
var mesh = new DtNavMesh(); return new DtNavMesh(new RecastTestMeshBuilder().GetMeshData(), 6, 0);
mesh.Init(TestMeshDataFactory.Create(), 6, 0);
return mesh;
} }
} }

View File

@ -1,6 +1,6 @@
/* /*
recast4j copyright (c) 2021 Piotr Piastucki piotr@jtilia.org recast4j copyright (c) 2021 Piotr Piastucki piotr@jtilia.org
DotRecast Copyright (c) 2023-2024 Choi Ikpil ikpil@naver.com DotRecast Copyright (c) 2023 Choi Ikpil ikpil@naver.com
This software is provided 'as-is', without any express or implied This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages warranty. In no event will the authors be held liable for any damages
@ -17,12 +17,11 @@ freely, subject to the following restrictions:
3. This notice may not be removed or altered from any source distribution. 3. This notice may not be removed or altered from any source distribution.
*/ */
using System;
using NUnit.Framework; using NUnit.Framework;
namespace DotRecast.Detour.Test; namespace DotRecast.Detour.Test;
[Parallelizable]
public class ConvexConvexIntersectionTest public class ConvexConvexIntersectionTest
{ {
[Test] [Test]
@ -30,10 +29,9 @@ public class ConvexConvexIntersectionTest
{ {
float[] p = { -4, 0, 0, -3, 0, 3, 2, 0, 3, 3, 0, -3, -2, 0, -4 }; float[] p = { -4, 0, 0, -3, 0, 3, 2, 0, 3, 3, 0, -3, -2, 0, -4 };
float[] q = { -4, 0, 0, -3, 0, 3, 2, 0, 3, 3, 0, -3, -2, 0, -4 }; float[] q = { -4, 0, 0, -3, 0, 3, 2, 0, 3, 3, 0, -3, -2, 0, -4 };
float[] buffer = new float[128]; float[] intersection = DtConvexConvexIntersections.Intersect(p, q);
Span<float> intersection = DtConvexConvexIntersections.Intersect(p, q, buffer);
Assert.That(intersection.Length, Is.EqualTo(5 * 3)); Assert.That(intersection.Length, Is.EqualTo(5 * 3));
Assert.That(intersection.ToArray(), Is.EquivalentTo(p)); Assert.That(intersection, Is.EqualTo(p));
} }
[Test] [Test]
@ -41,9 +39,8 @@ public class ConvexConvexIntersectionTest
{ {
float[] p = { -5, 0, -5, -5, 0, 4, 1, 0, 4, 1, 0, -5 }; float[] p = { -5, 0, -5, -5, 0, 4, 1, 0, 4, 1, 0, -5 };
float[] q = { -4, 0, 0, -3, 0, 3, 2, 0, 3, 3, 0, -3, -2, 0, -4 }; float[] q = { -4, 0, 0, -3, 0, 3, 2, 0, 3, 3, 0, -3, -2, 0, -4 };
float[] buffer = new float[128]; float[] intersection = DtConvexConvexIntersections.Intersect(p, q);
Span<float> intersection = DtConvexConvexIntersections.Intersect(p, q, buffer);
Assert.That(intersection.Length, Is.EqualTo(5 * 3)); Assert.That(intersection.Length, Is.EqualTo(5 * 3));
Assert.That(intersection.ToArray(), Is.EquivalentTo(new[] { 1, 0, 3, 1, 0, -3.4f, -2, 0, -4, -4, 0, 0, -3, 0, 3 })); Assert.That(intersection, Is.EqualTo(new[] { 1, 0, 3, 1, 0, -3.4f, -2, 0, -4, -4, 0, 0, -3, 0, 3 }));
} }
} }

View File

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

View File

@ -1,84 +0,0 @@
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

@ -1,112 +0,0 @@
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

@ -1,68 +0,0 @@
using System;
using System.Linq;
using DotRecast.Core.Numerics;
using NUnit.Framework;
namespace DotRecast.Detour.Test;
public class FindCollectPolyTest : AbstractDetourTest
{
private static readonly long[][] POLY_REFS =
{
new long[]
{
281474976710697L,
281474976710695L,
281474976710696L,
281474976710691L,
},
new long[]
{
281474976710769L,
281474976710773L,
},
new long[]
{
281474976710676L,
281474976710678L,
281474976710679L,
281474976710674L,
281474976710677L,
281474976710683L,
281474976710680L,
281474976710684L,
},
new long[]
{
281474976710748L,
281474976710753L,
281474976710752L,
281474976710750L,
},
new long[]
{
281474976710736L,
281474976710733L,
281474976710735L,
}
};
[Test]
public void TestFindNearestPoly()
{
IDtQueryFilter filter = new DtQueryDefaultFilter();
RcVec3f extents = new RcVec3f(2, 4, 2);
var polys = new long[32];
for (int i = 0; i < startRefs.Length; i++)
{
Array.Fill(polys, 0);
RcVec3f startPos = startPoss[i];
var status = query.QueryPolygons(startPos, extents, filter, polys, out var polyCount, 32);
Assert.That(status.Succeeded(), Is.True, $"index({i})");
Assert.That(polyCount, Is.EqualTo(POLY_REFS[i].Length), $"index({i})");
Assert.That(polys.AsSpan(0, polyCount).ToArray(), Is.EqualTo(POLY_REFS[i]), $"index({i})");
}
}
}

View File

@ -1,6 +1,5 @@
/* /*
recast4j Copyright (c) 2015-2019 Piotr Piastucki piotr@jtilia.org recast4j Copyright (c) 2015-2019 Piotr Piastucki piotr@jtilia.org
DotRecast Copyright (c) 2023-2024 Choi Ikpil ikpil@naver.com
This software is provided 'as-is', without any express or implied This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages warranty. In no event will the authors be held liable for any damages
@ -23,7 +22,7 @@ using NUnit.Framework;
namespace DotRecast.Detour.Test; namespace DotRecast.Detour.Test;
[Parallelizable]
public class FindDistanceToWallTest : AbstractDetourTest public class FindDistanceToWallTest : AbstractDetourTest
{ {
private static readonly float[] DISTANCES_TO_WALL = { 0.597511f, 3.201085f, 0.603713f, 2.791475f, 2.815544f }; private static readonly float[] DISTANCES_TO_WALL = { 0.597511f, 3.201085f, 0.603713f, 2.791475f, 2.815544f };

View File

@ -1,6 +1,5 @@
/* /*
recast4j Copyright (c) 2015-2019 Piotr Piastucki piotr@jtilia.org recast4j Copyright (c) 2015-2019 Piotr Piastucki piotr@jtilia.org
DotRecast Copyright (c) 2023-2024 Choi Ikpil ikpil@naver.com
This software is provided 'as-is', without any express or implied This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages warranty. In no event will the authors be held liable for any damages
@ -23,7 +22,7 @@ using NUnit.Framework;
namespace DotRecast.Detour.Test; namespace DotRecast.Detour.Test;
[Parallelizable]
public class FindLocalNeighbourhoodTest : AbstractDetourTest public class FindLocalNeighbourhoodTest : AbstractDetourTest
{ {
private static readonly long[][] REFS = private static readonly long[][] REFS =

View File

@ -1,6 +1,5 @@
/* /*
recast4j Copyright (c) 2015-2019 Piotr Piastucki piotr@jtilia.org recast4j Copyright (c) 2015-2019 Piotr Piastucki piotr@jtilia.org
DotRecast Copyright (c) 2023-2024 Choi Ikpil ikpil@naver.com
This software is provided 'as-is', without any express or implied This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages warranty. In no event will the authors be held liable for any damages
@ -22,12 +21,10 @@ using NUnit.Framework;
namespace DotRecast.Detour.Test; namespace DotRecast.Detour.Test;
[Parallelizable]
public class FindNearestPolyTest : AbstractDetourTest public class FindNearestPolyTest : AbstractDetourTest
{ {
private static readonly long[] POLY_REFS = private static readonly long[] POLY_REFS = { 281474976710696L, 281474976710773L, 281474976710680L, 281474976710753L, 281474976710733L };
{
281474976710696L, 281474976710773L, 281474976710680L, 281474976710753L, 281474976710733L
};
private static readonly RcVec3f[] POLY_POS = private static readonly RcVec3f[] POLY_POS =
{ {
@ -47,11 +44,11 @@ public class FindNearestPolyTest : AbstractDetourTest
{ {
RcVec3f startPos = startPoss[i]; RcVec3f startPos = startPoss[i];
var status = query.FindNearestPoly(startPos, extents, filter, out var nearestRef, out var nearestPt, out var _); var status = query.FindNearestPoly(startPos, extents, filter, out var nearestRef, out var nearestPt, out var _);
Assert.That(status.Succeeded(), Is.True, $"index({i})"); Assert.That(status.Succeeded(), Is.True);
Assert.That(nearestRef, Is.EqualTo(POLY_REFS[i]), $"index({i})"); Assert.That(nearestRef, Is.EqualTo(POLY_REFS[i]));
Assert.That(nearestPt.X, Is.EqualTo(POLY_POS[i].X).Within(0.001f), $"index({i})"); Assert.That(nearestPt.X, Is.EqualTo(POLY_POS[i].X).Within(0.001f));
Assert.That(nearestPt.Y, Is.EqualTo(POLY_POS[i].Y).Within(0.001f), $"index({i})"); Assert.That(nearestPt.Y, Is.EqualTo(POLY_POS[i].Y).Within(0.001f));
Assert.That(nearestPt.Z, Is.EqualTo(POLY_POS[i].Z).Within(0.001f), $"index({i})"); Assert.That(nearestPt.Z, Is.EqualTo(POLY_POS[i].Z).Within(0.001f));
} }
} }

View File

@ -1,6 +1,5 @@
/* /*
recast4j Copyright (c) 2015-2019 Piotr Piastucki piotr@jtilia.org recast4j Copyright (c) 2015-2019 Piotr Piastucki piotr@jtilia.org
DotRecast Copyright (c) 2023-2024 Choi Ikpil ikpil@naver.com
This software is provided 'as-is', without any express or implied This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages warranty. In no event will the authors be held liable for any damages
@ -17,22 +16,22 @@ freely, subject to the following restrictions:
3. This notice may not be removed or altered from any source distribution. 3. This notice may not be removed or altered from any source distribution.
*/ */
using System;
using System.Collections.Generic; using System.Collections.Generic;
using DotRecast.Core.Numerics; using DotRecast.Core.Numerics;
using NUnit.Framework; using NUnit.Framework;
namespace DotRecast.Detour.Test; namespace DotRecast.Detour.Test;
[Parallelizable]
public class FindPathTest : AbstractDetourTest public class FindPathTest : AbstractDetourTest
{ {
private static readonly DtStatus[] STATUSES = private static readonly DtStatus[] STATUSES =
{ {
DtStatus.DT_SUCCESS, DtStatus.DT_SUCCSESS,
DtStatus.DT_SUCCESS | DtStatus.DT_PARTIAL_RESULT, DtStatus.DT_SUCCSESS | DtStatus.DT_PARTIAL_RESULT,
DtStatus.DT_SUCCESS, DtStatus.DT_SUCCSESS,
DtStatus.DT_SUCCESS, DtStatus.DT_SUCCSESS,
DtStatus.DT_SUCCESS DtStatus.DT_SUCCSESS
}; };
private static readonly long[][] RESULTS = private static readonly long[][] RESULTS =
@ -185,7 +184,6 @@ public class FindPathTest : AbstractDetourTest
{ {
IDtQueryFilter filter = new DtQueryDefaultFilter(); IDtQueryFilter filter = new DtQueryDefaultFilter();
var path = new List<long>(); var path = new List<long>();
Span<DtStraightPath> straightPath = stackalloc DtStraightPath[256];
for (int i = 0; i < STRAIGHT_PATHS.Length; i++) for (int i = 0; i < STRAIGHT_PATHS.Length; i++)
{ {
// startRefs.Length; i++) { // startRefs.Length; i++) {
@ -194,8 +192,9 @@ public class FindPathTest : AbstractDetourTest
var startPos = startPoss[i]; var startPos = startPoss[i];
var endPos = endPoss[i]; var endPos = endPoss[i];
var status = query.FindPath(startRef, endRef, startPos, endPos, filter, ref path, DtFindPathOption.NoOption); var status = query.FindPath(startRef, endRef, startPos, endPos, filter, ref path, DtFindPathOption.NoOption);
query.FindStraightPath(startPos, endPos, path, path.Count, straightPath, out var nstraightPath, 256, 0); var straightPath = new List<DtStraightPath>();
Assert.That(nstraightPath, Is.EqualTo(STRAIGHT_PATHS[i].Length)); query.FindStraightPath(startPos, endPos, path, ref straightPath, int.MaxValue, 0);
Assert.That(straightPath.Count, Is.EqualTo(STRAIGHT_PATHS[i].Length));
for (int j = 0; j < STRAIGHT_PATHS[i].Length; j++) for (int j = 0; j < STRAIGHT_PATHS[i].Length; j++)
{ {
Assert.That(straightPath[j].refs, Is.EqualTo(STRAIGHT_PATHS[i][j].refs)); Assert.That(straightPath[j].refs, Is.EqualTo(STRAIGHT_PATHS[i][j].refs));

View File

@ -1,6 +1,5 @@
/* /*
recast4j Copyright (c) 2015-2019 Piotr Piastucki piotr@jtilia.org recast4j Copyright (c) 2015-2019 Piotr Piastucki piotr@jtilia.org
DotRecast Copyright (c) 2023-2024 Choi Ikpil ikpil@naver.com
This software is provided 'as-is', without any express or implied This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages warranty. In no event will the authors be held liable for any damages
@ -23,7 +22,7 @@ using NUnit.Framework;
namespace DotRecast.Detour.Test; namespace DotRecast.Detour.Test;
[Parallelizable]
public class FindPolysAroundCircleTest : AbstractDetourTest public class FindPolysAroundCircleTest : AbstractDetourTest
{ {
private static readonly long[][] REFS = private static readonly long[][] REFS =

View File

@ -1,6 +1,5 @@
/* /*
recast4j Copyright (c) 2015-2019 Piotr Piastucki piotr@jtilia.org recast4j Copyright (c) 2015-2019 Piotr Piastucki piotr@jtilia.org
DotRecast Copyright (c) 2023-2024 Choi Ikpil ikpil@naver.com
This software is provided 'as-is', without any express or implied This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages warranty. In no event will the authors be held liable for any damages
@ -23,7 +22,7 @@ using NUnit.Framework;
namespace DotRecast.Detour.Test; namespace DotRecast.Detour.Test;
[Parallelizable]
public class FindPolysAroundShapeTest : AbstractDetourTest public class FindPolysAroundShapeTest : AbstractDetourTest
{ {
private static readonly long[][] REFS = private static readonly long[][] REFS =

View File

@ -1,6 +1,5 @@
/* /*
recast4j Copyright (c) 2015-2019 Piotr Piastucki piotr@jtilia.org recast4j Copyright (c) 2015-2019 Piotr Piastucki piotr@jtilia.org
DotRecast Copyright (c) 2023-2024 Choi Ikpil ikpil@naver.com
This software is provided 'as-is', without any express or implied This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages warranty. In no event will the authors be held liable for any damages
@ -17,13 +16,13 @@ freely, subject to the following restrictions:
3. This notice may not be removed or altered from any source distribution. 3. This notice may not be removed or altered from any source distribution.
*/ */
using System;
using System.Collections.Generic; using System.Collections.Generic;
using DotRecast.Core; using DotRecast.Core;
using NUnit.Framework; using NUnit.Framework;
namespace DotRecast.Detour.Test; namespace DotRecast.Detour.Test;
[Parallelizable]
public class GetPolyWallSegmentsTest : AbstractDetourTest public class GetPolyWallSegmentsTest : AbstractDetourTest
{ {
private static readonly RcSegmentVert[][] VERTICES = private static readonly RcSegmentVert[][] VERTICES =
@ -83,30 +82,28 @@ public class GetPolyWallSegmentsTest : AbstractDetourTest
[Test] [Test]
public void TestFindDistanceToWall() public void TestFindDistanceToWall()
{ {
const int MAX_SEGS = DtDetour.DT_VERTS_PER_POLYGON * 4; var segmentVerts = new List<RcSegmentVert>();
Span<RcSegmentVert> segs = stackalloc RcSegmentVert[MAX_SEGS]; var segmentRefs = new List<long>();
Span<long> refs = stackalloc long[MAX_SEGS];
int nsegs = 0;
IDtQueryFilter filter = new DtQueryDefaultFilter(); IDtQueryFilter filter = new DtQueryDefaultFilter();
for (int i = 0; i < startRefs.Length; i++) for (int i = 0; i < startRefs.Length; i++)
{ {
var result = query.GetPolyWallSegments(startRefs[i], filter, segs, refs, ref nsegs, MAX_SEGS); var result = query.GetPolyWallSegments(startRefs[i], true, filter, ref segmentVerts, ref segmentRefs);
Assert.That(nsegs, Is.EqualTo(VERTICES[i].Length)); Assert.That(segmentVerts.Count, Is.EqualTo(VERTICES[i].Length));
Assert.That(nsegs, Is.EqualTo(REFS[i].Length)); Assert.That(segmentRefs.Count, Is.EqualTo(REFS[i].Length));
for (int v = 0; v < VERTICES[i].Length / 6; v++) for (int v = 0; v < VERTICES[i].Length / 6; v++)
{ {
Assert.That(segs[v].vmin.X, Is.EqualTo(VERTICES[i][v].vmin.X).Within(0.001f)); Assert.That(segmentVerts[v].vmin.X, Is.EqualTo(VERTICES[i][v].vmin.X).Within(0.001f));
Assert.That(segs[v].vmin.Y, Is.EqualTo(VERTICES[i][v].vmin.Y).Within(0.001f)); Assert.That(segmentVerts[v].vmin.Y, Is.EqualTo(VERTICES[i][v].vmin.Y).Within(0.001f));
Assert.That(segs[v].vmin.Z, Is.EqualTo(VERTICES[i][v].vmin.Z).Within(0.001f)); Assert.That(segmentVerts[v].vmin.Z, Is.EqualTo(VERTICES[i][v].vmin.Z).Within(0.001f));
Assert.That(segs[v].vmax.X, Is.EqualTo(VERTICES[i][v].vmax.X).Within(0.001f)); Assert.That(segmentVerts[v].vmax.X, Is.EqualTo(VERTICES[i][v].vmax.X).Within(0.001f));
Assert.That(segs[v].vmax.Y, Is.EqualTo(VERTICES[i][v].vmax.Y).Within(0.001f)); Assert.That(segmentVerts[v].vmax.Y, Is.EqualTo(VERTICES[i][v].vmax.Y).Within(0.001f));
Assert.That(segs[v].vmax.Z, Is.EqualTo(VERTICES[i][v].vmax.Z).Within(0.001f)); Assert.That(segmentVerts[v].vmax.Z, Is.EqualTo(VERTICES[i][v].vmax.Z).Within(0.001f));
} }
for (int v = 0; v < REFS[i].Length; v++) for (int v = 0; v < REFS[i].Length; v++)
{ {
Assert.That(refs[v], Is.EqualTo(REFS[i][v])); Assert.That(segmentRefs[v], Is.EqualTo(REFS[i][v]));
} }
} }
} }

View File

@ -1,6 +1,5 @@
/* /*
recast4j Copyright (c) 2015-2019 Piotr Piastucki piotr@jtilia.org recast4j Copyright (c) 2015-2019 Piotr Piastucki piotr@jtilia.org
DotRecast Copyright (c) 2023-2024 Choi Ikpil ikpil@naver.com
This software is provided 'as-is', without any express or implied This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages warranty. In no event will the authors be held liable for any damages
@ -24,7 +23,7 @@ using NUnit.Framework;
namespace DotRecast.Detour.Test.Io; namespace DotRecast.Detour.Test.Io;
[Parallelizable]
public class MeshDataReaderWriterTest public class MeshDataReaderWriterTest
{ {
private const int VERTS_PER_POLYGON = 6; private const int VERTS_PER_POLYGON = 6;
@ -33,7 +32,8 @@ public class MeshDataReaderWriterTest
[SetUp] [SetUp]
public void SetUp() public void SetUp()
{ {
meshData = TestMeshDataFactory.Create(); RecastTestMeshBuilder rcBuilder = new RecastTestMeshBuilder();
meshData = rcBuilder.GetMeshData();
} }
[Test] [Test]
@ -117,8 +117,11 @@ public class MeshDataReaderWriterTest
for (int i = 0; i < meshData.header.bvNodeCount; i++) for (int i = 0; i < meshData.header.bvNodeCount; i++)
{ {
Assert.That(readData.bvTree[i].i, Is.EqualTo(meshData.bvTree[i].i)); Assert.That(readData.bvTree[i].i, Is.EqualTo(meshData.bvTree[i].i));
Assert.That(readData.bvTree[i].bmin, Is.EqualTo(meshData.bvTree[i].bmin)); for (int j = 0; j < 3; j++)
Assert.That(readData.bvTree[i].bmax, Is.EqualTo(meshData.bvTree[i].bmax)); {
Assert.That(readData.bvTree[i].bmin[j], Is.EqualTo(meshData.bvTree[i].bmin[j]));
Assert.That(readData.bvTree[i].bmax[j], Is.EqualTo(meshData.bvTree[i].bmax[j]));
}
} }
for (int i = 0; i < meshData.header.offMeshConCount; i++) for (int i = 0; i < meshData.header.offMeshConCount; i++)
@ -128,7 +131,7 @@ public class MeshDataReaderWriterTest
Assert.That(readData.offMeshCons[i].poly, Is.EqualTo(meshData.offMeshCons[i].poly)); 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].side, Is.EqualTo(meshData.offMeshCons[i].side));
Assert.That(readData.offMeshCons[i].userId, Is.EqualTo(meshData.offMeshCons[i].userId)); Assert.That(readData.offMeshCons[i].userId, Is.EqualTo(meshData.offMeshCons[i].userId));
for (int j = 0; j < 2; j++) for (int j = 0; j < 6; j++)
{ {
Assert.That(readData.offMeshCons[i].pos[j], Is.EqualTo(meshData.offMeshCons[i].pos[j])); Assert.That(readData.offMeshCons[i].pos[j], Is.EqualTo(meshData.offMeshCons[i].pos[j]));
} }

View File

@ -1,6 +1,5 @@
/* /*
recast4j Copyright (c) 2015-2019 Piotr Piastucki piotr@jtilia.org recast4j Copyright (c) 2015-2019 Piotr Piastucki piotr@jtilia.org
DotRecast Copyright (c) 2023-2024 Choi Ikpil ikpil@naver.com
This software is provided 'as-is', without any express or implied This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages warranty. In no event will the authors be held liable for any damages
@ -25,6 +24,7 @@ using NUnit.Framework;
namespace DotRecast.Detour.Test.Io; namespace DotRecast.Detour.Test.Io;
[Parallelizable]
public class MeshSetReaderTest public class MeshSetReaderTest
{ {
private readonly DtMeshSetReader reader = new DtMeshSetReader(); private readonly DtMeshSetReader reader = new DtMeshSetReader();
@ -32,35 +32,27 @@ public class MeshSetReaderTest
[Test] [Test]
public void TestNavmesh() public void TestNavmesh()
{ {
byte[] @is = RcIO.ReadFileIfFound("all_tiles_navmesh.bin"); byte[] @is = RcResources.Load("all_tiles_navmesh.bin");
using var ms = new MemoryStream(@is); using var ms = new MemoryStream(@is);
using var br = new BinaryReader(ms); using var br = new BinaryReader(ms);
DtNavMesh mesh = reader.Read(br, 6); DtNavMesh mesh = reader.Read(br, 6);
Assert.That(mesh.GetMaxTiles(), Is.EqualTo(128)); Assert.That(mesh.GetMaxTiles(), Is.EqualTo(128));
Assert.That(mesh.GetParams().maxPolys, Is.EqualTo(0x8000)); Assert.That(mesh.GetParams().maxPolys, Is.EqualTo(0x8000));
Assert.That(mesh.GetParams().tileWidth, Is.EqualTo(9.6f).Within(0.001f)); Assert.That(mesh.GetParams().tileWidth, Is.EqualTo(9.6f).Within(0.001f));
List<DtMeshTile> tiles = mesh.GetTilesAt(4, 7);
const int MAX_NEIS = 32; Assert.That(tiles.Count, Is.EqualTo(1));
DtMeshTile[] tiles = new DtMeshTile[MAX_NEIS];
int nneis = 0;
nneis = mesh.GetTilesAt(4, 7, tiles, MAX_NEIS);
Assert.That(nneis, Is.EqualTo(1));
Assert.That(tiles[0].data.polys.Length, Is.EqualTo(7)); Assert.That(tiles[0].data.polys.Length, Is.EqualTo(7));
Assert.That(tiles[0].data.verts.Length, Is.EqualTo(22 * 3)); Assert.That(tiles[0].data.verts.Length, Is.EqualTo(22 * 3));
tiles = mesh.GetTilesAt(1, 6);
nneis = mesh.GetTilesAt(1, 6, tiles, MAX_NEIS); Assert.That(tiles.Count, Is.EqualTo(1));
Assert.That(nneis, Is.EqualTo(1));
Assert.That(tiles[0].data.polys.Length, Is.EqualTo(7)); Assert.That(tiles[0].data.polys.Length, Is.EqualTo(7));
Assert.That(tiles[0].data.verts.Length, Is.EqualTo(26 * 3)); Assert.That(tiles[0].data.verts.Length, Is.EqualTo(26 * 3));
tiles = mesh.GetTilesAt(6, 2);
nneis = mesh.GetTilesAt(6, 2, tiles, MAX_NEIS); Assert.That(tiles.Count, Is.EqualTo(1));
Assert.That(nneis, Is.EqualTo(1));
Assert.That(tiles[0].data.polys.Length, Is.EqualTo(1)); Assert.That(tiles[0].data.polys.Length, Is.EqualTo(1));
Assert.That(tiles[0].data.verts.Length, Is.EqualTo(4 * 3)); Assert.That(tiles[0].data.verts.Length, Is.EqualTo(4 * 3));
tiles = mesh.GetTilesAt(7, 6);
nneis = mesh.GetTilesAt(7, 6, tiles, MAX_NEIS); Assert.That(tiles.Count, Is.EqualTo(1));
Assert.That(nneis, Is.EqualTo(1));
Assert.That(tiles[0].data.polys.Length, Is.EqualTo(8)); Assert.That(tiles[0].data.polys.Length, Is.EqualTo(8));
Assert.That(tiles[0].data.verts.Length, Is.EqualTo(24 * 3)); Assert.That(tiles[0].data.verts.Length, Is.EqualTo(24 * 3));
} }
@ -68,7 +60,7 @@ public class MeshSetReaderTest
[Test] [Test]
public void TestDungeon() public void TestDungeon()
{ {
byte[] @is = RcIO.ReadFileIfFound("dungeon_all_tiles_navmesh.bin"); byte[] @is = RcResources.Load("dungeon_all_tiles_navmesh.bin");
using var ms = new MemoryStream(@is); using var ms = new MemoryStream(@is);
using var br = new BinaryReader(ms); using var br = new BinaryReader(ms);
@ -76,28 +68,20 @@ public class MeshSetReaderTest
Assert.That(mesh.GetMaxTiles(), Is.EqualTo(128)); Assert.That(mesh.GetMaxTiles(), Is.EqualTo(128));
Assert.That(mesh.GetParams().maxPolys, Is.EqualTo(0x8000)); Assert.That(mesh.GetParams().maxPolys, Is.EqualTo(0x8000));
Assert.That(mesh.GetParams().tileWidth, Is.EqualTo(9.6f).Within(0.001f)); Assert.That(mesh.GetParams().tileWidth, Is.EqualTo(9.6f).Within(0.001f));
List<DtMeshTile> tiles = mesh.GetTilesAt(6, 9);
const int MAX_NEIS = 32; Assert.That(tiles.Count, Is.EqualTo(1));
DtMeshTile[] tiles = new DtMeshTile[MAX_NEIS];
int nneis = 0;
nneis = mesh.GetTilesAt(6, 9, tiles, MAX_NEIS);
Assert.That(nneis, Is.EqualTo(1));
Assert.That(tiles[0].data.polys.Length, Is.EqualTo(2)); Assert.That(tiles[0].data.polys.Length, Is.EqualTo(2));
Assert.That(tiles[0].data.verts.Length, Is.EqualTo(7 * 3)); Assert.That(tiles[0].data.verts.Length, Is.EqualTo(7 * 3));
tiles = mesh.GetTilesAt(2, 9);
nneis = mesh.GetTilesAt(2, 9, tiles, MAX_NEIS); Assert.That(tiles.Count, Is.EqualTo(1));
Assert.That(nneis, Is.EqualTo(1));
Assert.That(tiles[0].data.polys.Length, Is.EqualTo(2)); Assert.That(tiles[0].data.polys.Length, Is.EqualTo(2));
Assert.That(tiles[0].data.verts.Length, Is.EqualTo(9 * 3)); Assert.That(tiles[0].data.verts.Length, Is.EqualTo(9 * 3));
tiles = mesh.GetTilesAt(4, 3);
nneis = mesh.GetTilesAt(4, 3, tiles, MAX_NEIS); Assert.That(tiles.Count, Is.EqualTo(1));
Assert.That(nneis, Is.EqualTo(1));
Assert.That(tiles[0].data.polys.Length, Is.EqualTo(3)); Assert.That(tiles[0].data.polys.Length, Is.EqualTo(3));
Assert.That(tiles[0].data.verts.Length, Is.EqualTo(6 * 3)); Assert.That(tiles[0].data.verts.Length, Is.EqualTo(6 * 3));
tiles = mesh.GetTilesAt(2, 8);
nneis = mesh.GetTilesAt(2, 8, tiles, MAX_NEIS); Assert.That(tiles.Count, Is.EqualTo(1));
Assert.That(nneis, Is.EqualTo(1));
Assert.That(tiles[0].data.polys.Length, Is.EqualTo(5)); Assert.That(tiles[0].data.polys.Length, Is.EqualTo(5));
Assert.That(tiles[0].data.verts.Length, Is.EqualTo(17 * 3)); Assert.That(tiles[0].data.verts.Length, Is.EqualTo(17 * 3));
} }
@ -105,7 +89,7 @@ public class MeshSetReaderTest
[Test] [Test]
public void TestDungeon32Bit() public void TestDungeon32Bit()
{ {
byte[] @is = RcIO.ReadFileIfFound("dungeon_all_tiles_navmesh_32bit.bin"); byte[] @is = RcResources.Load("dungeon_all_tiles_navmesh_32bit.bin");
using var ms = new MemoryStream(@is); using var ms = new MemoryStream(@is);
using var br = new BinaryReader(ms); using var br = new BinaryReader(ms);
@ -113,28 +97,20 @@ public class MeshSetReaderTest
Assert.That(mesh.GetMaxTiles(), Is.EqualTo(128)); Assert.That(mesh.GetMaxTiles(), Is.EqualTo(128));
Assert.That(mesh.GetParams().maxPolys, Is.EqualTo(0x8000)); Assert.That(mesh.GetParams().maxPolys, Is.EqualTo(0x8000));
Assert.That(mesh.GetParams().tileWidth, Is.EqualTo(9.6f).Within(0.001f)); Assert.That(mesh.GetParams().tileWidth, Is.EqualTo(9.6f).Within(0.001f));
List<DtMeshTile> tiles = mesh.GetTilesAt(6, 9);
const int MAX_NEIS = 32; Assert.That(tiles.Count, Is.EqualTo(1));
DtMeshTile[] tiles = new DtMeshTile[MAX_NEIS];
int nneis = 0;
nneis = mesh.GetTilesAt(6, 9, tiles, MAX_NEIS);
Assert.That(nneis, Is.EqualTo(1));
Assert.That(tiles[0].data.polys.Length, Is.EqualTo(2)); Assert.That(tiles[0].data.polys.Length, Is.EqualTo(2));
Assert.That(tiles[0].data.verts.Length, Is.EqualTo(7 * 3)); Assert.That(tiles[0].data.verts.Length, Is.EqualTo(7 * 3));
tiles = mesh.GetTilesAt(2, 9);
nneis = mesh.GetTilesAt(2, 9, tiles, MAX_NEIS); Assert.That(tiles.Count, Is.EqualTo(1));
Assert.That(nneis, Is.EqualTo(1));
Assert.That(tiles[0].data.polys.Length, Is.EqualTo(2)); Assert.That(tiles[0].data.polys.Length, Is.EqualTo(2));
Assert.That(tiles[0].data.verts.Length, Is.EqualTo(9 * 3)); Assert.That(tiles[0].data.verts.Length, Is.EqualTo(9 * 3));
tiles = mesh.GetTilesAt(4, 3);
nneis = mesh.GetTilesAt(4, 3, tiles, MAX_NEIS); Assert.That(tiles.Count, Is.EqualTo(1));
Assert.That(nneis, Is.EqualTo(1));
Assert.That(tiles[0].data.polys.Length, Is.EqualTo(3)); Assert.That(tiles[0].data.polys.Length, Is.EqualTo(3));
Assert.That(tiles[0].data.verts.Length, Is.EqualTo(6 * 3)); Assert.That(tiles[0].data.verts.Length, Is.EqualTo(6 * 3));
tiles = mesh.GetTilesAt(2, 8);
nneis = mesh.GetTilesAt(2, 8, tiles, MAX_NEIS); Assert.That(tiles.Count, Is.EqualTo(1));
Assert.That(nneis, Is.EqualTo(1));
Assert.That(tiles[0].data.polys.Length, Is.EqualTo(5)); Assert.That(tiles[0].data.polys.Length, Is.EqualTo(5));
Assert.That(tiles[0].data.verts.Length, Is.EqualTo(17 * 3)); Assert.That(tiles[0].data.verts.Length, Is.EqualTo(17 * 3));
} }

View File

@ -1,6 +1,5 @@
/* /*
recast4j Copyright (c) 2015-2019 Piotr Piastucki piotr@jtilia.org recast4j Copyright (c) 2015-2019 Piotr Piastucki piotr@jtilia.org
DotRecast Copyright (c) 2023-2024 Choi Ikpil ikpil@naver.com
This software is provided 'as-is', without any express or implied This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages warranty. In no event will the authors be held liable for any damages
@ -29,6 +28,7 @@ using NUnit.Framework;
namespace DotRecast.Detour.Test.Io; namespace DotRecast.Detour.Test.Io;
[Parallelizable]
public class MeshSetReaderWriterTest public class MeshSetReaderWriterTest
{ {
private readonly DtMeshSetWriter writer = new DtMeshSetWriter(); private readonly DtMeshSetWriter writer = new DtMeshSetWriter();
@ -66,12 +66,11 @@ public class MeshSetReaderWriterTest
header.option.maxTiles = m_maxTiles; header.option.maxTiles = m_maxTiles;
header.option.maxPolys = m_maxPolysPerTile; header.option.maxPolys = m_maxPolysPerTile;
header.numTiles = 0; header.numTiles = 0;
DtNavMesh mesh = new DtNavMesh(); DtNavMesh mesh = new DtNavMesh(header.option, 6);
mesh.Init(header.option, 6);
RcVec3f bmin = geom.GetMeshBoundsMin(); RcVec3f bmin = geom.GetMeshBoundsMin();
RcVec3f bmax = geom.GetMeshBoundsMax(); RcVec3f bmax = geom.GetMeshBoundsMax();
RcRecast.CalcTileCount(bmin, bmax, m_cellSize, m_tileSize, m_tileSize, out var tw, out var th); RcCommons.CalcTileCount(bmin, bmax, m_cellSize, m_tileSize, m_tileSize, out var tw, out var th);
for (int y = 0; y < th; ++y) for (int y = 0; y < th; ++y)
{ {
for (int x = 0; x < tw; ++x) for (int x = 0; x < tw; ++x)
@ -93,7 +92,7 @@ public class MeshSetReaderWriterTest
if (data != null) if (data != null)
{ {
mesh.RemoveTile(mesh.GetTileRefAt(x, y, 0)); mesh.RemoveTile(mesh.GetTileRefAt(x, y, 0));
mesh.AddTile(data, 0, 0, out _); mesh.AddTile(data, 0, 0);
} }
} }
} }
@ -108,28 +107,20 @@ public class MeshSetReaderWriterTest
Assert.That(mesh.GetMaxTiles(), Is.EqualTo(128)); Assert.That(mesh.GetMaxTiles(), Is.EqualTo(128));
Assert.That(mesh.GetParams().maxPolys, Is.EqualTo(0x8000)); Assert.That(mesh.GetParams().maxPolys, Is.EqualTo(0x8000));
Assert.That(mesh.GetParams().tileWidth, Is.EqualTo(9.6f).Within(0.001f)); Assert.That(mesh.GetParams().tileWidth, Is.EqualTo(9.6f).Within(0.001f));
List<DtMeshTile> tiles = mesh.GetTilesAt(6, 9);
const int MAX_NEIS = 32; Assert.That(tiles.Count, Is.EqualTo(1));
DtMeshTile[] tiles = new DtMeshTile[MAX_NEIS];
int nneis = 0;
nneis = mesh.GetTilesAt(6, 9, tiles, MAX_NEIS);
Assert.That(nneis, Is.EqualTo(1));
Assert.That(tiles[0].data.polys.Length, Is.EqualTo(2)); Assert.That(tiles[0].data.polys.Length, Is.EqualTo(2));
Assert.That(tiles[0].data.verts.Length, Is.EqualTo(7 * 3)); Assert.That(tiles[0].data.verts.Length, Is.EqualTo(7 * 3));
tiles = mesh.GetTilesAt(2, 9);
nneis = mesh.GetTilesAt(2, 9, tiles, MAX_NEIS); Assert.That(tiles.Count, Is.EqualTo(1));
Assert.That(nneis, Is.EqualTo(1));
Assert.That(tiles[0].data.polys.Length, Is.EqualTo(2)); Assert.That(tiles[0].data.polys.Length, Is.EqualTo(2));
Assert.That(tiles[0].data.verts.Length, Is.EqualTo(9 * 3)); Assert.That(tiles[0].data.verts.Length, Is.EqualTo(9 * 3));
tiles = mesh.GetTilesAt(4, 3);
nneis = mesh.GetTilesAt(4, 3, tiles, MAX_NEIS); Assert.That(tiles.Count, Is.EqualTo(1));
Assert.That(nneis, Is.EqualTo(1));
Assert.That(tiles[0].data.polys.Length, Is.EqualTo(3)); Assert.That(tiles[0].data.polys.Length, Is.EqualTo(3));
Assert.That(tiles[0].data.verts.Length, Is.EqualTo(6 * 3)); Assert.That(tiles[0].data.verts.Length, Is.EqualTo(6 * 3));
tiles = mesh.GetTilesAt(2, 8);
nneis = mesh.GetTilesAt(2, 8, tiles, MAX_NEIS); Assert.That(tiles.Count, Is.EqualTo(1));
Assert.That(nneis, Is.EqualTo(1));
Assert.That(tiles[0].data.polys.Length, Is.EqualTo(5)); Assert.That(tiles[0].data.polys.Length, Is.EqualTo(5));
Assert.That(tiles[0].data.verts.Length, Is.EqualTo(17 * 3)); Assert.That(tiles[0].data.verts.Length, Is.EqualTo(17 * 3));
} }

View File

@ -1,6 +1,5 @@
/* /*
recast4j Copyright (c) 2015-2019 Piotr Piastucki piotr@jtilia.org recast4j Copyright (c) 2015-2019 Piotr Piastucki piotr@jtilia.org
DotRecast Copyright (c) 2023-2024 Choi Ikpil ikpil@naver.com
This software is provided 'as-is', without any express or implied This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages warranty. In no event will the authors be held liable for any damages
@ -17,13 +16,13 @@ freely, subject to the following restrictions:
3. This notice may not be removed or altered from any source distribution. 3. This notice may not be removed or altered from any source distribution.
*/ */
using System;
using System.Collections.Generic; using System.Collections.Generic;
using DotRecast.Core.Numerics; using DotRecast.Core.Numerics;
using NUnit.Framework; using NUnit.Framework;
namespace DotRecast.Detour.Test; namespace DotRecast.Detour.Test;
[Parallelizable]
public class MoveAlongSurfaceTest : AbstractDetourTest public class MoveAlongSurfaceTest : AbstractDetourTest
{ {
private static readonly long[][] VISITED = private static readonly long[][] VISITED =
@ -70,21 +69,20 @@ public class MoveAlongSurfaceTest : AbstractDetourTest
public void TestMoveAlongSurface() public void TestMoveAlongSurface()
{ {
IDtQueryFilter filter = new DtQueryDefaultFilter(); IDtQueryFilter filter = new DtQueryDefaultFilter();
const int MAX_VISITED = 32; var visited = new List<long>();
Span<long> visited = stackalloc long[MAX_VISITED];
for (int i = 0; i < startRefs.Length; i++) for (int i = 0; i < startRefs.Length; i++)
{ {
long startRef = startRefs[i]; long startRef = startRefs[i];
RcVec3f startPos = startPoss[i]; RcVec3f startPos = startPoss[i];
RcVec3f endPos = endPoss[i]; RcVec3f endPos = endPoss[i];
var status = query.MoveAlongSurface(startRef, startPos, endPos, filter, out var result, visited, out var nvisited, MAX_VISITED); var status = query.MoveAlongSurface(startRef, startPos, endPos, filter, out var result, ref visited);
Assert.That(status.Succeeded(), Is.True); Assert.That(status.Succeeded(), Is.True);
Assert.That(result.X, Is.EqualTo(POSITION[i].X).Within(0.01f)); Assert.That(result.X, Is.EqualTo(POSITION[i].X).Within(0.01f));
Assert.That(result.Y, Is.EqualTo(POSITION[i].Y).Within(0.01f)); Assert.That(result.Y, Is.EqualTo(POSITION[i].Y).Within(0.01f));
Assert.That(result.Z, Is.EqualTo(POSITION[i].Z).Within(0.01f)); Assert.That(result.Z, Is.EqualTo(POSITION[i].Z).Within(0.01f));
Assert.That(nvisited, Is.EqualTo(VISITED[i].Length)); Assert.That(visited.Count, Is.EqualTo(VISITED[i].Length));
for (int j = 0; j < 3; j++) for (int j = 0; j < 3; j++)
{ {
Assert.That(visited[j], Is.EqualTo(VISITED[i][j])); Assert.That(visited[j], Is.EqualTo(VISITED[i][j]));

View File

@ -1,6 +1,5 @@
/* /*
recast4j Copyright (c) 2015-2019 Piotr Piastucki piotr@jtilia.org recast4j Copyright (c) 2015-2019 Piotr Piastucki piotr@jtilia.org
DotRecast Copyright (c) 2023-2024 Choi Ikpil ikpil@naver.com
This software is provided 'as-is', without any express or implied This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages warranty. In no event will the authors be held liable for any damages
@ -17,13 +16,11 @@ freely, subject to the following restrictions:
3. This notice may not be removed or altered from any source distribution. 3. This notice may not be removed or altered from any source distribution.
*/ */
using DotRecast.Core.Numerics;
using NUnit.Framework; using NUnit.Framework;
namespace DotRecast.Detour.Test; namespace DotRecast.Detour.Test;
using static DtDetour; [Parallelizable]
public class NavMeshBuilderTest public class NavMeshBuilderTest
{ {
private DtMeshData nmd; private DtMeshData nmd;
@ -31,7 +28,7 @@ public class NavMeshBuilderTest
[SetUp] [SetUp]
public void SetUp() public void SetUp()
{ {
nmd = TestMeshDataFactory.Create(); nmd = new RecastTestMeshBuilder().GetMeshData();
} }
[Test] [Test]
@ -52,14 +49,14 @@ public class NavMeshBuilderTest
Assert.That(nmd.bvTree[i], Is.Not.Null); Assert.That(nmd.bvTree[i], Is.Not.Null);
} }
for (int i = 0; i < 2; i++) for (int i = 0; i < 6; i++)
{ {
Assert.That(RcVec.Create(nmd.verts, 223 * 3 + (i * 3)), Is.EqualTo(nmd.offMeshCons[0].pos[i])); Assert.That(nmd.verts[223 * 3 + i], Is.EqualTo(nmd.offMeshCons[0].pos[i]));
} }
Assert.That(nmd.offMeshCons[0].rad, Is.EqualTo(0.1f)); Assert.That(nmd.offMeshCons[0].rad, Is.EqualTo(0.1f));
Assert.That(nmd.offMeshCons[0].poly, Is.EqualTo(118)); Assert.That(nmd.offMeshCons[0].poly, Is.EqualTo(118));
Assert.That(nmd.offMeshCons[0].flags, Is.EqualTo(DT_OFFMESH_CON_BIDIR)); Assert.That(nmd.offMeshCons[0].flags, Is.EqualTo(DtNavMesh.DT_OFFMESH_CON_BIDIR));
Assert.That(nmd.offMeshCons[0].side, Is.EqualTo(0xFF)); Assert.That(nmd.offMeshCons[0].side, Is.EqualTo(0xFF));
Assert.That(nmd.offMeshCons[0].userId, Is.EqualTo(0x4567)); Assert.That(nmd.offMeshCons[0].userId, Is.EqualTo(0x4567));
Assert.That(nmd.polys[118].vertCount, Is.EqualTo(2)); Assert.That(nmd.polys[118].vertCount, Is.EqualTo(2));

View File

@ -1,6 +1,6 @@
/* /*
recast4j copyright (c) 2021 Piotr Piastucki piotr@jtilia.org recast4j copyright (c) 2021 Piotr Piastucki piotr@jtilia.org
DotRecast Copyright (c) 2023-2024 Choi Ikpil ikpil@naver.com DotRecast Copyright (c) 2023 Choi Ikpil ikpil@naver.com
This software is provided 'as-is', without any express or implied This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages warranty. In no event will the authors be held liable for any damages
@ -17,13 +17,12 @@ freely, subject to the following restrictions:
3. This notice may not be removed or altered from any source distribution. 3. This notice may not be removed or altered from any source distribution.
*/ */
using System;
using DotRecast.Core.Numerics; using DotRecast.Core.Numerics;
using NUnit.Framework; using NUnit.Framework;
namespace DotRecast.Detour.Test; namespace DotRecast.Detour.Test;
[Parallelizable]
public class PolygonByCircleConstraintTest public class PolygonByCircleConstraintTest
{ {
private readonly IDtPolygonByCircleConstraint _constraint = DtStrictDtPolygonByCircleConstraint.Shared; private readonly IDtPolygonByCircleConstraint _constraint = DtStrictDtPolygonByCircleConstraint.Shared;
@ -33,12 +32,9 @@ public class PolygonByCircleConstraintTest
{ {
float[] polygon = { -2, 0, 2, 2, 0, 2, 2, 0, -2, -2, 0, -2 }; float[] polygon = { -2, 0, 2, 2, 0, 2, 2, 0, -2, -2, 0, -2 };
RcVec3f center = new RcVec3f(1, 0, 1); RcVec3f center = new RcVec3f(1, 0, 1);
var radius = 6; float[] constrained = _constraint.Apply(polygon, center, 6);
float[] buffer = new float[128]; Assert.That(constrained, Is.EqualTo(polygon));
Span<float> constrained = _constraint.Apply(polygon, center, radius, buffer);
Assert.That(constrained.ToArray(), Is.EquivalentTo(polygon));
} }
[Test] [Test]
@ -47,13 +43,10 @@ public class PolygonByCircleConstraintTest
int expectedSize = 21; int expectedSize = 21;
float[] polygon = { -2, 0, 2, 2, 0, 2, 2, 0, -2, -2, 0, -2 }; float[] polygon = { -2, 0, 2, 2, 0, 2, 2, 0, -2, -2, 0, -2 };
RcVec3f center = new RcVec3f(2, 0, 0); RcVec3f center = new RcVec3f(2, 0, 0);
var radius = 3;
float[] buffer = new float[128];
Span<float> constrained = _constraint.Apply(polygon, center, radius, buffer);
float[] constrained = _constraint.Apply(polygon, center, 3);
Assert.That(constrained.Length, Is.EqualTo(expectedSize)); Assert.That(constrained.Length, Is.EqualTo(expectedSize));
Assert.That(constrained.ToArray(), Is.SupersetOf(new[] { 2f, 0f, 2f, 2f, 0f, -2f })); Assert.That(constrained, Is.SupersetOf(new[] { 2f, 0f, 2f, 2f, 0f, -2f }));
} }
[Test] [Test]
@ -62,10 +55,7 @@ public class PolygonByCircleConstraintTest
int expectedSize = 12 * 3; int expectedSize = 12 * 3;
float[] polygon = { -4, 0, 0, -3, 0, 3, 2, 0, 3, 3, 0, -3, -2, 0, -4 }; float[] polygon = { -4, 0, 0, -3, 0, 3, 2, 0, 3, 3, 0, -3, -2, 0, -4 };
RcVec3f center = new RcVec3f(-1, 0, -1); RcVec3f center = new RcVec3f(-1, 0, -1);
var radius = 2; float[] constrained = _constraint.Apply(polygon, center, 2);
float[] buffer = new float[128];
Span<float> constrained = _constraint.Apply(polygon, center, radius, buffer);
Assert.That(constrained.Length, Is.EqualTo(expectedSize)); Assert.That(constrained.Length, Is.EqualTo(expectedSize));
@ -83,13 +73,10 @@ public class PolygonByCircleConstraintTest
int expectedSize = 9 * 3; int expectedSize = 9 * 3;
float[] polygon = { -4, 0, 0, -3, 0, 3, 2, 0, 3, 3, 0, -3, -2, 0, -4 }; float[] polygon = { -4, 0, 0, -3, 0, 3, 2, 0, 3, 3, 0, -3, -2, 0, -4 };
RcVec3f center = new RcVec3f(-2, 0, -1); RcVec3f center = new RcVec3f(-2, 0, -1);
var radius = 3; float[] constrained = _constraint.Apply(polygon, center, 3);
float[] buffer = new float[128];
Span<float> constrained = _constraint.Apply(polygon, center, radius, buffer);
Assert.That(constrained.Length, Is.EqualTo(expectedSize)); Assert.That(constrained.Length, Is.EqualTo(expectedSize));
Assert.That(constrained.ToArray(), Is.SupersetOf(new[] { -2f, 0f, -4f, -4f, 0f, 0f, -3.4641016f, 0.0f, 1.60769534f, -2.0f, 0.0f, 2.0f })); Assert.That(constrained, Is.SupersetOf(new[] { -2f, 0f, -4f, -4f, 0f, 0f, -3.4641016f, 0.0f, 1.60769534f, -2.0f, 0.0f, 2.0f }));
} }
[Test] [Test]
@ -98,12 +85,9 @@ public class PolygonByCircleConstraintTest
int expectedSize = 7 * 3; int expectedSize = 7 * 3;
float[] polygon = { -4, 0, 0, -3, 0, 3, 2, 0, 3, 3, 0, -3, -2, 0, -4 }; float[] polygon = { -4, 0, 0, -3, 0, 3, 2, 0, 3, 3, 0, -3, -2, 0, -4 };
RcVec3f center = new RcVec3f(4, 0, 0); RcVec3f center = new RcVec3f(4, 0, 0);
var radius = 4; float[] constrained = _constraint.Apply(polygon, center, 4);
float[] buffer = new float[128];
Span<float> constrained = _constraint.Apply(polygon, center, radius, buffer);
Assert.That(constrained.Length, Is.EqualTo(expectedSize)); Assert.That(constrained.Length, Is.EqualTo(expectedSize));
Assert.That(constrained.ToArray(), Is.SupersetOf(new[] { 1.53589869f, 0f, 3f, 2f, 0f, 3f, 3f, 0f, -3f })); Assert.That(constrained, Is.SupersetOf(new[] { 1.53589869f, 0f, 3f, 2f, 0f, 3f, 3f, 0f, -3f }));
} }
} }

View File

@ -1,6 +1,5 @@
/* /*
recast4j Copyright (c) 2015-2021 Piotr Piastucki piotr@jtilia.org recast4j Copyright (c) 2015-2021 Piotr Piastucki piotr@jtilia.org
DotRecast Copyright (c) 2023-2024 Choi Ikpil ikpil@naver.com
This software is provided 'as-is', without any express or implied This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages warranty. In no event will the authors be held liable for any damages
@ -20,22 +19,21 @@ freely, subject to the following restrictions:
using System; using System;
using DotRecast.Core; using DotRecast.Core;
using DotRecast.Core.Numerics; using DotRecast.Core.Numerics;
using NUnit.Framework; using NUnit.Framework;
namespace DotRecast.Detour.Test; namespace DotRecast.Detour.Test;
[Parallelizable]
public class RandomPointTest : AbstractDetourTest public class RandomPointTest : AbstractDetourTest
{ {
[Test] [Test]
[Repeat(10)]
public void TestRandom() public void TestRandom()
{ {
RcRand f = new RcRand(1); RcRand f = new RcRand(1);
IDtQueryFilter filter = new DtQueryDefaultFilter(); IDtQueryFilter filter = new DtQueryDefaultFilter();
for (int i = 0; i < 1000; i++)
var begin = RcFrequency.Ticks;
for (int i = 0; i < 10000; i++)
{ {
var status = query.FindRandomPoint(filter, f, out var randomRef, out var randomPt); var status = query.FindRandomPoint(filter, f, out var randomRef, out var randomPt);
Assert.That(status.Succeeded(), Is.True); Assert.That(status.Succeeded(), Is.True);
@ -57,9 +55,6 @@ public class RandomPointTest : AbstractDetourTest
Assert.That(randomPt.Z >= bmin[1], Is.True); Assert.That(randomPt.Z >= bmin[1], Is.True);
Assert.That(randomPt.Z <= bmax[1], Is.True); Assert.That(randomPt.Z <= bmax[1], Is.True);
} }
var ticks = RcFrequency.Ticks - begin;
Console.WriteLine($"RandomPointTest::TestRandom() - {(double)ticks / TimeSpan.TicksPerMillisecond} ms");
} }
[Test] [Test]
@ -108,7 +103,7 @@ public class RandomPointTest : AbstractDetourTest
var status = query.FindRandomPointWithinCircle(randomRef, randomPt, radius, filter, f, out var nextRandomRef, out var nextRandomPt); var status = query.FindRandomPointWithinCircle(randomRef, randomPt, radius, filter, f, out var nextRandomRef, out var nextRandomPt);
Assert.That(status.Failed(), Is.False); Assert.That(status.Failed(), Is.False);
float distance = RcVec.Dist2D(randomPt, nextRandomPt); float distance = RcVecUtils.Dist2D(randomPt, nextRandomPt);
Assert.That(distance <= radius, Is.True); Assert.That(distance <= radius, Is.True);
randomRef = nextRandomRef; randomRef = nextRandomRef;

View File

@ -1,6 +1,5 @@
/* /*
recast4j Copyright (c) 2015-2019 Piotr Piastucki piotr@jtilia.org recast4j Copyright (c) 2015-2019 Piotr Piastucki piotr@jtilia.org
DotRecast Copyright (c) 2023-2024 Choi Ikpil ikpil@naver.com
This software is provided 'as-is', without any express or implied This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages warranty. In no event will the authors be held liable for any damages
@ -17,13 +16,15 @@ freely, subject to the following restrictions:
3. This notice may not be removed or altered from any source distribution. 3. This notice may not be removed or altered from any source distribution.
*/ */
using DotRecast.Core.Numerics;
using DotRecast.Recast; using DotRecast.Recast;
using DotRecast.Recast.Geom; using DotRecast.Recast.Geom;
namespace DotRecast.Detour.Test; namespace DotRecast.Detour.Test;
public static class TestMeshDataFactory public class RecastTestMeshBuilder
{ {
private readonly DtMeshData meshData;
private const float m_cellSize = 0.3f; private const float m_cellSize = 0.3f;
private const float m_cellHeight = 0.2f; private const float m_cellHeight = 0.2f;
private const float m_agentHeight = 2.0f; private const float m_agentHeight = 2.0f;
@ -38,24 +39,27 @@ public static class TestMeshDataFactory
private const float m_detailSampleDist = 6.0f; private const float m_detailSampleDist = 6.0f;
private const float m_detailSampleMaxError = 1.0f; private const float m_detailSampleMaxError = 1.0f;
public static DtMeshData Create() public RecastTestMeshBuilder()
: this(SimpleInputGeomProvider.LoadFile("dungeon.obj"),
RcPartition.WATERSHED,
m_cellSize, m_cellHeight,
m_agentMaxSlope, m_agentHeight, m_agentRadius, m_agentMaxClimb,
m_regionMinSize, m_regionMergeSize,
m_edgeMaxLen, m_edgeMaxError,
m_vertsPerPoly,
m_detailSampleDist, m_detailSampleMaxError)
{ {
IInputGeomProvider geom = SimpleInputGeomProvider.LoadFile("dungeon.obj"); }
RcPartition partition = RcPartition.WATERSHED;
float cellSize = m_cellSize;
float cellHeight = m_cellHeight;
float agentMaxSlope = m_agentMaxSlope;
float agentHeight = m_agentHeight;
float agentRadius = m_agentRadius;
float agentMaxClimb = m_agentMaxClimb;
int regionMinSize = m_regionMinSize;
int regionMergeSize = m_regionMergeSize;
float edgeMaxLen = m_edgeMaxLen;
float edgeMaxError = m_edgeMaxError;
int vertsPerPoly = m_vertsPerPoly;
float detailSampleDist = m_detailSampleDist;
float detailSampleMaxError = m_detailSampleMaxError;
public RecastTestMeshBuilder(IInputGeomProvider geom,
RcPartition partition,
float cellSize, float cellHeight,
float agentMaxSlope, float agentHeight, float agentRadius, float agentMaxClimb,
int regionMinSize, int regionMergeSize,
float edgeMaxLen, float edgeMaxError,
int vertsPerPoly,
float detailSampleDist, float detailSampleMaxError)
{
RcConfig cfg = new RcConfig( RcConfig cfg = new RcConfig(
partition, partition,
cellSize, cellHeight, cellSize, cellHeight,
@ -68,32 +72,32 @@ public static class TestMeshDataFactory
SampleAreaModifications.SAMPLE_AREAMOD_GROUND, true); SampleAreaModifications.SAMPLE_AREAMOD_GROUND, true);
RcBuilderConfig bcfg = new RcBuilderConfig(cfg, geom.GetMeshBoundsMin(), geom.GetMeshBoundsMax()); RcBuilderConfig bcfg = new RcBuilderConfig(cfg, geom.GetMeshBoundsMin(), geom.GetMeshBoundsMax());
RcBuilder rcBuilder = new RcBuilder(); RcBuilder rcBuilder = new RcBuilder();
RcBuilderResult rcResult = rcBuilder.Build(geom, bcfg, false); RcBuilderResult rcResult = rcBuilder.Build(geom, bcfg);
RcPolyMesh pmesh = rcResult.Mesh; RcPolyMesh m_pmesh = rcResult.GetMesh();
for (int i = 0; i < pmesh.npolys; ++i) for (int i = 0; i < m_pmesh.npolys; ++i)
{ {
pmesh.flags[i] = 1; m_pmesh.flags[i] = 1;
} }
RcPolyMeshDetail dmesh = rcResult.MeshDetail; RcPolyMeshDetail m_dmesh = rcResult.GetMeshDetail();
DtNavMeshCreateParams option = new DtNavMeshCreateParams(); DtNavMeshCreateParams option = new DtNavMeshCreateParams();
option.verts = pmesh.verts; option.verts = m_pmesh.verts;
option.vertCount = pmesh.nverts; option.vertCount = m_pmesh.nverts;
option.polys = pmesh.polys; option.polys = m_pmesh.polys;
option.polyAreas = pmesh.areas; option.polyAreas = m_pmesh.areas;
option.polyFlags = pmesh.flags; option.polyFlags = m_pmesh.flags;
option.polyCount = pmesh.npolys; option.polyCount = m_pmesh.npolys;
option.nvp = pmesh.nvp; option.nvp = m_pmesh.nvp;
option.detailMeshes = dmesh.meshes; option.detailMeshes = m_dmesh.meshes;
option.detailVerts = dmesh.verts; option.detailVerts = m_dmesh.verts;
option.detailVertsCount = dmesh.nverts; option.detailVertsCount = m_dmesh.nverts;
option.detailTris = dmesh.tris; option.detailTris = m_dmesh.tris;
option.detailTriCount = dmesh.ntris; option.detailTriCount = m_dmesh.ntris;
option.walkableHeight = agentHeight; option.walkableHeight = agentHeight;
option.walkableRadius = agentRadius; option.walkableRadius = agentRadius;
option.walkableClimb = agentMaxClimb; option.walkableClimb = agentMaxClimb;
option.bmin = pmesh.bmin; option.bmin = m_pmesh.bmin;
option.bmax = pmesh.bmax; option.bmax = m_pmesh.bmax;
option.cs = cellSize; option.cs = cellSize;
option.ch = cellHeight; option.ch = cellHeight;
option.buildBvTree = true; option.buildBvTree = true;
@ -116,8 +120,11 @@ public static class TestMeshDataFactory
option.offMeshConUserID = new int[1]; option.offMeshConUserID = new int[1];
option.offMeshConUserID[0] = 0x4567; option.offMeshConUserID[0] = 0x4567;
option.offMeshConCount = 1; option.offMeshConCount = 1;
var meshData = DtNavMeshBuilder.CreateNavMeshData(option); meshData = DtNavMeshBuilder.CreateNavMeshData(option);
}
public DtMeshData GetMeshData()
{
return meshData; return meshData;
} }
} }

View File

@ -1,6 +1,5 @@
/* /*
recast4j Copyright (c) 2015-2019 Piotr Piastucki piotr@jtilia.org recast4j Copyright (c) 2015-2019 Piotr Piastucki piotr@jtilia.org
DotRecast Copyright (c) 2023-2024 Choi Ikpil ikpil@naver.com
This software is provided 'as-is', without any express or implied This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages warranty. In no event will the authors be held liable for any damages

View File

@ -1,6 +1,5 @@
/* /*
recast4j Copyright (c) 2015-2019 Piotr Piastucki piotr@jtilia.org recast4j Copyright (c) 2015-2019 Piotr Piastucki piotr@jtilia.org
DotRecast Copyright (c) 2023-2024 Choi Ikpil ikpil@naver.com
This software is provided 'as-is', without any express or implied This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages warranty. In no event will the authors be held liable for any damages
@ -28,8 +27,8 @@ public class TestDetourBuilder : DetourBuilder
float agentMaxClimb, int x, int y, bool applyRecastDemoFlags) float agentMaxClimb, int x, int y, bool applyRecastDemoFlags)
{ {
RcBuilder rcBuilder = new RcBuilder(); RcBuilder rcBuilder = new RcBuilder();
RcBuilderResult rcResult = rcBuilder.Build(geom, rcConfig, false); RcBuilderResult rcResult = rcBuilder.Build(geom, rcConfig);
RcPolyMesh pmesh = rcResult.Mesh; RcPolyMesh pmesh = rcResult.GetMesh();
if (applyRecastDemoFlags) if (applyRecastDemoFlags)
{ {
@ -59,7 +58,7 @@ public class TestDetourBuilder : DetourBuilder
} }
} }
RcPolyMeshDetail dmesh = rcResult.MeshDetail; RcPolyMeshDetail dmesh = rcResult.GetMeshDetail();
DtNavMeshCreateParams option = GetNavMeshCreateParams(rcConfig.cfg, pmesh, dmesh, agentHeight, agentRadius, DtNavMeshCreateParams option = GetNavMeshCreateParams(rcConfig.cfg, pmesh, dmesh, agentHeight, agentRadius,
agentMaxClimb); agentMaxClimb);
return Build(option, x, y); return Build(option, x, y);

View File

@ -1,6 +1,5 @@
/* /*
recast4j Copyright (c) 2015-2019 Piotr Piastucki piotr@jtilia.org recast4j Copyright (c) 2015-2019 Piotr Piastucki piotr@jtilia.org
DotRecast Copyright (c) 2023-2024 Choi Ikpil ikpil@naver.com
This software is provided 'as-is', without any express or implied This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages warranty. In no event will the authors be held liable for any damages
@ -18,6 +17,7 @@ freely, subject to the following restrictions:
*/ */
using System.Collections.Generic; using System.Collections.Generic;
using DotRecast.Core.Numerics;
using DotRecast.Recast; using DotRecast.Recast;
using DotRecast.Recast.Geom; using DotRecast.Recast.Geom;
@ -64,8 +64,7 @@ public class TestTiledNavMeshBuilder
navMeshParams.tileHeight = tileSize * cellSize; navMeshParams.tileHeight = tileSize * cellSize;
navMeshParams.maxTiles = 128; navMeshParams.maxTiles = 128;
navMeshParams.maxPolys = 32768; navMeshParams.maxPolys = 32768;
navMesh = new DtNavMesh(); navMesh = new DtNavMesh(navMeshParams, 6);
navMesh.Init(navMeshParams, 6);
// Build all tiles // Build all tiles
RcConfig cfg = new RcConfig(true, tileSize, tileSize, RcConfig.CalcBorder(agentRadius, cellSize), RcConfig cfg = new RcConfig(true, tileSize, tileSize, RcConfig.CalcBorder(agentRadius, cellSize),
@ -79,13 +78,13 @@ public class TestTiledNavMeshBuilder
true, true, true, true, true, true,
SampleAreaModifications.SAMPLE_AREAMOD_GROUND, true); SampleAreaModifications.SAMPLE_AREAMOD_GROUND, true);
RcBuilder rcBuilder = new RcBuilder(); RcBuilder rcBuilder = new RcBuilder();
List<RcBuilderResult> rcResult = rcBuilder.BuildTiles(geom, cfg, false, true); List<RcBuilderResult> rcResult = rcBuilder.BuildTiles(geom, cfg, null);
// Add tiles to nav mesh // Add tiles to nav mesh
foreach (RcBuilderResult result in rcResult) foreach (RcBuilderResult result in rcResult)
{ {
RcPolyMesh pmesh = result.Mesh; RcPolyMesh pmesh = result.GetMesh();
if (pmesh.npolys == 0) if (pmesh.npolys == 0)
{ {
continue; continue;
@ -104,7 +103,7 @@ public class TestTiledNavMeshBuilder
option.polyFlags = pmesh.flags; option.polyFlags = pmesh.flags;
option.polyCount = pmesh.npolys; option.polyCount = pmesh.npolys;
option.nvp = pmesh.nvp; option.nvp = pmesh.nvp;
RcPolyMeshDetail dmesh = result.MeshDetail; RcPolyMeshDetail dmesh = result.GetMeshDetail();
option.detailMeshes = dmesh.meshes; option.detailMeshes = dmesh.meshes;
option.detailVerts = dmesh.verts; option.detailVerts = dmesh.verts;
option.detailVertsCount = dmesh.nverts; option.detailVertsCount = dmesh.nverts;
@ -117,10 +116,10 @@ public class TestTiledNavMeshBuilder
option.bmax = pmesh.bmax; option.bmax = pmesh.bmax;
option.cs = cellSize; option.cs = cellSize;
option.ch = cellHeight; option.ch = cellHeight;
option.tileX = result.TileX; option.tileX = result.tileX;
option.tileZ = result.TileZ; option.tileZ = result.tileZ;
option.buildBvTree = true; option.buildBvTree = true;
navMesh.AddTile(Detour.DtNavMeshBuilder.CreateNavMeshData(option), 0, 0, out _); navMesh.AddTile(DtNavMeshBuilder.CreateNavMeshData(option), 0, 0);
} }
} }

View File

@ -1,6 +1,5 @@
/* /*
recast4j Copyright (c) 2015-2019 Piotr Piastucki piotr@jtilia.org recast4j Copyright (c) 2015-2019 Piotr Piastucki piotr@jtilia.org
DotRecast Copyright (c) 2023-2024 Choi Ikpil ikpil@naver.com
This software is provided 'as-is', without any express or implied This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages warranty. In no event will the authors be held liable for any damages
@ -24,10 +23,10 @@ using NUnit.Framework;
namespace DotRecast.Detour.Test; namespace DotRecast.Detour.Test;
[Parallelizable]
public class TiledFindPathTest public class TiledFindPathTest
{ {
private static readonly DtStatus[] STATUSES = { DtStatus.DT_SUCCESS }; private static readonly DtStatus[] STATUSES = { DtStatus.DT_SUCCSESS };
private static readonly long[][] RESULTS = private static readonly long[][] RESULTS =
{ {

View File

@ -1,7 +1,7 @@
/* /*
Copyright (c) 2009-2010 Mikko Mononen memon@inside.org Copyright (c) 2009-2010 Mikko Mononen memon@inside.org
recast4j copyright (c) 2015-2019 Piotr Piastucki piotr@jtilia.org recast4j copyright (c) 2015-2019 Piotr Piastucki piotr@jtilia.org
DotRecast Copyright (c) 2023-2024 Choi Ikpil ikpil@naver.com DotRecast Copyright (c) 2023 Choi Ikpil ikpil@naver.com
This software is provided 'as-is', without any express or implied This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages warranty. In no event will the authors be held liable for any damages
@ -27,7 +27,7 @@ using NUnit.Framework;
namespace DotRecast.Detour.TileCache.Test; namespace DotRecast.Detour.TileCache.Test;
[Parallelizable]
public class AbstractTileCacheTest public class AbstractTileCacheTest
{ {
private const int EXPECTED_LAYERS_PER_TILE = 4; private const int EXPECTED_LAYERS_PER_TILE = 4;
@ -44,7 +44,7 @@ public class AbstractTileCacheTest
public DtTileCache GetTileCache(IInputGeomProvider geom, RcByteOrder order, bool cCompatibility) public DtTileCache GetTileCache(IInputGeomProvider geom, RcByteOrder order, bool cCompatibility)
{ {
DtTileCacheParams option = new DtTileCacheParams(); DtTileCacheParams option = new DtTileCacheParams();
RcRecast.CalcTileCount(geom.GetMeshBoundsMin(), geom.GetMeshBoundsMax(), m_cellSize, m_tileSize, m_tileSize, out var tw, out var th); RcCommons.CalcTileCount(geom.GetMeshBoundsMin(), geom.GetMeshBoundsMax(), m_cellSize, m_tileSize, m_tileSize, out var tw, out var th);
option.ch = m_cellHeight; option.ch = m_cellHeight;
option.cs = m_cellSize; option.cs = m_cellSize;
option.orig = geom.GetMeshBoundsMin(); option.orig = geom.GetMeshBoundsMin();
@ -64,8 +64,7 @@ public class AbstractTileCacheTest
navMeshParams.maxTiles = 256; navMeshParams.maxTiles = 256;
navMeshParams.maxPolys = 16384; navMeshParams.maxPolys = 16384;
var navMesh = new DtNavMesh(); var navMesh = new DtNavMesh(navMeshParams, 6);
navMesh.Init(navMeshParams, 6);
var comp = DtTileCacheCompressorFactory.Shared.Create(cCompatibility ? 0 : 1); var comp = DtTileCacheCompressorFactory.Shared.Create(cCompatibility ? 0 : 1);
var storageParams = new DtTileCacheStorageParams(order, cCompatibility); var storageParams = new DtTileCacheStorageParams(order, cCompatibility);
var process = new TestTileCacheMeshProcess(); var process = new TestTileCacheMeshProcess();

View File

@ -1,25 +1,25 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<TargetFrameworks>net6.0;net7.0;net8.0</TargetFrameworks> <TargetFrameworks>net6.0;net7.0</TargetFrameworks>
<IsPackable>false</IsPackable> <IsPackable>false</IsPackable>
<IsTestProject>true</IsTestProject> <IsTestProject>true</IsTestProject>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.11.1" /> <PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.7.2" />
<PackageReference Include="Moq" Version="4.20.72" /> <PackageReference Include="Moq" Version="4.20.69" />
<PackageReference Include="NUnit" Version="4.2.2" /> <PackageReference Include="NUnit" Version="3.13.3"/>
<PackageReference Include="NUnit3TestAdapter" Version="4.6.0" /> <PackageReference Include="NUnit3TestAdapter" Version="4.5.0"/>
<PackageReference Include="NUnit.Analyzers" Version="4.3.0"> <PackageReference Include="NUnit.Analyzers" Version="3.9.0">
<PrivateAssets>all</PrivateAssets> <PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference> </PackageReference>
<PackageReference Include="coverlet.collector" Version="6.0.2"> <PackageReference Include="coverlet.collector" Version="6.0.0">
<PrivateAssets>all</PrivateAssets> <PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference> </PackageReference>
<PackageReference Include="K4os.Compression.LZ4" Version="1.3.8" /> <PackageReference Include="K4os.Compression.LZ4" Version="1.3.6" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>

View File

@ -1,7 +1,7 @@
/* /*
Copyright (c) 2009-2010 Mikko Mononen memon@inside.org Copyright (c) 2009-2010 Mikko Mononen memon@inside.org
recast4j copyright (c) 2015-2019 Piotr Piastucki piotr@jtilia.org recast4j copyright (c) 2015-2019 Piotr Piastucki piotr@jtilia.org
DotRecast Copyright (c) 2023-2024 Choi Ikpil ikpil@naver.com DotRecast Copyright (c) 2023 Choi Ikpil ikpil@naver.com
This software is provided 'as-is', without any express or implied This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages warranty. In no event will the authors be held liable for any damages

View File

@ -1,7 +1,7 @@
/* /*
Copyright (c) 2009-2010 Mikko Mononen memon@inside.org Copyright (c) 2009-2010 Mikko Mononen memon@inside.org
recast4j copyright (c) 2015-2019 Piotr Piastucki piotr@jtilia.org recast4j copyright (c) 2015-2019 Piotr Piastucki piotr@jtilia.org
DotRecast Copyright (c) 2023-2024 Choi Ikpil ikpil@naver.com DotRecast Copyright (c) 2023 Choi Ikpil ikpil@naver.com
This software is provided 'as-is', without any express or implied This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages warranty. In no event will the authors be held liable for any damages
@ -27,7 +27,7 @@ using NUnit.Framework;
namespace DotRecast.Detour.TileCache.Test.Io; namespace DotRecast.Detour.TileCache.Test.Io;
[Parallelizable]
public class TileCacheReaderTest public class TileCacheReaderTest
{ {
private readonly DtTileCacheReader reader = new DtTileCacheReader(DtTileCacheCompressorFactory.Shared); private readonly DtTileCacheReader reader = new DtTileCacheReader(DtTileCacheCompressorFactory.Shared);
@ -35,7 +35,7 @@ public class TileCacheReaderTest
[Test] [Test]
public void TestNavmesh() public void TestNavmesh()
{ {
using var ms = new MemoryStream(RcIO.ReadFileIfFound("all_tiles_tilecache.bin")); using var ms = new MemoryStream(RcResources.Load("all_tiles_tilecache.bin"));
using var br = new BinaryReader(ms); using var br = new BinaryReader(ms);
DtTileCache tc = reader.Read(br, 6, null); DtTileCache tc = reader.Read(br, 6, null);
Assert.That(tc.GetNavMesh().GetMaxTiles(), Is.EqualTo(256)); Assert.That(tc.GetNavMesh().GetMaxTiles(), Is.EqualTo(256));
@ -133,7 +133,7 @@ public class TileCacheReaderTest
[Test] [Test]
public void TestDungeon() public void TestDungeon()
{ {
using var ms = new MemoryStream(RcIO.ReadFileIfFound("dungeon_all_tiles_tilecache.bin")); using var ms = new MemoryStream(RcResources.Load("dungeon_all_tiles_tilecache.bin"));
using var br = new BinaryReader(ms); using var br = new BinaryReader(ms);
DtTileCache tc = reader.Read(br, 6, null); DtTileCache tc = reader.Read(br, 6, null);
Assert.That(tc.GetNavMesh().GetMaxTiles(), Is.EqualTo(256)); Assert.That(tc.GetNavMesh().GetMaxTiles(), Is.EqualTo(256));

View File

@ -1,7 +1,7 @@
/* /*
Copyright (c) 2009-2010 Mikko Mononen memon@inside.org Copyright (c) 2009-2010 Mikko Mononen memon@inside.org
recast4j copyright (c) 2015-2019 Piotr Piastucki piotr@jtilia.org recast4j copyright (c) 2015-2019 Piotr Piastucki piotr@jtilia.org
DotRecast Copyright (c) 2023-2024 Choi Ikpil ikpil@naver.com DotRecast Copyright (c) 2023 Choi Ikpil ikpil@naver.com
This software is provided 'as-is', without any express or implied This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages warranty. In no event will the authors be held liable for any damages
@ -28,7 +28,7 @@ using NUnit.Framework;
namespace DotRecast.Detour.TileCache.Test.Io; namespace DotRecast.Detour.TileCache.Test.Io;
[Parallelizable]
public class TileCacheReaderWriterTest : AbstractTileCacheTest public class TileCacheReaderWriterTest : AbstractTileCacheTest
{ {
private readonly DtTileCacheReader reader = new DtTileCacheReader(DtTileCacheCompressorFactory.Shared); private readonly DtTileCacheReader reader = new DtTileCacheReader(DtTileCacheCompressorFactory.Shared);

View File

@ -1,7 +1,7 @@
/* /*
Copyright (c) 2009-2010 Mikko Mononen memon@inside.org Copyright (c) 2009-2010 Mikko Mononen memon@inside.org
recast4j copyright (c) 2015-2019 Piotr Piastucki piotr@jtilia.org recast4j copyright (c) 2015-2019 Piotr Piastucki piotr@jtilia.org
DotRecast Copyright (c) 2023-2024 Choi Ikpil ikpil@naver.com DotRecast Copyright (c) 2023 Choi Ikpil ikpil@naver.com
This software is provided 'as-is', without any express or implied This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages warranty. In no event will the authors be held liable for any damages

View File

@ -1,7 +1,7 @@
/* /*
Copyright (c) 2009-2010 Mikko Mononen memon@inside.org Copyright (c) 2009-2010 Mikko Mononen memon@inside.org
recast4j copyright (c) 2015-2019 Piotr Piastucki piotr@jtilia.org recast4j copyright (c) 2015-2019 Piotr Piastucki piotr@jtilia.org
DotRecast Copyright (c) 2023-2024 Choi Ikpil ikpil@naver.com DotRecast Copyright (c) 2023 Choi Ikpil ikpil@naver.com
This software is provided 'as-is', without any express or implied This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages warranty. In no event will the authors be held liable for any damages
@ -26,6 +26,7 @@ using NUnit.Framework;
namespace DotRecast.Detour.TileCache.Test; namespace DotRecast.Detour.TileCache.Test;
[Parallelizable]
public class TempObstaclesTest : AbstractTileCacheTest public class TempObstaclesTest : AbstractTileCacheTest
{ {
[Test] [Test]
@ -42,29 +43,21 @@ public class TempObstaclesTest : AbstractTileCacheTest
tc.BuildNavMeshTile(refs); tc.BuildNavMeshTile(refs);
} }
const int MAX_NEIS = 32; List<DtMeshTile> tiles = tc.GetNavMesh().GetTilesAt(1, 4);
DtMeshTile[] tiles = new DtMeshTile[MAX_NEIS];
int nneis = 0;
nneis = tc.GetNavMesh().GetTilesAt(1, 4, tiles, MAX_NEIS);
DtMeshTile tile = tiles[0]; DtMeshTile tile = tiles[0];
Assert.That(tile.data.header.vertCount, Is.EqualTo(16)); Assert.That(tile.data.header.vertCount, Is.EqualTo(16));
Assert.That(tile.data.header.polyCount, Is.EqualTo(6)); Assert.That(tile.data.header.polyCount, Is.EqualTo(6));
long o = tc.AddObstacle(new RcVec3f(-1.815208f, 9.998184f, -20.307983f), 1f, 2f); long o = tc.AddObstacle(new RcVec3f(-1.815208f, 9.998184f, -20.307983f), 1f, 2f);
bool upToDate = tc.Update(); bool upToDate = tc.Update();
Assert.That(upToDate, Is.True); Assert.That(upToDate, Is.True);
tiles = tc.GetNavMesh().GetTilesAt(1, 4);
nneis = tc.GetNavMesh().GetTilesAt(1, 4, tiles, MAX_NEIS);
tile = tiles[0]; tile = tiles[0];
Assert.That(tile.data.header.vertCount, Is.EqualTo(22)); Assert.That(tile.data.header.vertCount, Is.EqualTo(22));
Assert.That(tile.data.header.polyCount, Is.EqualTo(11)); Assert.That(tile.data.header.polyCount, Is.EqualTo(11));
tc.RemoveObstacle(o); tc.RemoveObstacle(o);
upToDate = tc.Update(); upToDate = tc.Update();
Assert.That(upToDate, Is.True); Assert.That(upToDate, Is.True);
tiles = tc.GetNavMesh().GetTilesAt(1, 4);
nneis = tc.GetNavMesh().GetTilesAt(1, 4, tiles, MAX_NEIS);
tile = tiles[0]; tile = tiles[0];
Assert.That(tile.data.header.vertCount, Is.EqualTo(16)); Assert.That(tile.data.header.vertCount, Is.EqualTo(16));
Assert.That(tile.data.header.polyCount, Is.EqualTo(6)); Assert.That(tile.data.header.polyCount, Is.EqualTo(6));
@ -84,32 +77,24 @@ public class TempObstaclesTest : AbstractTileCacheTest
tc.BuildNavMeshTile(refs); tc.BuildNavMeshTile(refs);
} }
const int MAX_NEIS = 32; List<DtMeshTile> tiles = tc.GetNavMesh().GetTilesAt(1, 4);
DtMeshTile[] tiles = new DtMeshTile[MAX_NEIS];
int nneis = 0;
nneis = tc.GetNavMesh().GetTilesAt(1, 4, tiles, MAX_NEIS);
DtMeshTile tile = tiles[0]; DtMeshTile tile = tiles[0];
Assert.That(tile.data.header.vertCount, Is.EqualTo(16)); Assert.That(tile.data.header.vertCount, Is.EqualTo(16));
Assert.That(tile.data.header.polyCount, Is.EqualTo(6)); Assert.That(tile.data.header.polyCount, Is.EqualTo(6));
long o = tc.AddBoxObstacle( long o = tc.AddBoxObstacle(
new RcVec3f(-2.315208f, 9.998184f, -20.807983f), new RcVec3f(-2.315208f, 9.998184f, -20.807983f),
new RcVec3f(-1.315208f, 11.998184f, -19.807983f) new RcVec3f(-1.315208f, 11.998184f, -19.807983f)
); );
bool upToDate = tc.Update(); bool upToDate = tc.Update();
Assert.That(upToDate, Is.True); Assert.That(upToDate, Is.True);
tiles = tc.GetNavMesh().GetTilesAt(1, 4);
nneis = tc.GetNavMesh().GetTilesAt(1, 4, tiles, MAX_NEIS);
tile = tiles[0]; tile = tiles[0];
Assert.That(tile.data.header.vertCount, Is.EqualTo(22)); Assert.That(tile.data.header.vertCount, Is.EqualTo(22));
Assert.That(tile.data.header.polyCount, Is.EqualTo(11)); Assert.That(tile.data.header.polyCount, Is.EqualTo(11));
tc.RemoveObstacle(o); tc.RemoveObstacle(o);
upToDate = tc.Update(); upToDate = tc.Update();
Assert.That(upToDate, Is.True); Assert.That(upToDate, Is.True);
tiles = tc.GetNavMesh().GetTilesAt(1, 4);
nneis = tc.GetNavMesh().GetTilesAt(1, 4, tiles, MAX_NEIS);
tile = tiles[0]; tile = tiles[0];
Assert.That(tile.data.header.vertCount, Is.EqualTo(16)); Assert.That(tile.data.header.vertCount, Is.EqualTo(16));
Assert.That(tile.data.header.polyCount, Is.EqualTo(6)); Assert.That(tile.data.header.polyCount, Is.EqualTo(6));

View File

@ -1,4 +1,4 @@
using DotRecast.Recast.Geom; using DotRecast.Recast.Geom;
namespace DotRecast.Detour.TileCache.Test; namespace DotRecast.Detour.TileCache.Test;

View File

@ -1,7 +1,7 @@
/* /*
Copyright (c) 2009-2010 Mikko Mononen memon@inside.org Copyright (c) 2009-2010 Mikko Mononen memon@inside.org
recast4j copyright (c) 2015-2019 Piotr Piastucki piotr@jtilia.org recast4j copyright (c) 2015-2019 Piotr Piastucki piotr@jtilia.org
DotRecast Copyright (c) 2023-2024 Choi Ikpil ikpil@naver.com DotRecast Copyright (c) 2023 Choi Ikpil ikpil@naver.com
This software is provided 'as-is', without any express or implied This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages warranty. In no event will the authors be held liable for any damages
@ -73,7 +73,7 @@ public class TestTileLayerBuilder : DtTileCacheLayerBuilder
RcVec3f bmin = geom.GetMeshBoundsMin(); RcVec3f bmin = geom.GetMeshBoundsMin();
RcVec3f bmax = geom.GetMeshBoundsMax(); RcVec3f bmax = geom.GetMeshBoundsMax();
RcRecast.CalcTileCount(bmin, bmax, CellSize, m_tileSize, m_tileSize, out tw, out th); RcCommons.CalcTileCount(bmin, bmax, CellSize, m_tileSize, m_tileSize, out tw, out th);
} }
public List<byte[]> Build(RcByteOrder order, bool cCompatibility, int threads) public List<byte[]> Build(RcByteOrder order, bool cCompatibility, int threads)

View File

@ -1,7 +1,7 @@
/* /*
Copyright (c) 2009-2010 Mikko Mononen memon@inside.org Copyright (c) 2009-2010 Mikko Mononen memon@inside.org
recast4j copyright (c) 2015-2019 Piotr Piastucki piotr@jtilia.org recast4j copyright (c) 2015-2019 Piotr Piastucki piotr@jtilia.org
DotRecast Copyright (c) 2023-2024 Choi Ikpil ikpil@naver.com DotRecast Copyright (c) 2023 Choi Ikpil ikpil@naver.com
This software is provided 'as-is', without any express or implied This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages warranty. In no event will the authors be held liable for any damages
@ -18,7 +18,6 @@ freely, subject to the following restrictions:
3. This notice may not be removed or altered from any source distribution. 3. This notice may not be removed or altered from any source distribution.
*/ */
using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;
using DotRecast.Core; using DotRecast.Core;
@ -30,6 +29,7 @@ using NUnit.Framework;
namespace DotRecast.Detour.TileCache.Test; namespace DotRecast.Detour.TileCache.Test;
[Parallelizable]
public class TileCacheFindPathTest : AbstractTileCacheTest public class TileCacheFindPathTest : AbstractTileCacheTest
{ {
private readonly RcVec3f start = new RcVec3f(39.44734f, 9.998177f, -0.784811f); private readonly RcVec3f start = new RcVec3f(39.44734f, 9.998177f, -0.784811f);
@ -39,7 +39,7 @@ public class TileCacheFindPathTest : AbstractTileCacheTest
public TileCacheFindPathTest() public TileCacheFindPathTest()
{ {
using var msr = new MemoryStream(RcIO.ReadFileIfFound("dungeon_all_tiles_tilecache.bin")); using var msr = new MemoryStream(RcResources.Load("dungeon_all_tiles_tilecache.bin"));
using var br = new BinaryReader(msr); using var br = new BinaryReader(msr);
DtTileCache tcC = new DtTileCacheReader(DtTileCacheCompressorFactory.Shared).Read(br, 6, new TestTileCacheMeshProcess()); DtTileCache tcC = new DtTileCacheReader(DtTileCacheCompressorFactory.Shared).Read(br, 6, new TestTileCacheMeshProcess());
navmesh = tcC.GetNavMesh(); navmesh = tcC.GetNavMesh();
@ -56,11 +56,11 @@ public class TileCacheFindPathTest : AbstractTileCacheTest
var path = new List<long>(); var path = new List<long>();
var status = query.FindPath(startRef, endRef, startPos, endPos, filter, ref path, DtFindPathOption.NoOption); var status = query.FindPath(startRef, endRef, startPos, endPos, filter, ref path, DtFindPathOption.NoOption);
const int maxStraightPath = 256; int maxStraightPath = 256;
int options = 0; int options = 0;
Span<DtStraightPath> pathStr = stackalloc DtStraightPath[maxStraightPath]; var pathStr = new List<DtStraightPath>();
query.FindStraightPath(startPos, endPos, path, path.Count, pathStr, out var npathStr, maxStraightPath, options); query.FindStraightPath(startPos, endPos, path, ref pathStr, maxStraightPath, options);
Assert.That(npathStr, Is.EqualTo(8)); Assert.That(pathStr.Count, Is.EqualTo(8));
} }
} }

View File

@ -1,7 +1,7 @@
/* /*
Copyright (c) 2009-2010 Mikko Mononen memon@inside.org Copyright (c) 2009-2010 Mikko Mononen memon@inside.org
recast4j copyright (c) 2015-2019 Piotr Piastucki piotr@jtilia.org recast4j copyright (c) 2015-2019 Piotr Piastucki piotr@jtilia.org
DotRecast Copyright (c) 2023-2024 Choi Ikpil ikpil@naver.com DotRecast Copyright (c) 2023 Choi Ikpil ikpil@naver.com
This software is provided 'as-is', without any express or implied This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages warranty. In no event will the authors be held liable for any damages
@ -26,14 +26,14 @@ using NUnit.Framework;
namespace DotRecast.Detour.TileCache.Test; namespace DotRecast.Detour.TileCache.Test;
[Parallelizable]
public class TileCacheNavigationTest : AbstractTileCacheTest public class TileCacheNavigationTest : AbstractTileCacheTest
{ {
protected readonly long[] startRefs = { 281475006070787L }; protected readonly long[] startRefs = { 281475006070787L };
protected readonly long[] endRefs = { 281474986147841L }; protected readonly long[] endRefs = { 281474986147841L };
protected readonly RcVec3f[] startPoss = { new RcVec3f(39.447338f, 9.998177f, -0.784811f) }; protected readonly RcVec3f[] startPoss = { new RcVec3f(39.447338f, 9.998177f, -0.784811f) };
protected readonly RcVec3f[] endPoss = { new RcVec3f(19.292645f, 11.611748f, -57.750366f) }; protected readonly RcVec3f[] endPoss = { new RcVec3f(19.292645f, 11.611748f, -57.750366f) };
private readonly DtStatus[] statuses = { DtStatus.DT_SUCCESS }; private readonly DtStatus[] statuses = { DtStatus.DT_SUCCSESS };
private readonly long[][] results = private readonly long[][] results =
{ {

View File

@ -1,7 +1,7 @@
/* /*
Copyright (c) 2009-2010 Mikko Mononen memon@inside.org Copyright (c) 2009-2010 Mikko Mononen memon@inside.org
recast4j copyright (c) 2015-2019 Piotr Piastucki piotr@jtilia.org recast4j copyright (c) 2015-2019 Piotr Piastucki piotr@jtilia.org
DotRecast Copyright (c) 2023-2024 Choi Ikpil ikpil@naver.com DotRecast Copyright (c) 2023 Choi Ikpil ikpil@naver.com
This software is provided 'as-is', without any express or implied This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages warranty. In no event will the authors be held liable for any damages
@ -26,7 +26,7 @@ using NUnit.Framework;
namespace DotRecast.Detour.TileCache.Test; namespace DotRecast.Detour.TileCache.Test;
[Parallelizable]
public class TileCacheTest : AbstractTileCacheTest public class TileCacheTest : AbstractTileCacheTest
{ {
[Test] [Test]

View File

@ -1,4 +1,4 @@
using DotRecast.Detour.TileCache.Io.Compress; using DotRecast.Detour.TileCache.Io.Compress;
using DotRecast.Detour.TileCache.Test.Io; using DotRecast.Detour.TileCache.Test.Io;
using NUnit.Framework; using NUnit.Framework;

View File

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

View File

@ -1,6 +1,5 @@
/* /*
recast4j Copyright (c) 2015-2019 Piotr Piastucki piotr@jtilia.org recast4j Copyright (c) 2015-2019 Piotr Piastucki piotr@jtilia.org
DotRecast Copyright (c) 2023-2024 Choi Ikpil ikpil@naver.com
This software is provided 'as-is', without any express or implied This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages warranty. In no event will the authors be held liable for any damages
@ -25,6 +24,9 @@ using NUnit.Framework;
namespace DotRecast.Recast.Test; namespace DotRecast.Recast.Test;
using static RcConstants;
[Parallelizable]
public class RecastLayersTest public class RecastLayersTest
{ {
private const float m_cellSize = 0.3f; private const float m_cellSize = 0.3f;

View File

@ -1,6 +1,5 @@
/* /*
recast4j Copyright (c) 2015-2019 Piotr Piastucki piotr@jtilia.org recast4j Copyright (c) 2015-2019 Piotr Piastucki piotr@jtilia.org
DotRecast Copyright (c) 2023-2024 Choi Ikpil ikpil@naver.com
This software is provided 'as-is', without any express or implied This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages warranty. In no event will the authors be held liable for any damages
@ -26,9 +25,10 @@ using NUnit.Framework;
namespace DotRecast.Recast.Test; namespace DotRecast.Recast.Test;
using static RcRecast; using static RcConstants;
using static RcAreas; using static RcAreas;
[Parallelizable]
public class RecastSoloMeshTest public class RecastSoloMeshTest
{ {
private const float m_cellSize = 0.3f; private const float m_cellSize = 0.3f;
@ -101,7 +101,7 @@ public class RecastSoloMeshTest
long time = RcFrequency.Ticks; long time = RcFrequency.Ticks;
RcVec3f bmin = geomProvider.GetMeshBoundsMin(); RcVec3f bmin = geomProvider.GetMeshBoundsMin();
RcVec3f bmax = geomProvider.GetMeshBoundsMax(); RcVec3f bmax = geomProvider.GetMeshBoundsMax();
RcContext m_ctx = new RcContext(); RcTelemetry m_ctx = new RcTelemetry();
// //
// Step 1. Initialize build config. // Step 1. Initialize build config.
// //
@ -139,8 +139,8 @@ public class RecastSoloMeshTest
// Find triangles which are walkable based on their slope and rasterize them. // 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 // If your input data is multiple meshes, you can transform them here, calculate
// the are type for each of the meshes and rasterize them. // the are type for each of the meshes and rasterize them.
int[] m_triareas = RcRecast.MarkWalkableTriangles(m_ctx, cfg.WalkableSlopeAngle, verts, tris, ntris, cfg.WalkableAreaMod); int[] m_triareas = RcCommons.MarkWalkableTriangles(m_ctx, cfg.WalkableSlopeAngle, verts, tris, ntris, cfg.WalkableAreaMod);
RcRasterizations.RasterizeTriangles(m_ctx, verts, tris, m_triareas, ntris, m_solid, cfg.WalkableClimb); RcRasterizations.RasterizeTriangles(m_solid, verts, tris, m_triareas, ntris, cfg.WalkableClimb, m_ctx);
} }
// //
@ -217,20 +217,10 @@ public class RecastSoloMeshTest
// Prepare for region partitioning, by calculating distance field // Prepare for region partitioning, by calculating distance field
// along the walkable surface. // along the walkable surface.
RcRegions.BuildDistanceField(m_ctx, m_chf); RcRegions.BuildDistanceField(m_ctx, m_chf);
// Partition the walkable surface into simple regions without holes.
RcRegions.BuildRegions(m_ctx, m_chf, cfg.MinRegionArea, cfg.MergeRegionArea);
} }
else if (m_partitionType == RcPartition.MONOTONE)
{
// Partition the walkable surface into simple regions without holes. // Partition the walkable surface into simple regions without holes.
// Monotone partitioning does not need distancefield. RcRegions.BuildRegions(m_ctx, m_chf, cfg.MinRegionArea, cfg.MergeRegionArea, RcPartitionType.OfValue(cfg.Partition));
RcRegions.BuildRegionsMonotone(m_ctx, m_chf, cfg.MinRegionArea, cfg.MergeRegionArea);
}
else
{
// Partition the walkable surface into simple regions without holes.
RcRegions.BuildLayerRegions(m_ctx, m_chf, cfg.MinRegionArea);
}
Assert.That(m_chf.maxDistance, Is.EqualTo(expDistance), "maxDistance"); Assert.That(m_chf.maxDistance, Is.EqualTo(expDistance), "maxDistance");
Assert.That(m_chf.maxRegions, Is.EqualTo(expRegions), "Regions"); Assert.That(m_chf.maxRegions, Is.EqualTo(expRegions), "Regions");

View File

@ -1,6 +1,5 @@
/* /*
recast4j Copyright (c) 2015-2019 Piotr Piastucki piotr@jtilia.org recast4j Copyright (c) 2015-2019 Piotr Piastucki piotr@jtilia.org
DotRecast Copyright (c) 2023-2024 Choi Ikpil ikpil@naver.com
This software is provided 'as-is', without any express or implied This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages warranty. In no event will the authors be held liable for any damages
@ -22,8 +21,9 @@ using DotRecast.Core;
namespace DotRecast.Recast.Test; namespace DotRecast.Recast.Test;
using static RcRecast; using static RcConstants;
[Parallelizable]
public class RecastTest public class RecastTest
{ {
[Test] [Test]
@ -36,21 +36,21 @@ public class RecastTest
int[] unwalkable_tri = { 0, 2, 1 }; int[] unwalkable_tri = { 0, 2, 1 };
int nt = 1; int nt = 1;
RcContext ctx = new RcContext(); RcTelemetry ctx = new RcTelemetry();
{ {
int[] areas = { 42 }; int[] areas = { 42 };
RcRecast.ClearUnwalkableTriangles(ctx, walkableSlopeAngle, verts, nv, unwalkable_tri, nt, areas); RcCommons.ClearUnwalkableTriangles(ctx, walkableSlopeAngle, verts, nv, unwalkable_tri, nt, areas);
Assert.That(areas[0], Is.EqualTo(RC_NULL_AREA), "Sets area ID of unwalkable triangle to RC_NULL_AREA"); Assert.That(areas[0], Is.EqualTo(RC_NULL_AREA), "Sets area ID of unwalkable triangle to RC_NULL_AREA");
} }
{ {
int[] areas = { 42 }; int[] areas = { 42 };
RcRecast.ClearUnwalkableTriangles(ctx, walkableSlopeAngle, verts, nv, walkable_tri, nt, areas); RcCommons.ClearUnwalkableTriangles(ctx, walkableSlopeAngle, verts, nv, walkable_tri, nt, areas);
Assert.That(areas[0], Is.EqualTo(42), "Does not modify walkable triangle aread ID's"); Assert.That(areas[0], Is.EqualTo(42), "Does not modify walkable triangle aread ID's");
} }
{ {
int[] areas = { 42 }; int[] areas = { 42 };
walkableSlopeAngle = 0; walkableSlopeAngle = 0;
RcRecast.ClearUnwalkableTriangles(ctx, walkableSlopeAngle, verts, nv, walkable_tri, nt, areas); RcCommons.ClearUnwalkableTriangles(ctx, walkableSlopeAngle, verts, nv, walkable_tri, nt, areas);
Assert.That(areas[0], Is.EqualTo(RC_NULL_AREA), "Slopes equal to the max slope are considered unwalkable."); Assert.That(areas[0], Is.EqualTo(RC_NULL_AREA), "Slopes equal to the max slope are considered unwalkable.");
} }
} }

View File

@ -1,6 +1,5 @@
/* /*
recast4j Copyright (c) 2015-2019 Piotr Piastucki piotr@jtilia.org recast4j Copyright (c) 2015-2019 Piotr Piastucki piotr@jtilia.org
DotRecast Copyright (c) 2023-2024 Choi Ikpil ikpil@naver.com
This software is provided 'as-is', without any express or implied This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages warranty. In no event will the authors be held liable for any damages
@ -28,6 +27,7 @@ using NUnit.Framework;
namespace DotRecast.Recast.Test; namespace DotRecast.Recast.Test;
[Parallelizable]
public class RecastTileMeshTest public class RecastTileMeshTest
{ {
private const float m_cellSize = 0.3f; private const float m_cellSize = 0.3f;
@ -70,29 +70,29 @@ public class RecastTileMeshTest
true, true, true, true, true, true,
SampleAreaModifications.SAMPLE_AREAMOD_GROUND, true); SampleAreaModifications.SAMPLE_AREAMOD_GROUND, true);
RcBuilderConfig bcfg = new RcBuilderConfig(cfg, geom.GetMeshBoundsMin(), geom.GetMeshBoundsMax(), 7, 8); RcBuilderConfig bcfg = new RcBuilderConfig(cfg, geom.GetMeshBoundsMin(), geom.GetMeshBoundsMax(), 7, 8);
RcBuilderResult rcResult = builder.Build(geom, bcfg, false); RcBuilderResult rcResult = builder.Build(geom, bcfg);
Assert.That(rcResult.Mesh.npolys, Is.EqualTo(1)); Assert.That(rcResult.GetMesh().npolys, Is.EqualTo(1));
Assert.That(rcResult.Mesh.nverts, Is.EqualTo(5)); Assert.That(rcResult.GetMesh().nverts, Is.EqualTo(5));
bcfg = new RcBuilderConfig(cfg, geom.GetMeshBoundsMin(), geom.GetMeshBoundsMax(), 6, 9); bcfg = new RcBuilderConfig(cfg, geom.GetMeshBoundsMin(), geom.GetMeshBoundsMax(), 6, 9);
rcResult = builder.Build(geom, bcfg, false); rcResult = builder.Build(geom, bcfg);
Assert.That(rcResult.Mesh.npolys, Is.EqualTo(2)); Assert.That(rcResult.GetMesh().npolys, Is.EqualTo(2));
Assert.That(rcResult.Mesh.nverts, Is.EqualTo(7)); Assert.That(rcResult.GetMesh().nverts, Is.EqualTo(7));
bcfg = new RcBuilderConfig(cfg, geom.GetMeshBoundsMin(), geom.GetMeshBoundsMax(), 2, 9); bcfg = new RcBuilderConfig(cfg, geom.GetMeshBoundsMin(), geom.GetMeshBoundsMax(), 2, 9);
rcResult = builder.Build(geom, bcfg, false); rcResult = builder.Build(geom, bcfg);
Assert.That(rcResult.Mesh.npolys, Is.EqualTo(2)); Assert.That(rcResult.GetMesh().npolys, Is.EqualTo(2));
Assert.That(rcResult.Mesh.nverts, Is.EqualTo(9)); Assert.That(rcResult.GetMesh().nverts, Is.EqualTo(9));
bcfg = new RcBuilderConfig(cfg, geom.GetMeshBoundsMin(), geom.GetMeshBoundsMax(), 4, 3); bcfg = new RcBuilderConfig(cfg, geom.GetMeshBoundsMin(), geom.GetMeshBoundsMax(), 4, 3);
rcResult = builder.Build(geom, bcfg, false); rcResult = builder.Build(geom, bcfg);
Assert.That(rcResult.Mesh.npolys, Is.EqualTo(3)); Assert.That(rcResult.GetMesh().npolys, Is.EqualTo(3));
Assert.That(rcResult.Mesh.nverts, Is.EqualTo(6)); Assert.That(rcResult.GetMesh().nverts, Is.EqualTo(6));
bcfg = new RcBuilderConfig(cfg, geom.GetMeshBoundsMin(), geom.GetMeshBoundsMax(), 2, 8); bcfg = new RcBuilderConfig(cfg, geom.GetMeshBoundsMin(), geom.GetMeshBoundsMax(), 2, 8);
rcResult = builder.Build(geom, bcfg, false); rcResult = builder.Build(geom, bcfg);
Assert.That(rcResult.Mesh.npolys, Is.EqualTo(5)); Assert.That(rcResult.GetMesh().npolys, Is.EqualTo(5));
Assert.That(rcResult.Mesh.nverts, Is.EqualTo(17)); Assert.That(rcResult.GetMesh().nverts, Is.EqualTo(17));
bcfg = new RcBuilderConfig(cfg, geom.GetMeshBoundsMin(), geom.GetMeshBoundsMax(), 0, 8); bcfg = new RcBuilderConfig(cfg, geom.GetMeshBoundsMin(), geom.GetMeshBoundsMax(), 0, 8);
rcResult = builder.Build(geom, bcfg, false); rcResult = builder.Build(geom, bcfg);
Assert.That(rcResult.Mesh.npolys, Is.EqualTo(6)); Assert.That(rcResult.GetMesh().npolys, Is.EqualTo(6));
Assert.That(rcResult.Mesh.nverts, Is.EqualTo(15)); Assert.That(rcResult.GetMesh().nverts, Is.EqualTo(15));
} }
[Test] [Test]
@ -138,27 +138,28 @@ public class RecastTileMeshTest
private void Build(IInputGeomProvider geom, RcBuilder builder, RcConfig cfg, int threads, bool validate) private void Build(IInputGeomProvider geom, RcBuilder builder, RcConfig cfg, int threads, bool validate)
{ {
CancellationTokenSource cts = new CancellationTokenSource(); CancellationTokenSource cts = new CancellationTokenSource();
List<RcBuilderResult> tiles = builder.BuildTiles(geom, cfg, false, true, threads, Task.Factory, cts.Token); List<RcBuilderResult> tiles = new();
var task = builder.BuildTilesAsync(geom, cfg, threads, tiles, Task.Factory, cts.Token);
if (validate) if (validate)
{ {
RcBuilderResult rcResult = GetTile(tiles, 7, 8); RcBuilderResult rcResult = GetTile(tiles, 7, 8);
Assert.That(rcResult.Mesh.npolys, Is.EqualTo(1)); Assert.That(rcResult.GetMesh().npolys, Is.EqualTo(1));
Assert.That(rcResult.Mesh.nverts, Is.EqualTo(5)); Assert.That(rcResult.GetMesh().nverts, Is.EqualTo(5));
rcResult = GetTile(tiles, 6, 9); rcResult = GetTile(tiles, 6, 9);
Assert.That(rcResult.Mesh.npolys, Is.EqualTo(2)); Assert.That(rcResult.GetMesh().npolys, Is.EqualTo(2));
Assert.That(rcResult.Mesh.nverts, Is.EqualTo(7)); Assert.That(rcResult.GetMesh().nverts, Is.EqualTo(7));
rcResult = GetTile(tiles, 2, 9); rcResult = GetTile(tiles, 2, 9);
Assert.That(rcResult.Mesh.npolys, Is.EqualTo(2)); Assert.That(rcResult.GetMesh().npolys, Is.EqualTo(2));
Assert.That(rcResult.Mesh.nverts, Is.EqualTo(9)); Assert.That(rcResult.GetMesh().nverts, Is.EqualTo(9));
rcResult = GetTile(tiles, 4, 3); rcResult = GetTile(tiles, 4, 3);
Assert.That(rcResult.Mesh.npolys, Is.EqualTo(3)); Assert.That(rcResult.GetMesh().npolys, Is.EqualTo(3));
Assert.That(rcResult.Mesh.nverts, Is.EqualTo(6)); Assert.That(rcResult.GetMesh().nverts, Is.EqualTo(6));
rcResult = GetTile(tiles, 2, 8); rcResult = GetTile(tiles, 2, 8);
Assert.That(rcResult.Mesh.npolys, Is.EqualTo(5)); Assert.That(rcResult.GetMesh().npolys, Is.EqualTo(5));
Assert.That(rcResult.Mesh.nverts, Is.EqualTo(17)); Assert.That(rcResult.GetMesh().nverts, Is.EqualTo(17));
rcResult = GetTile(tiles, 0, 8); rcResult = GetTile(tiles, 0, 8);
Assert.That(rcResult.Mesh.npolys, Is.EqualTo(6)); Assert.That(rcResult.GetMesh().npolys, Is.EqualTo(6));
Assert.That(rcResult.Mesh.nverts, Is.EqualTo(15)); Assert.That(rcResult.GetMesh().nverts, Is.EqualTo(15));
} }
try try
@ -174,6 +175,6 @@ public class RecastTileMeshTest
private RcBuilderResult GetTile(List<RcBuilderResult> tiles, int x, int z) private RcBuilderResult GetTile(List<RcBuilderResult> tiles, int x, int z)
{ {
return tiles.FirstOrDefault(tile => tile.TileX == x && tile.TileZ == z); return tiles.FirstOrDefault(tile => tile.tileX == x && tile.tileZ == z);
} }
} }

View File

@ -1,6 +1,5 @@
/* /*
recast4j Copyright (c) 2015-2019 Piotr Piastucki piotr@jtilia.org recast4j Copyright (c) 2015-2019 Piotr Piastucki piotr@jtilia.org
DotRecast Copyright (c) 2023-2024 Choi Ikpil ikpil@naver.com
This software is provided 'as-is', without any express or implied This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages warranty. In no event will the authors be held liable for any damages

View File

@ -1,15 +0,0 @@
namespace DotRecast.Tool.PublishToUniRecast;
public class CsProj
{
public readonly string RootPath;
public readonly string Name;
public readonly string TargetPath;
public CsProj(string rootPath, string name, string targetPath)
{
RootPath = rootPath;
Name = name;
TargetPath = targetPath;
}
}

View File

@ -1,9 +0,0 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFrameworks>net6.0;net7.0;net8.0</TargetFrameworks>
<IsPackable>false</IsPackable>
</PropertyGroup>
</Project>

View File

@ -1,169 +0,0 @@
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.IO;
using System.Linq;
namespace DotRecast.Tool.PublishToUniRecast;
public static class Program
{
public static void Main(string[] args)
{
var source = SearchDirectory("DotRecast");
var destination = SearchDirectory("UniRecast");
if (!Directory.Exists(source))
{
throw new Exception("not found source directory");
}
if (!Directory.Exists(destination))
{
throw new Exception("not found destination directory");
}
var ignorePaths = ImmutableArray.Create("bin", "obj");
var projs = ImmutableArray.Create(
// src
new CsProj("src", "DotRecast.Core", "Runtime"),
new CsProj("src", "DotRecast.Recast", "Runtime"),
new CsProj("src", "DotRecast.Detour", "Runtime"),
new CsProj("src", "DotRecast.Detour.Crowd", "Runtime"),
new CsProj("src", "DotRecast.Detour.Dynamic", "Runtime"),
new CsProj("src", "DotRecast.Detour.Extras", "Runtime"),
new CsProj("src", "DotRecast.Detour.TileCache", "Runtime"),
new CsProj("src", "DotRecast.Recast.Toolset", "Runtime")
);
foreach (var proj in projs)
{
var sourcePath = Path.Combine(source, proj.RootPath, $"{proj.Name}");
var destPath = Path.Combine(destination, $"{proj.TargetPath}", $"{proj.Name}");
SyncFiles(sourcePath, destPath, ignorePaths, "*.cs");
}
// // 몇몇 필요한 리소스 복사 하기
// string destResourcePath = destDotRecast + "/resources";
// if (!Directory.Exists(destResourcePath))
// {
// Directory.CreateDirectory(destResourcePath);
// }
// string sourceResourcePath = Path.Combine(dotRecastPath, "resources/nav_test.obj");
// File.Copy(sourceResourcePath, destResourcePath + "/nav_test.obj", true);
}
public static string SearchPath(string searchPath, int depth, out bool isDir)
{
isDir = false;
for (int i = 0; i < depth; ++i)
{
var relativePath = string.Join("", Enumerable.Range(0, i).Select(x => "../"));
var searchingPath = Path.Combine(relativePath, searchPath);
var fullSearchingPath = Path.GetFullPath(searchingPath);
if (File.Exists(fullSearchingPath))
{
return fullSearchingPath;
}
if (Directory.Exists(fullSearchingPath))
{
isDir = true;
return fullSearchingPath;
}
}
return string.Empty;
}
// only directory
public static string SearchDirectory(string dirname, int depth = 10)
{
var searchingPath = SearchPath(dirname, depth, out var isDir);
if (isDir)
{
return searchingPath;
}
var path = Path.GetDirectoryName(searchingPath) ?? string.Empty;
return path;
}
public static string SearchFile(string filename, int depth = 10)
{
var searchingPath = SearchPath(filename, depth, out var isDir);
if (!isDir)
{
return searchingPath;
}
return string.Empty;
}
private static void SyncFiles(string srcRootPath, string dstRootPath, IList<string> ignoreFolders, string searchPattern = "*")
{
// 끝에서부터 이그노어 폴더일 경우 패스
var destLastFolderName = Path.GetFileName(dstRootPath);
if (ignoreFolders.Any(x => x == destLastFolderName))
return;
if (!Directory.Exists(dstRootPath))
Directory.CreateDirectory(dstRootPath);
// 소스파일 추출
var sourceFiles = Directory.GetFiles(srcRootPath, searchPattern).ToList();
var sourceFolders = Directory.GetDirectories(srcRootPath)
.Select(x => new DirectoryInfo(x))
.ToList();
// 대상 파일 추출
var destinationFiles = Directory.GetFiles(dstRootPath, searchPattern).ToList();
var destinationFolders = Directory.GetDirectories(dstRootPath)
.Select(x => new DirectoryInfo(x))
.ToList();
// 대상에 파일이 있는데, 소스에 없을 경우, 대상 파일을 삭제 한다.
foreach (var destinationFile in destinationFiles)
{
var destName = Path.GetFileName(destinationFile);
var found = sourceFiles.Any(x => Path.GetFileName(x) == destName);
if (found)
continue;
File.Delete(destinationFile);
Console.WriteLine($"delete file - {destinationFile}");
}
// 대상에 폴더가 있는데, 소스에 없을 경우, 대상 폴더를 삭제 한다.
foreach (var destinationFolder in destinationFolders)
{
var found = sourceFolders.Any(sourceFolder => sourceFolder.Name == destinationFolder.Name);
if (found)
continue;
Directory.Delete(destinationFolder.FullName, true);
Console.WriteLine($"delete folder - {destinationFolder.FullName}");
}
// 소스 파일을 복사 한다.
foreach (var sourceFile in sourceFiles)
{
var name = Path.GetFileName(sourceFile);
var dest = Path.Combine(dstRootPath, name);
File.Copy(sourceFile, dest, true);
Console.WriteLine($"copy - {sourceFile} => {dest}");
}
// 대상 폴더를 복사 한다
foreach (var sourceFolder in sourceFolders)
{
var dest = Path.Combine(dstRootPath, sourceFolder.Name);
SyncFiles(sourceFolder.FullName, dest, ignoreFolders, searchPattern);
}
}
}